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

This commit is contained in:
Ilya Laktyushin 2020-06-27 04:12:16 +03:00
commit 76baf58242
30 changed files with 2226 additions and 1830 deletions

View File

@ -5635,3 +5635,6 @@ Any member of this group will be able to see messages in the channel.";
"Settings.SetProfilePhotoOrVideo" = "Set Profile Photo or Video";
"Settings.SetNewProfilePhotoOrVideo" = "Set New Profile Photo or Video";
"Conversation.Unarchive" = "Unarhive";
"Conversation.UnarchiveDone" = "The chat was moved to your main list.";

View File

@ -29,6 +29,7 @@ public protocol MediaManager: class {
func filteredPlaylistState(accountId: AccountRecordId, playlistId: SharedMediaPlaylistId, itemId: SharedMediaPlaylistItemId, type: MediaManagerPlayerType) -> Signal<SharedMediaPlayerItemPlaybackState?, NoError>
func setOverlayVideoNode(_ node: OverlayMediaItemNode?)
func hasOverlayVideoNode(_ node: OverlayMediaItemNode) -> Bool
func audioRecorder(beginWithTone: Bool, applicationBindings: TelegramApplicationBindings, beganWithTone: @escaping (Bool) -> Void) -> Signal<ManagedAudioRecorder?, NoError>
}

View File

@ -21,6 +21,9 @@ open class OverlayMediaItemNode: ASDisplayNode {
open var unminimize: (() -> Void)?
public var manualExpandEmbed: (() -> Void)?
public var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)?
open var group: OverlayMediaItemNodeGroup? {
return nil
}

View File

