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.SetProfilePhotoOrVideo" = "Set Profile Photo or Video";
"Settings.SetNewProfilePhotoOrVideo" = "Set New 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 filteredPlaylistState(accountId: AccountRecordId, playlistId: SharedMediaPlaylistId, itemId: SharedMediaPlaylistItemId, type: MediaManagerPlayerType) -> Signal<SharedMediaPlayerItemPlaybackState?, NoError>
func setOverlayVideoNode(_ node: OverlayMediaItemNode?) func setOverlayVideoNode(_ node: OverlayMediaItemNode?)
func hasOverlayVideoNode(_ node: OverlayMediaItemNode) -> Bool
func audioRecorder(beginWithTone: Bool, applicationBindings: TelegramApplicationBindings, beganWithTone: @escaping (Bool) -> Void) -> Signal<ManagedAudioRecorder?, NoError> 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)? open var unminimize: (() -> Void)?
public var manualExpandEmbed: (() -> Void)?
public var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)?
open var group: OverlayMediaItemNodeGroup? { open var group: OverlayMediaItemNodeGroup? {
return nil return nil
} }

View File

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

View File

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

View File

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

View File

@ -290,6 +290,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
var playbackCompleted: (() -> Void)? 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) { init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
@ -423,6 +425,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { 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) super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
var dismiss = false var dismiss = false
@ -888,6 +894,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
if let node = node.0 as? OverlayMediaItemNode { 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) var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view)
let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview) 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) 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)
} }
videoNode.allowsGroupOpacity = true if surfaceCopyView.superview != nil {
videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak videoNode] _ in videoNode.allowsGroupOpacity = true
videoNode?.allowsGroupOpacity = false 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) videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
transformedFrame.origin = CGPoint() transformedFrame.origin = CGPoint()
@ -1272,9 +1285,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
} }
@objc func pictureInPictureButtonPressed() { private func expandIntoCustomPiP() {
if let item = self.item, let videoNode = self.videoNode { if let item = self.item, let videoNode = self.videoNode, let customUnembedWhenPortrait = customUnembedWhenPortrait {
self.customUnembedWhenPortrait = nil
videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false) videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false)
let context = self.context let context = self.context
@ -1306,9 +1319,77 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode { if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode {
return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in
return (overlayNode?.view.snapshotContentTree(), nil) return (overlayNode?.view.snapshotContentTree(), nil)
}), addToTransitionSurface: { [weak overlaySupernode, weak overlayNode] view in }), addToTransitionSurface: { [weak context, weak overlaySupernode, weak overlayNode] view in
overlaySupernode?.view.addSubview(view) guard let context = context, let overlayNode = overlayNode else {
overlayNode?.canAttachContent = false 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, {
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) { } else if let info = context.sharedContext.mediaManager.galleryHiddenMediaManager.findTarget(messageId: id, media: media) {
return GalleryTransitionArguments(transitionNode: (info.1, info.1.bounds, { return GalleryTransitionArguments(transitionNode: (info.1, info.1.bounds, {

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
import Postbox import Postbox
import TelegramApi import TelegramApi
import SwiftSignalKit
import SyncCore import SyncCore
extension PeerStatusSettings { 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 { 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 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)) self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
} }
convenience init(presentationData: PresentationData, hideBackground: Bool) { convenience init(presentationData: PresentationData, hideBackground: Bool, hideBadge: Bool) {
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground), strings: NavigationBarStrings(presentationStrings: presentationData.strings)) self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground, hideBadge: hideBadge), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
} }
convenience init(presentationTheme: PresentationTheme, presentationStrings: PresentationStrings) { 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, pointsCount: 8,
minRandomness: 1, minRandomness: 1,
maxRandomness: 1, maxRandomness: 1,
minSpeed: 3, minSpeed: 1,
maxSpeed: 7, maxSpeed: 7,
minScale: 0.55, minScale: 0.55,
maxScale: 0.9, maxScale: 0.9,
@ -34,7 +34,7 @@ final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration
pointsCount: 8, pointsCount: 8,
minRandomness: 1, minRandomness: 1,
maxRandomness: 1, maxRandomness: 1,
minSpeed: 3, minSpeed: 1,
maxSpeed: 7, maxSpeed: 7,
minScale: 0.55, minScale: 0.55,
maxScale: 1, maxScale: 1,

View File

@ -322,6 +322,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private let peekTimerDisposable = MetaDisposable() private let peekTimerDisposable = MetaDisposable()
private var hasEmbeddedTitleContent = false private var hasEmbeddedTitleContent = false
private var isEmbeddedTitleContentHidden = false
public override var customData: Any? { public override var customData: Any? {
return self.chatLocation return self.chatLocation
@ -375,7 +376,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .inline: case .inline:
navigationBarPresentationData = nil navigationBarPresentationData = nil
default: 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) 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 { if case let .peer(peerId) = chatLocation, peerId != context.account.peerId, subject != .scheduledMessages {
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId) 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.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 self.navigationItem.titleView = self.chatTitleView
@ -2799,9 +2806,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let navigationBarTheme: NavigationBarTheme let navigationBarTheme: NavigationBarTheme
if self.hasEmbeddedTitleContent { if self.hasEmbeddedTitleContent {
navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: true) navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: true, hideBadge: true)
} else { } 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))) 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 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())) }, 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 { 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 { guard let strongSelf = self else {
return return
} }
let hasEmbeddedTitleContent = strongSelf.chatDisplayNode.hasEmbeddedTitleContent
let isEmbeddedTitleContentHidden = strongSelf.chatDisplayNode.isEmbeddedTitleContentHidden
if strongSelf.hasEmbeddedTitleContent != hasEmbeddedTitleContent { if strongSelf.hasEmbeddedTitleContent != hasEmbeddedTitleContent {
strongSelf.hasEmbeddedTitleContent = hasEmbeddedTitleContent strongSelf.hasEmbeddedTitleContent = hasEmbeddedTitleContent
@ -4780,6 +4798,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
strongSelf.updateNavigationBarPresentation() 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 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) { override public func inFocusUpdated(isInFocus: Bool) {
self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus) self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus)
} }

View File

@ -69,6 +69,9 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
private let context: AccountContext private let context: AccountContext
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let videoNode: OverlayUniversalVideoNode private let videoNode: OverlayUniversalVideoNode
private let disableInternalAnimationIn: Bool
private let isUIHiddenUpdated: () -> Void
private let unembedWhenPortrait: (OverlayMediaItemNode) -> Bool
private var validLayout: (CGSize, CGFloat, CGFloat)? private var validLayout: (CGSize, CGFloat, CGFloat)?
@ -78,9 +81,14 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
private(set) var interactiveExtension: CGFloat = 0.0 private(set) var interactiveExtension: CGFloat = 0.0
private var freezeInteractiveExtension = false 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.dismissed = dismissed
self.interactiveExtensionUpdated = interactiveExtensionUpdated self.interactiveExtensionUpdated = interactiveExtensionUpdated
self.isUIHiddenUpdated = isUIHiddenUpdated
self.unembedWhenPortrait = unembedWhenPortrait
self.disableInternalAnimationIn = disableInternalAnimationIn
self.context = context self.context = context
@ -96,6 +104,14 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))) 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) { @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
@ -116,7 +132,7 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
if translation.y > 80.0 { if translation.y > 80.0 {
self.freezeInteractiveExtension = true self.freezeInteractiveExtension = true
self.videoNode.customExpand?() self.expandIntoPiP()
} else { } else {
self.interactiveExtension = max(0.0, offset) self.interactiveExtension = max(0.0, offset)
self.interactiveExtensionUpdated(.immediate) self.interactiveExtensionUpdated(.immediate)
@ -146,72 +162,63 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
let sourceFrame = self.videoNode.view.convert(self.videoNode.bounds, to: transitionSurface.view) let sourceFrame = self.videoNode.view.convert(self.videoNode.bounds, to: transitionSurface.view)
let targetFrame = self.view.convert(videoFrame, to: transitionSurface.view) let targetFrame = self.view.convert(videoFrame, to: transitionSurface.view)
self.context.sharedContext.mediaManager.setOverlayVideoNode(nil) var navigationBarCopy: UIView?
transitionSurface.addSubnode(self.videoNode) var navigationBarContainer: UIView?
var nodeTransition = transition
let navigationBarCopy = navigationBar?.view.snapshotView(afterScreenUpdates: true) if self.disableInternalAnimationIn {
let navigationBarContainer = UIView() nodeTransition = .immediate
navigationBarContainer.frame = targetFrame } else {
navigationBarContainer.clipsToBounds = true self.context.sharedContext.mediaManager.setOverlayVideoNode(nil)
transitionSurface.view.addSubview(navigationBarContainer) transitionSurface.addSubnode(self.videoNode)
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 = navigationBar?.view.snapshotView(afterScreenUpdates: true)
navigationBarCopy.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) let navigationBarContainerValue = UIView()
navigationBarContainer = navigationBarContainerValue
navigationBarContainerValue.frame = targetFrame
navigationBarContainerValue.clipsToBounds = true
transitionSurface.view.addSubview(navigationBarContainerValue)
} }
self.videoNode.updateRoundCorners(false, transition: .animated(duration: 0.25, curve: .spring)) if !self.disableInternalAnimationIn {
self.videoNode.showControls() navigationBarContainer?.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
}
self.videoNode.updateLayout(targetFrame.size, transition: .animated(duration: 0.25, curve: .spring)) if !self.disableInternalAnimationIn {
self.videoNode.frame = targetFrame if let navigationBar = navigationBar, let navigationBarCopy = navigationBarCopy {
self.videoNode.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in let navigationFrame = navigationBar.view.convert(navigationBar.bounds, to: transitionSurface.view)
guard let strongSelf = self else { let navigationSourceFrame = navigationFrame.offsetBy(dx: -sourceFrame.minX, dy: -sourceFrame.minY)
return let navigationTargetFrame = navigationFrame.offsetBy(dx: -targetFrame.minX, dy: -targetFrame.minY)
} navigationBarCopy.frame = navigationTargetFrame
navigationBarContainer.removeFromSuperview() navigationBarContainer?.addSubview(navigationBarCopy)
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) navigationBarCopy.layer.animateFrame(from: navigationSourceFrame, to: navigationTargetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
strongSelf.videoNode.updateLayout(targetFrame.size, transition: transition) navigationBarCopy.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
} }
strongSelf.dismissed()
} }
self.videoNode.updateRoundCorners(false, transition: nodeTransition)
if !self.disableInternalAnimationIn {
self.videoNode.showControls()
}
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()
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.customClose = { [weak self] in self.videoNode.customClose = { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -227,6 +234,39 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
transition.updateFrame(node: self.videoNode, frame: videoFrame) 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 { enum ChatEmbeddedTitlePeekContent: Equatable {
@ -387,6 +427,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var embeddedTitlePeekContent: ChatEmbeddedTitlePeekContent = .none private var embeddedTitlePeekContent: ChatEmbeddedTitlePeekContent = .none
private var embeddedTitleContentNode: ChatEmbeddedTitleContentNode? private var embeddedTitleContentNode: ChatEmbeddedTitleContentNode?
private var dismissedEmbeddedTitleContentNode: 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?) { init(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) {
self.context = context self.context = context
@ -892,6 +935,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
let statusBarHeight = layout.insets(options: [.statusBar]).top 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 { 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) let embeddedSize = CGSize(width: layout.size.width, height: min(400.0, embeddedTitleContentNode.calculateHeight(width: layout.size.width)) + statusBarHeight + embeddedTitleContentNode.interactiveExtension)
if embeddedTitleContentNode.supernode == nil { 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 { func acceptEmbeddedTitlePeekContent(content: NavigationControllerDropContent) -> Bool {
guard let (_, navigationHeight) = self.validLayout else { guard let (_, navigationHeight) = self.validLayout else {
@ -2658,7 +2718,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
return false return false
} }
if let item = content.item as? VideoNavigationControllerDropContentItem, let itemNode = item.itemNode as? OverlayUniversalVideoNode { 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 { guard let strongSelf = self else {
return return
} }
@ -2671,12 +2731,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.embeddedTitleContentNode = nil strongSelf.embeddedTitleContentNode = nil
strongSelf.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode strongSelf.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode
strongSelf.requestLayout(.animated(duration: 0.25, curve: .spring)) 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.embeddedTitleContentNode = embeddedTitleContentNode
self.embeddedTitlePeekContent = .none self.embeddedTitlePeekContent = .none
self.updateHasEmbeddedTitleContent?(true) self.updateHasEmbeddedTitleContent?()
DispatchQueue.main.async { DispatchQueue.main.async {
self.requestLayout(.animated(duration: 0.25, curve: .spring)) self.requestLayout(.animated(duration: 0.25, curve: .spring))
} }
@ -2685,4 +2753,46 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
return false 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 !peerStatusSettings.flags.isEmpty {
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
displayActionsPanel = true displayActionsPanel = true
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) { } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) {
displayActionsPanel = true displayActionsPanel = true
} else if peerStatusSettings.contains(.canShareContact) { } else if peerStatusSettings.contains(.canShareContact) {
displayActionsPanel = true displayActionsPanel = true

View File

@ -119,9 +119,82 @@ final class ChatPanelInterfaceInteraction {
let openScheduledMessages: () -> Void let openScheduledMessages: () -> Void
let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
let openPeersNearby: () -> Void let openPeersNearby: () -> Void
let unarchivePeer: () -> Void
let statuses: ChatPanelInterfaceInteractionStatuses? 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.setupReplyMessage = setupReplyMessage
self.setupEditMessage = setupEditMessage self.setupEditMessage = setupEditMessage
self.beginMessageSelection = beginMessageSelection self.beginMessageSelection = beginMessageSelection
@ -191,6 +264,7 @@ final class ChatPanelInterfaceInteraction {
self.openScheduledMessages = openScheduledMessages self.openScheduledMessages = openScheduledMessages
self.openPeersNearby = openPeersNearby self.openPeersNearby = openPeersNearby
self.displaySearchResultsTooltip = displaySearchResultsTooltip self.displaySearchResultsTooltip = displaySearchResultsTooltip
self.unarchivePeer = unarchivePeer
self.statuses = statuses self.statuses = statuses
} }
} }

View File

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

View File

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

View File

@ -1044,12 +1044,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
audioRecordingDotNode = currentAudioRecordingDotNode audioRecordingDotNode = currentAudioRecordingDotNode
} else { } else {
self.audioRecordingDotNode?.removeFromSupernode() self.audioRecordingDotNode?.removeFromSupernode()
audioRecordingDotNode = AnimationNode(animation: "voicebin") audioRecordingDotNode = AnimationNode(animation: "Bin")
self.audioRecordingDotNode = audioRecordingDotNode self.audioRecordingDotNode = audioRecordingDotNode
self.addSubnode(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)) audioRecordingDotNode.frame = CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: panelHeight - 44 + 1), size: CGSize(width: 40.0, height: 40))
if animateDotAppearing { if animateDotAppearing {

View File

@ -616,4 +616,8 @@ public final class MediaManagerImpl: NSObject, MediaManager {
self.overlayMediaManager.controller?.addNode(node, customTransition: true) 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: { }, openScheduledMessages: {
}, openPeersNearby: { }, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in }, displaySearchResultsTooltip: { _, _ in
}, statuses: nil) }, unarchivePeer: {}, statuses: nil)
self.selectionPanel.interfaceInteraction = interfaceInteraction self.selectionPanel.interfaceInteraction = interfaceInteraction
@ -4390,6 +4390,9 @@ public final class PeerInfoScreen: ViewController {
if other.contentNode != nil { if other.contentNode != nil {
return nil return nil
} }
if let allowsCustomTransition = other.allowsCustomTransition, !allowsCustomTransition() {
return nil
}
if let tag = other.userInfo as? PeerInfoNavigationSourceTag, tag.peerId == peerId { if let tag = other.userInfo as? PeerInfoNavigationSourceTag, tag.peerId == peerId {
return PeerInfoNavigationTransitionNode(screenNode: strongSelf.controllerNode, presentationData: strongSelf.presentationData, headerNode: strongSelf.controllerNode.headerNode) return PeerInfoNavigationTransitionNode(screenNode: strongSelf.controllerNode, presentationData: strongSelf.presentationData, headerNode: strongSelf.controllerNode.headerNode)
} }

View File

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

View File

@ -289,10 +289,20 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
applyLayout() applyLayout()
} }
self.imageNode.frame = CGRect(origin: CGPoint(), size: size) transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size))
self.playerNode.frame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0) 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 { 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 private let defaultExpand: () -> Void
public var customExpand: (() -> Void)? public var customExpand: (() -> Void)?
public var customClose: (() -> 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) { public init(postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, expand: @escaping () -> Void, close: @escaping () -> Void) {
self.content = content self.content = content
self.defaultExpand = expand self.defaultExpand = expand
var expandImpl: (() -> Void)? var expandImpl: (() -> Void)?
var controlsAreShowingUpdatedImpl: ((Bool) -> Void)?
var unminimizeImpl: (() -> Void)? var unminimizeImpl: (() -> Void)?
var togglePlayPauseImpl: (() -> Void)? var togglePlayPauseImpl: (() -> Void)?
var closeImpl: (() -> Void)? var closeImpl: (() -> Void)?
let decoration = OverlayVideoDecoration(unminimize: { let decoration = OverlayVideoDecoration(contentDimensions: content.dimensions, unminimize: {
unminimizeImpl?() unminimizeImpl?()
}, togglePlayPause: { }, togglePlayPause: {
togglePlayPauseImpl?() togglePlayPauseImpl?()
@ -51,6 +53,8 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
expandImpl?() expandImpl?()
}, close: { }, close: {
closeImpl?() closeImpl?()
}, controlsAreShowingUpdated: { value in
controlsAreShowingUpdatedImpl?(value)
}) })
self.videoNode = UniversalVideoNode(postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .overlay) self.videoNode = UniversalVideoNode(postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .overlay)
self.decoration = decoration self.decoration = decoration
@ -58,14 +62,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
super.init() super.init()
expandImpl = { [weak self] in expandImpl = { [weak self] in
guard let strongSelf = self else { self?.expand()
return
}
if let customExpand = strongSelf.customExpand {
customExpand()
} else {
strongSelf.defaultExpand()
}
} }
unminimizeImpl = { [weak self] in unminimizeImpl = { [weak self] in
@ -90,6 +87,10 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
strongSelf.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) strongSelf.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
} }
} }
controlsAreShowingUpdatedImpl = { [weak self] value in
self?.controlsAreShowingUpdated?(value)
}
self.clipsToBounds = true self.clipsToBounds = true
self.cornerRadius = 4.0 self.cornerRadius = 4.0
@ -104,7 +105,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
if previous != value { if previous != value {
if !value { if !value {
strongSelf.dismiss() strongSelf.dismiss()
close() closeImpl?()
} }
} }
} }
@ -155,4 +156,12 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
public func showControls() { public func showControls() {
self.decoration.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) 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 { final class OverlayVideoDecoration: UniversalVideoDecoration {
private let contentDimensions: CGSize
let backgroundNode: ASDisplayNode? let backgroundNode: ASDisplayNode?
let contentContainerNode: ASDisplayNode let contentContainerNode: ASDisplayNode
let foregroundNode: ASDisplayNode? let foregroundNode: ASDisplayNode?
private let unminimize: () -> Void private let unminimize: () -> Void
private let controlsAreShowingUpdated: (Bool) -> Void
private let shadowNode: ASImageNode private let shadowNode: ASImageNode
private let foregroundContainerNode: ASDisplayNode private let foregroundContainerNode: ASDisplayNode
@ -46,8 +49,11 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
private var validLayoutSize: CGSize? 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.unminimize = unminimize
self.controlsAreShowingUpdated = controlsAreShowingUpdated
self.shadowNode = ASImageNode() self.shadowNode = ASImageNode()
self.shadowNode.image = backgroundImage self.shadowNode.image = backgroundImage
@ -78,6 +84,14 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
self.statusDisposable.dispose() 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)?) { func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?) {
if self.contentNode !== contentNode { if self.contentNode !== contentNode {
let previous = self.contentNode let previous = self.contentNode
@ -93,7 +107,7 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
if contentNode.supernode !== self.contentContainerNode { if contentNode.supernode !== self.contentContainerNode {
self.contentContainerNode.addSubnode(contentNode) self.contentContainerNode.addSubnode(contentNode)
if let validLayoutSize = self.validLayoutSize { if let validLayoutSize = self.validLayoutSize {
contentNode.frame = CGRect(origin: CGPoint(), size: validLayoutSize) contentNode.frame = self.frameForContent(size: validLayoutSize)
contentNode.updateLayout(size: validLayoutSize, transition: .immediate) contentNode.updateLayout(size: validLayoutSize, transition: .immediate)
} }
} }
@ -107,6 +121,8 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayoutSize = size self.validLayoutSize = size
let contentFrame = self.frameForContent(size: size)
let shadowInsets = UIEdgeInsets(top: 2.0, left: 3.0, bottom: 4.0, right: 3.0) 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))) 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)) 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 { if let contentNode = self.contentNode {
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: contentNode, frame: contentFrame)
contentNode.updateLayout(size: size, transition: transition) contentNode.updateLayout(size: contentFrame.size, transition: transition)
} }
} }
@ -141,9 +157,11 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
if self.controlsNode.alpha.isZero { if self.controlsNode.alpha.isZero {
self.controlsNode.alpha = 1.0 self.controlsNode.alpha = 1.0
self.controlsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.controlsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.controlsAreShowingUpdated(true)
} else { } else {
self.controlsNode.alpha = 0.0 self.controlsNode.alpha = 0.0
self.controlsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) 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 { if self.controlsNode.alpha.isZero {
self.controlsNode.alpha = 1.0 self.controlsNode.alpha = 1.0
self.controlsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) 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_SecureStorageReset_Title: String { return self._s[219]! }
public var Wallet_Receive_CommentHeader: String { return self._s[220]! } public var Wallet_Receive_CommentHeader: String { return self._s[220]! }
public var Wallet_Info_ReceiveGrams: String { return self._s[221]! } 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 form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue) 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 form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator) let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue) return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)