mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 19:30:29 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
76baf58242
@ -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.";
|
||||
|
||||
@ -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>
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -561,6 +561,9 @@ public enum TabBarItemContextActionType {
|
||||
super.viewDidDisappear(animated)
|
||||
}
|
||||
|
||||
open func viewWillLeaveNavigation() {
|
||||
}
|
||||
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
self.activeInputView = nil
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
videoNode.allowsGroupOpacity = true
|
||||
videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak videoNode] _ in
|
||||
videoNode?.allowsGroupOpacity = 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
|
||||
overlaySupernode?.view.addSubview(view)
|
||||
overlayNode?.canAttachContent = false
|
||||
}), 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, {
|
||||
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, {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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 it is too large
Load Diff
1
submodules/TelegramUI/Resources/Animations/Bin.json
Normal file
1
submodules/TelegramUI/Resources/Animations/Bin.json
Normal file
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
Binary file not shown.
@ -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,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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,72 +162,63 @@ 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)
|
||||
|
||||
self.context.sharedContext.mediaManager.setOverlayVideoNode(nil)
|
||||
transitionSurface.addSubnode(self.videoNode)
|
||||
|
||||
let navigationBarCopy = navigationBar?.view.snapshotView(afterScreenUpdates: true)
|
||||
let navigationBarContainer = UIView()
|
||||
navigationBarContainer.frame = targetFrame
|
||||
navigationBarContainer.clipsToBounds = true
|
||||
transitionSurface.view.addSubview(navigationBarContainer)
|
||||
|
||||
navigationBarContainer.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
if let navigationBar = navigationBar, let navigationBarCopy = navigationBarCopy {
|
||||
let navigationFrame = navigationBar.view.convert(navigationBar.bounds, to: transitionSurface.view)
|
||||
let navigationSourceFrame = navigationFrame.offsetBy(dx: -sourceFrame.minX, dy: -sourceFrame.minY)
|
||||
let navigationTargetFrame = navigationFrame.offsetBy(dx: -targetFrame.minX, dy: -targetFrame.minY)
|
||||
navigationBarCopy.frame = navigationTargetFrame
|
||||
navigationBarContainer.addSubview(navigationBarCopy)
|
||||
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)
|
||||
|
||||
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)
|
||||
navigationBarCopy = navigationBar?.view.snapshotView(afterScreenUpdates: true)
|
||||
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))
|
||||
self.videoNode.showControls()
|
||||
if !self.disableInternalAnimationIn {
|
||||
navigationBarContainer?.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
self.videoNode.updateLayout(targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
||||
self.videoNode.frame = targetFrame
|
||||
self.videoNode.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
navigationBarContainer.removeFromSuperview()
|
||||
strongSelf.addSubnode(strongSelf.videoNode)
|
||||
if let (size, topInset, interactiveExtension) = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, topInset: topInset, interactiveExtension: interactiveExtension, transition: .immediate, transitionSurface: nil, navigationBar: nil)
|
||||
}
|
||||
})
|
||||
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
self.videoNode.customExpand = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
|
||||
strongSelf.videoNode.customExpand = nil
|
||||
strongSelf.videoNode.customClose = nil
|
||||
|
||||
let previousFrame = strongSelf.videoNode.frame
|
||||
strongSelf.context.sharedContext.mediaManager.setOverlayVideoNode(strongSelf.videoNode)
|
||||
strongSelf.videoNode.updateRoundCorners(true, transition: transition)
|
||||
|
||||
if let targetSuperview = strongSelf.videoNode.view.superview {
|
||||
let sourceFrame = strongSelf.view.convert(previousFrame, to: targetSuperview)
|
||||
let targetFrame = strongSelf.videoNode.frame
|
||||
strongSelf.videoNode.frame = sourceFrame
|
||||
strongSelf.videoNode.updateLayout(sourceFrame.size, transition: .immediate)
|
||||
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)
|
||||
|
||||
transition.updateFrame(node: strongSelf.videoNode, frame: targetFrame)
|
||||
strongSelf.videoNode.updateLayout(targetFrame.size, transition: transition)
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -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?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, openScheduledMessages: {
|
||||
}, openPeersNearby: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
}, statuses: nil)
|
||||
}, unarchivePeer: {}, statuses: nil)
|
||||
|
||||
self.navigationItem.titleView = self.titleView
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -550,7 +550,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
}, openScheduledMessages: {
|
||||
}, openPeersNearby: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
}, statuses: nil)
|
||||
}, unarchivePeer: {}, statuses: nil)
|
||||
|
||||
self.updateInterfaceState(animated: false, { return $0 })
|
||||
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
@ -90,6 +87,10 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
|
||||
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.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user