@ -228,6 +228,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
let velocity = recognizer.velocity(in: self.view).x
if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 {
self.state.top?.value.viewWillLeaveNavigation()
navigationTransitionCoordinator.animateCompletion(velocity, completion: { [weak self] in
guard let strongSelf = self, let _ = strongSelf.state.layout, let _ = strongSelf.state.transition, let top = strongSelf.state.top else {
return
@ -399,6 +400,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
})
}
fromValue.value.viewWillLeaveNavigation()
fromValue.value.viewWillDisappear(true)
toValue.value.viewWillAppear(true)
toValue.value.setIgnoreAppearanceMethodInvocations(true)
@ -464,6 +466,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
})
} else {
if let fromValue = fromValue {
fromValue.value.viewWillLeaveNavigation()
fromValue.value.viewWillDisappear(false)
fromValue.value.setIgnoreAppearanceMethodInvocations(true)
fromValue.value.displayNode.removeFromSupernode()

View File

@ -117,6 +117,7 @@ open class NavigationBar: ASDisplayNode {
public var userInfo: Any?
public var makeCustomTransitionNode: ((NavigationBar, Bool) -> CustomNavigationTransitionNode?)?
public var allowsCustomTransition: (() -> Bool)?
private var collapsed: Bool {
get {

View File

@ -561,6 +561,9 @@ public enum TabBarItemContextActionType {
super.viewDidDisappear(animated)
}
open func viewWillLeaveNavigation() {
}
open override func viewDidAppear(_ animated: Bool) {
self.activeInputView = nil

View File

@ -290,6 +290,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
var playbackCompleted: (() -> Void)?
private var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)?
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context
self.presentationData = presentationData
@ -423,6 +425,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
if let _ = self.customUnembedWhenPortrait, layout.size.width < layout.size.height {
self.expandIntoCustomPiP()
}
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
var dismiss = false
@ -888,6 +894,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
if let node = node.0 as? OverlayMediaItemNode {
self.customUnembedWhenPortrait = node.customUnembedWhenPortrait
node.customUnembedWhenPortrait = nil
}
if let node = node.0 as? OverlayMediaItemNode, self.context.sharedContext.mediaManager.hasOverlayVideoNode(node) {
var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view)
let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview)
@ -960,10 +971,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
}
if surfaceCopyView.superview != nil {
videoNode.allowsGroupOpacity = true
videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak videoNode] _ in
videoNode?.allowsGroupOpacity = false
})
}
videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
transformedFrame.origin = CGPoint()
@ -1272,9 +1285,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
@objc func pictureInPictureButtonPressed() {
if let item = self.item, let videoNode = self.videoNode {
private func expandIntoCustomPiP() {
if let item = self.item, let videoNode = self.videoNode, let customUnembedWhenPortrait = customUnembedWhenPortrait {
self.customUnembedWhenPortrait = nil
videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false)
let context = self.context
@ -1306,9 +1319,77 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode {
return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in
return (overlayNode?.view.snapshotContentTree(), nil)
}), addToTransitionSurface: { [weak overlaySupernode, weak overlayNode] view in
}), addToTransitionSurface: { [weak context, weak overlaySupernode, weak overlayNode] view in
guard let context = context, let overlayNode = overlayNode else {
return
}
if context.sharedContext.mediaManager.hasOverlayVideoNode(overlayNode) {
overlaySupernode?.view.addSubview(view)
overlayNode?.canAttachContent = false
}
overlayNode.canAttachContent = false
})
} else if let info = context.sharedContext.mediaManager.galleryHiddenMediaManager.findTarget(messageId: id, media: media) {
return GalleryTransitionArguments(transitionNode: (info.1, info.1.bounds, {
return info.2()
}), addToTransitionSurface: info.0)
}
return nil
}))
case .webPage:
break
}
}
if customUnembedWhenPortrait(overlayNode) {
self.beginCustomDismiss()
self.statusNode.isHidden = true
self.animateOut(toOverlay: overlayNode, completion: { [weak self] in
self?.completeCustomDismiss()
})
}
}
}
@objc func pictureInPictureButtonPressed() {
if let item = self.item, let videoNode = self.videoNode {
videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false)
let context = self.context
let baseNavigationController = self.baseNavigationController()
let mediaManager = self.context.sharedContext.mediaManager
var expandImpl: (() -> Void)?
let overlayNode = OverlayUniversalVideoNode(postbox: self.context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, content: item.content, expand: {
expandImpl?()
}, close: { [weak mediaManager] in
mediaManager?.setOverlayVideoNode(nil)
})
expandImpl = { [weak overlayNode] in
guard let contentInfo = item.contentInfo, let overlayNode = overlayNode else {
return
}
switch contentInfo {
case let .message(message):
let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), replaceRootController: { controller, ready in
if let baseNavigationController = baseNavigationController {
baseNavigationController.replaceTopController(controller, animated: false, ready: ready)
}
}, baseNavigationController: baseNavigationController)
gallery.temporaryDoNotWaitForReady = true
baseNavigationController?.view.endEditing(true)
(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)
}), addToTransitionSurface: { [weak context, weak overlaySupernode, weak overlayNode] view in
guard let context = context, let overlayNode = overlayNode else {
return
}
if context.sharedContext.mediaManager.hasOverlayVideoNode(overlayNode) {
overlaySupernode?.view.addSubview(view)
}
overlayNode.canAttachContent = false
})
} else if let info = context.sharedContext.mediaManager.galleryHiddenMediaManager.findTarget(messageId: id, media: media) {
return GalleryTransitionArguments(transitionNode: (info.1, info.1.bounds, {

View File

@ -1,7 +1,7 @@
import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
import SyncCore
extension PeerStatusSettings {
@ -34,3 +34,22 @@ extension PeerStatusSettings {
}
}
}
public func unarchiveAutomaticallyArchivedPeer(account: Account, peerId: PeerId) {
let _ = (account.postbox.transaction { transaction -> Void in
updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root)
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
guard let currentData = current as? CachedUserData, let currentStatusSettings = currentData.peerStatusSettings else {
return current
}
var statusSettings = currentStatusSettings
statusSettings.flags.remove(.canBlock)
statusSettings.flags.remove(.canReport)
statusSettings.flags.remove(.autoArchived)
return currentData.withUpdatedPeerStatusSettings(statusSettings)
})
}
|> deliverOnMainQueue).start()
let _ = updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: nil).start()
}

View File

@ -45,9 +45,9 @@ public extension TabBarControllerTheme {
}
public extension NavigationBarTheme {
convenience init(rootControllerTheme: PresentationTheme, hideBackground: Bool = false) {
convenience init(rootControllerTheme: PresentationTheme, hideBackground: Bool = false, hideBadge: Bool = false) {
let theme = rootControllerTheme.rootController.navigationBar
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)
self.init(buttonColor: theme.buttonColor, disabledButtonColor: theme.disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.backgroundColor, separatorColor: hideBackground ? .clear : theme.separatorColor, badgeBackgroundColor: hideBadge ? .clear : theme.badgeBackgroundColor, badgeStrokeColor: hideBadge ? .clear : theme.badgeStrokeColor, badgeTextColor: hideBadge ? .clear : theme.badgeTextColor)
}
}
@ -62,8 +62,8 @@ 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(presentationData: PresentationData, hideBackground: Bool, hideBadge: Bool) {
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground, hideBadge: hideBadge), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
}
convenience init(presentationTheme: PresentationTheme, presentationStrings: PresentationStrings) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -24,7 +24,7 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 3,
minSpeed: 1,
maxSpeed: 7,
minScale: 0.55,
maxScale: 0.9,
@ -34,7 +34,7 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 3,
minSpeed: 1,
maxSpeed: 7,
minScale: 0.55,
maxScale: 1,

View File

@ -322,6 +322,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private let peekTimerDisposable = MetaDisposable()
private var hasEmbeddedTitleContent = false
private var isEmbeddedTitleContentHidden = false
public override var customData: Any? {
return self.chatLocation
@ -375,7 +376,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .inline:
navigationBarPresentationData = nil
default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: true)
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: true, hideBadge: false)
}
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
@ -2059,6 +2060,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if case let .peer(peerId) = chatLocation, peerId != context.account.peerId, subject != .scheduledMessages {
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
}
self.navigationBar?.allowsCustomTransition = { [weak self] in
guard let strongSelf = self else {
return false
}
return !strongSelf.chatDisplayNode.hasEmbeddedTitleContent
}
self.chatTitleView = ChatTitleView(account: self.context.account, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder)
self.navigationItem.titleView = self.chatTitleView
@ -2799,9 +2806,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let navigationBarTheme: NavigationBarTheme
if self.hasEmbeddedTitleContent {
navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: true)
navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: true, hideBadge: true)
} else {
navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: true)
navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: true, hideBadge: false)
}
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
@ -4610,6 +4617,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return nil
}))
}
}, unarchivePeer: { [weak self] in
guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else {
return
}
unarchiveAutomaticallyArchivedPeer(account: strongSelf.context.account, peerId: peerId)
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_UnarchiveDone), elevatedLayout: false, action: { _ in return false }), in: .current)
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get()))
switch self.chatLocation {
@ -4756,10 +4770,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}))
}
self.chatDisplayNode.updateHasEmbeddedTitleContent = { [weak self] hasEmbeddedTitleContent in
self.chatDisplayNode.updateHasEmbeddedTitleContent = { [weak self] in
guard let strongSelf = self else {
return
}
let hasEmbeddedTitleContent = strongSelf.chatDisplayNode.hasEmbeddedTitleContent
let isEmbeddedTitleContentHidden = strongSelf.chatDisplayNode.isEmbeddedTitleContentHidden
if strongSelf.hasEmbeddedTitleContent != hasEmbeddedTitleContent {
strongSelf.hasEmbeddedTitleContent = hasEmbeddedTitleContent
@ -4780,6 +4798,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
strongSelf.updateNavigationBarPresentation()
}
if strongSelf.isEmbeddedTitleContentHidden != isEmbeddedTitleContentHidden {
strongSelf.isEmbeddedTitleContentHidden = isEmbeddedTitleContentHidden
if let navigationBar = strongSelf.navigationBar {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
transition.updateAlpha(node: navigationBar, alpha: isEmbeddedTitleContentHidden ? 0.0 : 1.0)
}
}
}
self.interfaceInteraction = interfaceInteraction
@ -5174,6 +5201,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
override public func viewWillLeaveNavigation() {
self.chatDisplayNode.willNavigateAway()
}
override public func inFocusUpdated(isInFocus: Bool) {
self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus)
}

View File

@ -69,6 +69,9 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
private let context: AccountContext
private let backgroundNode: ASDisplayNode
private let videoNode: OverlayUniversalVideoNode
private let disableInternalAnimationIn: Bool
private let isUIHiddenUpdated: () -> Void
private let unembedWhenPortrait: (OverlayMediaItemNode) -> Bool
private var validLayout: (CGSize, CGFloat, CGFloat)?
@ -78,9 +81,14 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
private(set) var interactiveExtension: CGFloat = 0.0
private var freezeInteractiveExtension = false
init(context: AccountContext, videoNode: OverlayUniversalVideoNode, interactiveExtensionUpdated: @escaping (ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void) {
private(set) var isUIHidden: Bool = false
init(context: AccountContext, videoNode: OverlayUniversalVideoNode, disableInternalAnimationIn: Bool, interactiveExtensionUpdated: @escaping (ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void, isUIHiddenUpdated: @escaping () -> Void, unembedWhenPortrait: @escaping (OverlayMediaItemNode) -> Bool) {
self.dismissed = dismissed
self.interactiveExtensionUpdated = interactiveExtensionUpdated
self.isUIHiddenUpdated = isUIHiddenUpdated
self.unembedWhenPortrait = unembedWhenPortrait
self.disableInternalAnimationIn = disableInternalAnimationIn
self.context = context
@ -96,6 +104,14 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
self.addSubnode(self.backgroundNode)
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
self.videoNode.controlsAreShowingUpdated = { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.isUIHidden = !value
strongSelf.isUIHiddenUpdated()
}
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
@ -116,7 +132,7 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
if translation.y > 80.0 {
self.freezeInteractiveExtension = true
self.videoNode.customExpand?()
self.expandIntoPiP()
} else {
self.interactiveExtension = max(0.0, offset)
self.interactiveExtensionUpdated(.immediate)
@ -146,70 +162,61 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
let sourceFrame = self.videoNode.view.convert(self.videoNode.bounds, to: transitionSurface.view)
let targetFrame = self.view.convert(videoFrame, to: transitionSurface.view)
var navigationBarCopy: UIView?
var navigationBarContainer: UIView?
var nodeTransition = transition
if self.disableInternalAnimationIn {
nodeTransition = .immediate
} else {
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)
navigationBarCopy = navigationBar?.view.snapshotView(afterScreenUpdates: true)
let navigationBarContainerValue = UIView()
navigationBarContainer = navigationBarContainerValue
navigationBarContainerValue.frame = targetFrame
navigationBarContainerValue.clipsToBounds = true
transitionSurface.view.addSubview(navigationBarContainerValue)
}
navigationBarContainer.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
if !self.disableInternalAnimationIn {
navigationBarContainer?.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
}
if !self.disableInternalAnimationIn {
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)
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.updateRoundCorners(false, transition: nodeTransition)
if !self.disableInternalAnimationIn {
self.videoNode.showControls()
}
self.videoNode.updateLayout(targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
self.videoNode.updateLayout(targetFrame.size, transition: nodeTransition)
self.videoNode.frame = targetFrame
if self.disableInternalAnimationIn {
self.addSubnode(self.videoNode)
} else {
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()
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
@ -227,6 +234,39 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
transition.updateFrame(node: self.videoNode, frame: videoFrame)
}
}
func expand(intoLandscape: Bool) {
if intoLandscape {
let unembedWhenPortrait = self.unembedWhenPortrait
self.videoNode.customUnembedWhenPortrait = { videoNode in
unembedWhenPortrait(videoNode)
}
}
self.videoNode.expand()
}
func expandIntoPiP() {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
self.videoNode.customExpand = nil
self.videoNode.customClose = nil
let previousFrame = self.videoNode.frame
self.context.sharedContext.mediaManager.setOverlayVideoNode(self.videoNode)
self.videoNode.updateRoundCorners(true, transition: transition)
if let targetSuperview = self.videoNode.view.superview {
let sourceFrame = self.view.convert(previousFrame, to: targetSuperview)
let targetFrame = self.videoNode.frame
self.videoNode.frame = sourceFrame
self.videoNode.updateLayout(sourceFrame.size, transition: .immediate)
transition.updateFrame(node: self.videoNode, frame: targetFrame)
self.videoNode.updateLayout(targetFrame.size, transition: transition)
}
self.dismissed()
}
}
enum ChatEmbeddedTitlePeekContent: Equatable {
@ -387,6 +427,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var embeddedTitlePeekContent: ChatEmbeddedTitlePeekContent = .none
private var embeddedTitleContentNode: ChatEmbeddedTitleContentNode?
private var dismissedEmbeddedTitleContentNode: ChatEmbeddedTitleContentNode?
var hasEmbeddedTitleContent: Bool {
return self.embeddedTitleContentNode != nil
}
init(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) {
self.context = context
@ -892,6 +935,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
let statusBarHeight = layout.insets(options: [.statusBar]).top
if let embeddedTitleContentNode = self.embeddedTitleContentNode, embeddedTitleContentNode.supernode != nil {
if layout.size.width > layout.size.height {
self.embeddedTitleContentNode = nil
self.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode
embeddedTitleContentNode.expand(intoLandscape: true)
self.updateHasEmbeddedTitleContent?()
}
}
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 {
@ -2648,7 +2700,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}
var updateHasEmbeddedTitleContent: ((Bool) -> Void)?
var isEmbeddedTitleContentHidden: Bool {
if let embeddedTitleContentNode = self.embeddedTitleContentNode {
return embeddedTitleContentNode.isUIHidden
} else {
return false
}
}
var updateHasEmbeddedTitleContent: (() -> Void)?
func acceptEmbeddedTitlePeekContent(content: NavigationControllerDropContent) -> Bool {
guard let (_, navigationHeight) = self.validLayout else {
@ -2658,7 +2718,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
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
let embeddedTitleContentNode = ChatEmbeddedTitleContentNode(context: self.context, videoNode: itemNode, disableInternalAnimationIn: false, interactiveExtensionUpdated: { [weak self] transition in
guard let strongSelf = self else {
return
}
@ -2671,12 +2731,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.embeddedTitleContentNode = nil
strongSelf.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode
strongSelf.requestLayout(.animated(duration: 0.25, curve: .spring))
strongSelf.updateHasEmbeddedTitleContent?(false)
strongSelf.updateHasEmbeddedTitleContent?()
}
}, isUIHiddenUpdated: { [weak self] in
self?.updateHasEmbeddedTitleContent?()
}, unembedWhenPortrait: { [weak self] itemNode in
guard let strongSelf = self, let itemNode = itemNode as? OverlayUniversalVideoNode else {
return false
}
strongSelf.unembedWhenPortrait(contentNode: itemNode)
return true
})
self.embeddedTitleContentNode = embeddedTitleContentNode
self.embeddedTitlePeekContent = .none
self.updateHasEmbeddedTitleContent?(true)
self.updateHasEmbeddedTitleContent?()
DispatchQueue.main.async {
self.requestLayout(.animated(duration: 0.25, curve: .spring))
}
@ -2685,4 +2753,46 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
return false
}
private func unembedWhenPortrait(contentNode: OverlayUniversalVideoNode) {
let embeddedTitleContentNode = ChatEmbeddedTitleContentNode(context: self.context, videoNode: contentNode, disableInternalAnimationIn: true, 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?()
}
}, isUIHiddenUpdated: { [weak self] in
self?.updateHasEmbeddedTitleContent?()
}, unembedWhenPortrait: { [weak self] itemNode in
guard let strongSelf = self, let itemNode = itemNode as? OverlayUniversalVideoNode else {
return false
}
strongSelf.unembedWhenPortrait(contentNode: itemNode)
return true
})
self.embeddedTitleContentNode = embeddedTitleContentNode
self.embeddedTitlePeekContent = .none
self.updateHasEmbeddedTitleContent?()
self.requestLayout(.immediate)
}
func willNavigateAway() {
if let embeddedTitleContentNode = self.embeddedTitleContentNode {
self.embeddedTitleContentNode = nil
self.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode
embeddedTitleContentNode.expandIntoPiP()
self.requestLayout(.animated(duration: 0.25, curve: .spring))
self.updateHasEmbeddedTitleContent?()
}
}
}

View File

@ -35,7 +35,7 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
if !peerStatusSettings.flags.isEmpty {
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
displayActionsPanel = true
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) {
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) {
displayActionsPanel = true
} else if peerStatusSettings.contains(.canShareContact) {
displayActionsPanel = true

View File

@ -119,9 +119,82 @@ final class ChatPanelInterfaceInteraction {
let openScheduledMessages: () -> Void
let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
let openPeersNearby: () -> Void
let unarchivePeer: () -> Void
let statuses: ChatPanelInterfaceInteractionStatuses?
init(setupReplyMessage: @escaping (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, deleteSelectedMessages: @escaping () -> Void, reportSelectedMessages: @escaping () -> Void, reportMessages: @escaping ([Message], ContextController?) -> Void, deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, forwardMessages: @escaping ([Message]) -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, dismissMessageSearch: @escaping () -> Void, updateMessageSearch: @escaping (String) -> Void, openSearchResults: @escaping () -> Void, navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, navigateToMessage: @escaping (MessageId) -> Void, navigateToChat: @escaping (PeerId) -> Void, navigateToProfile: @escaping (PeerId) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping () -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, unblockPeer: @escaping () -> Void, pinMessage: @escaping (MessageId) -> Void, unpinMessage: @escaping () -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping (Bool) -> Void, toggleMessageStickerStarred: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, requestUnvoteInMessage: @escaping (MessageId) -> Void, requestStopPollInMessage: @escaping (MessageId) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, reportPeerIrrelevantGeoLocation: @escaping () -> Void, displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void, displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void, openScheduledMessages: @escaping () -> Void, openPeersNearby: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) {
init(
setupReplyMessage: @escaping (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
deleteSelectedMessages: @escaping () -> Void,
reportSelectedMessages: @escaping () -> Void,
reportMessages: @escaping ([Message], ContextController?) -> Void,
deleteMessages: @escaping ([Message], ContextController?, @escaping (ContextMenuActionResult) -> Void) -> Void,
forwardSelectedMessages: @escaping () -> Void,
forwardCurrentForwardMessages: @escaping () -> Void,
forwardMessages: @escaping ([Message]) -> Void,
shareSelectedMessages: @escaping () -> Void,
updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void,
updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void,
openStickers: @escaping () -> Void,
editMessage: @escaping () -> Void,
beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void,
dismissMessageSearch: @escaping () -> Void,
updateMessageSearch: @escaping (String) -> Void,
openSearchResults: @escaping () -> Void,
navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void,
openCalendarSearch: @escaping () -> Void,
toggleMembersSearch: @escaping (Bool) -> Void,
navigateToMessage: @escaping (MessageId) -> Void,
navigateToChat: @escaping (PeerId) -> Void,
navigateToProfile: @escaping (PeerId) -> Void,
openPeerInfo: @escaping () -> Void,
togglePeerNotifications: @escaping () -> Void,
sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool,
sendBotCommand: @escaping (Peer, String) -> Void,
sendBotStart: @escaping (String?) -> Void,
botSwitchChatWithPayload: @escaping (PeerId, String) -> Void,
beginMediaRecording: @escaping (Bool) -> Void,
finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void,
stopMediaRecording: @escaping () -> Void,
lockMediaRecording: @escaping () -> Void,
deleteRecordedMedia: @escaping () -> Void,
sendRecordedMedia: @escaping () -> Void,
displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void,
displayVideoUnmuteTip: @escaping (CGPoint?) -> Void,
switchMediaRecordingMode: @escaping () -> Void,
setupMessageAutoremoveTimeout: @escaping () -> Void,
sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool,
unblockPeer: @escaping () -> Void,
pinMessage: @escaping (MessageId) -> Void,
unpinMessage: @escaping () -> Void,
shareAccountContact: @escaping () -> Void,
reportPeer: @escaping () -> Void,
presentPeerContact: @escaping () -> Void,
dismissReportPeer: @escaping () -> Void,
deleteChat: @escaping () -> Void,
beginCall: @escaping (Bool) -> Void,
toggleMessageStickerStarred: @escaping (MessageId) -> Void,
presentController: @escaping (ViewController, Any?) -> Void,
getNavigationController: @escaping () -> NavigationController?,
presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
navigateFeed: @escaping () -> Void,
openGrouping: @escaping () -> Void,
toggleSilentPost: @escaping () -> Void,
requestUnvoteInMessage: @escaping (MessageId) -> Void,
requestStopPollInMessage: @escaping (MessageId) -> Void,
updateInputLanguage: @escaping ((String?) -> String?) -> Void,
unarchiveChat: @escaping () -> Void,
openLinkEditing: @escaping () -> Void,
reportPeerIrrelevantGeoLocation: @escaping () -> Void,
displaySlowmodeTooltip: @escaping (ASDisplayNode, CGRect) -> Void,
displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void,
openScheduledMessages: @escaping () -> Void,
openPeersNearby: @escaping () -> Void,
displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void,
unarchivePeer: @escaping () -> Void,
statuses: ChatPanelInterfaceInteractionStatuses?
) {
self.setupReplyMessage = setupReplyMessage
self.setupEditMessage = setupEditMessage
self.beginMessageSelection = beginMessageSelection
@ -191,6 +264,7 @@ final class ChatPanelInterfaceInteraction {
self.openScheduledMessages = openScheduledMessages
self.openPeersNearby = openPeersNearby
self.displaySearchResultsTooltip = displaySearchResultsTooltip
self.unarchivePeer = unarchivePeer
self.statuses = statuses
}
}

View File

@ -123,7 +123,7 @@ final class ChatRecentActionsController: TelegramBaseController {
}, openScheduledMessages: {
}, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, statuses: nil)
}, unarchivePeer: {}, statuses: nil)
self.navigationItem.titleView = self.titleView

View File

@ -16,6 +16,7 @@ private enum ChatReportPeerTitleButton: Equatable {
case reportSpam
case reportUserSpam
case reportIrrelevantGeoLocation
case unarchive
func title(strings: PresentationStrings) -> String {
switch self {
@ -35,6 +36,8 @@ private enum ChatReportPeerTitleButton: Equatable {
return strings.Conversation_ReportSpam
case .reportIrrelevantGeoLocation:
return strings.Conversation_ReportGroupLocation
case .unarchive:
return strings.Conversation_Unarchive
}
}
}
@ -42,7 +45,19 @@ private enum ChatReportPeerTitleButton: Equatable {
private func peerButtons(_ state: ChatPresentationInterfaceState) -> [ChatReportPeerTitleButton] {
var buttons: [ChatReportPeerTitleButton] = []
if let peer = state.renderedPeer?.chatMainPeer as? TelegramUser, let contactStatus = state.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings {
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
if peerStatusSettings.contains(.autoArchived) {
if peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.canReport) {
if peer.isDeleted {
buttons.append(.reportUserSpam)
} else {
if !state.peerIsBlocked {
buttons.append(.block)
}
}
}
buttons.append(.unarchive)
} else if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
if peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.canReport) {
if !state.peerIsBlocked {
buttons.append(.block)
@ -375,12 +390,15 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
}
} else {
let additionalRightInset: CGFloat = 36.0
let areaWidth = width - maxInset * 2.0 - additionalRightInset
var areaWidth = width - maxInset * 2.0 - additionalRightInset
let maxButtonWidth = floor(areaWidth / CGFloat(self.buttons.count))
let buttonSizes = self.buttons.map { button -> CGFloat in
return button.1.sizeThatFits(CGSize(width: maxButtonWidth, height: 100.0)).width
}
let buttonsWidth = buttonSizes.reduce(0.0, +)
if buttonsWidth < areaWidth - 20.0 {
areaWidth += additionalRightInset
}
let maxButtonSpacing = floor((areaWidth - buttonsWidth) / CGFloat(self.buttons.count - 1))
let buttonSpacing = min(maxButtonSpacing, 110.0)
let updatedButtonsWidth = buttonsWidth + CGFloat(self.buttons.count - 1) * buttonSpacing
@ -469,6 +487,8 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
self.interfaceInteraction?.shareAccountContact()
case .block, .reportSpam, .reportUserSpam:
self.interfaceInteraction?.reportPeer()
case .unarchive:
self.interfaceInteraction?.unarchivePeer()
case .addContact:
self.interfaceInteraction?.presentPeerContact()
case .reportIrrelevantGeoLocation:

View File

@ -1044,12 +1044,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
audioRecordingDotNode = currentAudioRecordingDotNode
} else {
self.audioRecordingDotNode?.removeFromSupernode()
audioRecordingDotNode = AnimationNode(animation: "voicebin")
audioRecordingDotNode = AnimationNode(animation: "Bin")
self.audioRecordingDotNode = audioRecordingDotNode
self.addSubnode(audioRecordingDotNode)
}
animateDotAppearing = transition.isAnimated && !hideInfo
animateDotAppearing = transition.isAnimated && !isLocked && !hideInfo
audioRecordingDotNode.frame = CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: panelHeight - 44 + 1), size: CGSize(width: 40.0, height: 40))
if animateDotAppearing {

View File

@ -616,4 +616,8 @@ public final class MediaManagerImpl: NSObject, MediaManager {
self.overlayMediaManager.controller?.addNode(node, customTransition: true)
}
}
public func hasOverlayVideoNode(_ node: OverlayMediaItemNode) -> Bool {
return self.currentOverlayVideoNode === node
}
}

View File

@ -416,7 +416,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, openScheduledMessages: {
}, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, statuses: nil)
}, unarchivePeer: {}, statuses: nil)
self.selectionPanel.interfaceInteraction = interfaceInteraction
@ -4390,6 +4390,9 @@ public final class PeerInfoScreen: ViewController {
if other.contentNode != nil {
return nil
}
if let allowsCustomTransition = other.allowsCustomTransition, !allowsCustomTransition() {
return nil
}
if let tag = other.userInfo as? PeerInfoNavigationSourceTag, tag.peerId == peerId {
return PeerInfoNavigationTransitionNode(screenNode: strongSelf.controllerNode, presentationData: strongSelf.presentationData, headerNode: strongSelf.controllerNode.headerNode)
}

View File

@ -550,7 +550,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
}, openScheduledMessages: {
}, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, statuses: nil)
}, unarchivePeer: {}, statuses: nil)
self.updateInterfaceState(animated: false, { return $0 })

View File

@ -289,10 +289,20 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
applyLayout()
}
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
self.playerNode.frame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size))
let fromFrame = self.playerNode.frame
let toFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)
if case let .animated(duration, curve) = transition, fromFrame != toFrame, !fromFrame.width.isZero, !fromFrame.height.isZero, !toFrame.width.isZero, !toFrame.height.isZero {
self.playerNode.frame = toFrame
transition.animatePosition(node: self.playerNode, from: CGPoint(x: fromFrame.center.x - toFrame.center.x, y: fromFrame.center.y - toFrame.center.y))
let transform = CATransform3DScale(CATransform3DIdentity, fromFrame.width / toFrame.width, fromFrame.height / toFrame.height, 1.0)
self.playerNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration)
} else {
transition.updateFrame(node: self.playerNode, frame: toFrame)
}
if let thumbnailNode = self.thumbnailNode {
thumbnailNode.frame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)
transition.updateFrame(node: thumbnailNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0))
}
}

View File

@ -33,17 +33,19 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
private let defaultExpand: () -> Void
public var customExpand: (() -> Void)?
public var customClose: (() -> Void)?
public var controlsAreShowingUpdated: ((Bool) -> 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 controlsAreShowingUpdatedImpl: ((Bool) -> Void)?
var unminimizeImpl: (() -> Void)?
var togglePlayPauseImpl: (() -> Void)?
var closeImpl: (() -> Void)?
let decoration = OverlayVideoDecoration(unminimize: {
let decoration = OverlayVideoDecoration(contentDimensions: content.dimensions, unminimize: {
unminimizeImpl?()
}, togglePlayPause: {
togglePlayPauseImpl?()
@ -51,6 +53,8 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
expandImpl?()
}, close: {
closeImpl?()
}, controlsAreShowingUpdated: { value in
controlsAreShowingUpdatedImpl?(value)
})
self.videoNode = UniversalVideoNode(postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .overlay)
self.decoration = decoration
@ -58,14 +62,7 @@ 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()
}
self?.expand()
}
unminimizeImpl = { [weak self] in
@ -91,6 +88,10 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
}
}
controlsAreShowingUpdatedImpl = { [weak self] value in
self?.controlsAreShowingUpdated?(value)
}
self.clipsToBounds = true
self.cornerRadius = 4.0
@ -104,7 +105,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
if previous != value {
if !value {
strongSelf.dismiss()
close()
closeImpl?()
}
}
}
@ -155,4 +156,12 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
public func showControls() {
self.decoration.showControls()
}
public func expand() {
if let customExpand = self.customExpand {
customExpand()
} else {
self.defaultExpand()
}
}
}

View File

@ -26,11 +26,14 @@ private func setupArrowFrame(size: CGSize, edge: OverlayMediaItemMinimizationEdg
private let backgroundImage = UIImage(bundleImageName: "Chat/Message/OverlayPlainVideoShadow")?.precomposed().resizableImage(withCapInsets: UIEdgeInsets(top: 22.0, left: 25.0, bottom: 26.0, right: 25.0), resizingMode: .stretch)
final class OverlayVideoDecoration: UniversalVideoDecoration {
private let contentDimensions: CGSize
let backgroundNode: ASDisplayNode?
let contentContainerNode: ASDisplayNode
let foregroundNode: ASDisplayNode?
private let unminimize: () -> Void
private let controlsAreShowingUpdated: (Bool) -> Void
private let shadowNode: ASImageNode
private let foregroundContainerNode: ASDisplayNode
@ -46,8 +49,11 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
private var validLayoutSize: CGSize?
init(unminimize: @escaping () -> Void, togglePlayPause: @escaping () -> Void, expand: @escaping () -> Void, close: @escaping () -> Void) {
init(contentDimensions: CGSize, unminimize: @escaping () -> Void, togglePlayPause: @escaping () -> Void, expand: @escaping () -> Void, close: @escaping () -> Void, controlsAreShowingUpdated: @escaping (Bool) -> Void) {
self.contentDimensions = contentDimensions
self.unminimize = unminimize
self.controlsAreShowingUpdated = controlsAreShowingUpdated
self.shadowNode = ASImageNode()
self.shadowNode.image = backgroundImage
@ -78,6 +84,14 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
self.statusDisposable.dispose()
}
private func frameForContent(size: CGSize) -> CGRect {
if !self.contentDimensions.width.isZero && !self.contentDimensions.height.isZero {
let fittedSize = self.contentDimensions.aspectFittedWithOverflow(size, leeway: 10.0)
return CGRect(origin: CGPoint(x: floor(size.width - fittedSize.width) / 2.0, y: floor(size.height - fittedSize.height) / 2.0), size: fittedSize)
}
return CGRect(origin: CGPoint(), size: size)
}
func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?) {
if self.contentNode !== contentNode {
let previous = self.contentNode
@ -93,7 +107,7 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
if contentNode.supernode !== self.contentContainerNode {
self.contentContainerNode.addSubnode(contentNode)
if let validLayoutSize = self.validLayoutSize {
contentNode.frame = CGRect(origin: CGPoint(), size: validLayoutSize)
contentNode.frame = self.frameForContent(size: validLayoutSize)
contentNode.updateLayout(size: validLayoutSize, transition: .immediate)
}
}
@ -107,6 +121,8 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayoutSize = size
let contentFrame = self.frameForContent(size: size)
let shadowInsets = UIEdgeInsets(top: 2.0, left: 3.0, bottom: 4.0, right: 3.0)
transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: -shadowInsets.left, y: -shadowInsets.top), size: CGSize(width: size.width + shadowInsets.left + shadowInsets.right, height: size.height + shadowInsets.top + shadowInsets.bottom)))
@ -129,8 +145,8 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - progressSize.width) / 2.0), y: floorToScreenPixels((size.height - progressSize.height) / 2.0)), size: progressSize))
if let contentNode = self.contentNode {
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(), size: size))
contentNode.updateLayout(size: size, transition: transition)
transition.updateFrame(node: contentNode, frame: contentFrame)
contentNode.updateLayout(size: contentFrame.size, transition: transition)
}
}
@ -141,9 +157,11 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
if self.controlsNode.alpha.isZero {
self.controlsNode.alpha = 1.0
self.controlsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.controlsAreShowingUpdated(true)
} else {
self.controlsNode.alpha = 0.0
self.controlsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
self.controlsAreShowingUpdated(false)
}
}
}
@ -152,6 +170,7 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
if self.controlsNode.alpha.isZero {
self.controlsNode.alpha = 1.0
self.controlsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.controlsAreShowingUpdated(true)
}
}

View File

@ -449,12 +449,12 @@ public final class WalletStrings: Equatable {
public var Wallet_SecureStorageReset_Title: String { return self._s[219]! }
public var Wallet_Receive_CommentHeader: String { return self._s[220]! }
public var Wallet_Info_ReceiveGrams: String { return self._s[221]! }
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
}
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)