diff --git a/TelegramUI/AutodownloadMediaCategoryController.swift b/TelegramUI/AutodownloadMediaCategoryController.swift index 9f2d3b8c95..e242caf2aa 100644 --- a/TelegramUI/AutodownloadMediaCategoryController.swift +++ b/TelegramUI/AutodownloadMediaCategoryController.swift @@ -426,8 +426,8 @@ func autodownloadMediaCategoryController(context: AccountContext, connectionType } let controller = ItemListController(context: context, state: signal) - controller.willDisappear = { _ in - let _ = (combineLatest(initialValuePromise.get(), currentAutodownloadSettings()) + controller.didDisappear = { _ in + let _ = (combineLatest(initialValuePromise.get() |> take(1), currentAutodownloadSettings()) |> mapToSignal { initialValue, currentValue -> Signal in let initialConnection = initialValue.connectionSettings(for: connectionType.automaticDownloadNetworkType) let currentConnection = currentValue.connectionSettings(for: connectionType.automaticDownloadNetworkType) diff --git a/TelegramUI/CachedResourceRepresentations.swift b/TelegramUI/CachedResourceRepresentations.swift index 16d6821316..0bcdbf14f3 100644 --- a/TelegramUI/CachedResourceRepresentations.swift +++ b/TelegramUI/CachedResourceRepresentations.swift @@ -147,3 +147,27 @@ final class CachedPatternWallpaperRepresentation: CachedMediaResourceRepresentat } } } + +final class CachedAlbumArtworkRepresentation: CachedMediaResourceRepresentation { + let size: CGSize? + + var uniqueId: String { + if let size = self.size { + return "album-artwork-\(Int(size.width))x\(Int(size.height))" + } else { + return "album-artwork" + } + } + + init(size: CGSize) { + self.size = size + } + + func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + if let to = to as? CachedAlbumArtworkRepresentation { + return self.size == to.size + } else { + return false + } + } +} diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 685c282427..a466ae53f4 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -324,7 +324,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } } - return openChatMessage(context: context, message: message, standalone: false, reverseMessageGalleryOrder: false, stream: mode == .stream, fromPlayingVideo: mode == .automaticPlayback, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: { + return openChatMessage(context: context, message: message, standalone: false, reverseMessageGalleryOrder: false, mode: mode, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: { self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -699,9 +699,9 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } strongSelf.sendMessages([.message(text: command, attributes: attributes, mediaReference: nil, replyToMessageId: (postAsReply && messageId != nil) ? messageId! : nil, localGroupingKey: nil)]) } - }, openInstantPage: { [weak self] message in + }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.navigationController as? NavigationController, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { - openChatInstantPage(context: strongSelf.context, message: message, navigationController: navigationController) + openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) } }, openWallpaper: { [weak self] message in if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { @@ -3414,12 +3414,21 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) + var shouldOpenCurrentlyActiveVideo = false + if let previousLayout = self.validLayout, previousLayout.size.width < previousLayout.size.height && previousLayout.size.height == layout.size.width && self.traceVisibility() && isTopmostChatController(self) { + shouldOpenCurrentlyActiveVideo = true + } + self.validLayout = layout self.chatTitleView?.layout = layout self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop in self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop) }) + + if shouldOpenCurrentlyActiveVideo { + self.chatDisplayNode.openCurrentPlayingWithSoundMedia() + } } func updateChatPresentationInterfaceState(animated: Bool = true, interactive: Bool, saveInterfaceState: Bool = false, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) { diff --git a/TelegramUI/ChatControllerInteraction.swift b/TelegramUI/ChatControllerInteraction.swift index c850670718..84f45b6a7f 100644 --- a/TelegramUI/ChatControllerInteraction.swift +++ b/TelegramUI/ChatControllerInteraction.swift @@ -41,7 +41,7 @@ public enum ChatControllerInteractionOpenMessageMode { case `default` case stream case automaticPlayback - case playWithSound + case landscape } struct ChatInterfacePollActionState: Equatable { @@ -65,7 +65,7 @@ public final class ChatControllerInteraction { let shareCurrentLocation: () -> Void let shareAccountContact: () -> Void let sendBotCommand: (MessageId?, String) -> Void - let openInstantPage: (Message) -> Void + let openInstantPage: (Message, ChatMessageItemAssociatedData?) -> Void let openWallpaper: (Message) -> Void let openHashtag: (String?, String) -> Void let updateInputState: ((ChatTextInputState) -> ChatTextInputState) -> Void @@ -98,7 +98,7 @@ public final class ChatControllerInteraction { var pollActionState: ChatInterfacePollActionState var searchTextHighightState: String? - init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) { + init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention @@ -147,7 +147,7 @@ public final class ChatControllerInteraction { static var `default`: ChatControllerInteraction { return ChatControllerInteraction(openMessage: { _, _ in - return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, navigationController: { return nil }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index fabc082786..da3725872a 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -413,7 +413,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if display { var nodes: [(CGFloat, ChatMessageItemView, ASDisplayNode)] = [] strongSelf.historyNode.forEachVisibleItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView, let (_, isVideoMessage, _, badgeNode) = itemNode.playMediaWithSound(), let node = badgeNode { + if let itemNode = itemNode as? ChatMessageItemView, let (_, _, isVideoMessage, _, badgeNode) = itemNode.playMediaWithSound(), let node = badgeNode { if !isVideoMessage, case let .visible(fraction) = itemNode.visibility { nodes.insert((fraction, itemNode, node), at: 0) } @@ -566,6 +566,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } } + self.validLayout = (layout, navigationBarHeight) let cleanInsets = layout.intrinsicInsets @@ -1369,7 +1370,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let textInputPanelNode = self.textInputPanelNode, updateInputTextState { textInputPanelNode.updateInputTextState(chatPresentationInterfaceState.interfaceState.effectiveInputState, keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, animated: transition.isAnimated) } else { - textInputPanelNode?.updateKeepSendButtonEnabled(keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, animated: transition.isAnimated) + self.textInputPanelNode?.updateKeepSendButtonEnabled(keepSendButtonEnabled: keepSendButtonEnabled, extendedSearchLayout: extendedSearchLayout, animated: transition.isAnimated) } var restrictionText: String? @@ -1483,23 +1484,42 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } func playFirstMediaWithSound() { - var actions: [(CGFloat, () -> Void)] = [] + var actions: [(CGFloat, Bool, () -> Void)] = [] var hasUnconsumed = false self.historyNode.forEachVisibleItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView, let (action, isVideoMessage, isUnconsumed, _) = itemNode.playMediaWithSound() { + if let itemNode = itemNode as? ChatMessageItemView, let (action, _, _, isUnconsumed, _) = itemNode.playMediaWithSound() { if case let .visible(fraction) = itemNode.visibility { - actions.insert((fraction, action), at: 0) + hasUnconsumed = isUnconsumed + actions.insert((fraction, isUnconsumed, action), at: 0) } } } - for (fraction, action) in actions { - if fraction > 0.7 { + for (fraction, isUnconsumed, action) in actions { + if fraction > 0.7 && (!hasUnconsumed || isUnconsumed) { action() break } } } + func openCurrentPlayingWithSoundMedia() { + var result: (Message?, ListViewItemNode)? + self.historyNode.forEachVisibleItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView, let (_, soundEnabled, _, _, _) = itemNode.playMediaWithSound(), soundEnabled { + if case let .visible(fraction) = itemNode.visibility, fraction > 0.7 { + result = (itemNode.item?.message, itemNode) + } + } + } + if let (message, itemNode) = result { + if let message = message { + let _ = self.controllerInteraction.openMessage(message, .landscape) + } + + self.historyNode.ensureItemNodeVisibleAtTopInset(itemNode) + } + } + var isInputViewFocused: Bool { if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { return inputPanelNode.isFocused diff --git a/TelegramUI/ChatListItem.swift b/TelegramUI/ChatListItem.swift index e0daba0da0..0861680c85 100644 --- a/TelegramUI/ChatListItem.swift +++ b/TelegramUI/ChatListItem.swift @@ -1239,6 +1239,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } + override func touchesToOtherItemsPrevented() { + super.touchesToOtherItemsPrevented() + if let item = self.item { + item.interaction.setPeerIdWithRevealedOptions(nil, nil) + } + } + override func revealOptionsInteractivelyOpened() { if let item = self.item { item.interaction.setPeerIdWithRevealedOptions(item.index.messageIndex.id.peerId, nil) diff --git a/TelegramUI/ChatListNode.swift b/TelegramUI/ChatListNode.swift index 49f9d4c447..498ee571b6 100644 --- a/TelegramUI/ChatListNode.swift +++ b/TelegramUI/ChatListNode.swift @@ -418,7 +418,7 @@ final class ChatListNode: ListView { }, setPeerIdWithRevealedOptions: { [weak self] peerId, fromPeerId in if let strongSelf = self { strongSelf.updateState { state in - if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) { + if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) || (peerId == nil && fromPeerId == nil) { var state = state state.peerIdWithRevealedOptions = peerId return state diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index afb915bbfd..8703f8e618 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -1013,7 +1013,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } - func playMediaWithSound() -> (() -> Void, Bool, Bool, ASDisplayNode?)? { + func playMediaWithSound() -> (() -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.contentImageNode?.playMediaWithSound() } } diff --git a/TelegramUI/ChatMessageBubbleContentNode.swift b/TelegramUI/ChatMessageBubbleContentNode.swift index 405c90bccf..4d04240c03 100644 --- a/TelegramUI/ChatMessageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageBubbleContentNode.swift @@ -147,7 +147,7 @@ class ChatMessageBubbleContentNode: ASDisplayNode { func updateAutomaticMediaDownloadSettings(_ settings: MediaAutoDownloadSettings) { } - func playMediaWithSound() -> (() -> Void, Bool, Bool, ASDisplayNode?)? { + func playMediaWithSound() -> (() -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return nil } diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index 586b9a841c..517f0b3c91 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -1622,7 +1622,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { case let .botCommand(command): foundTapAction = true if let item = self.item { - item.controllerInteraction.sendBotCommand(item.message.id, command) + item.controllerInteraction.sendBotCommand(item.message.id, command) } break loop case let .hashtag(peerName, hashtag): @@ -1632,7 +1632,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { case .instantPage: foundTapAction = true if let item = self.item { - item.controllerInteraction.openInstantPage(item.message) + item.controllerInteraction.openInstantPage(item.message, item.associatedData) } break loop case .wallpaper: @@ -1781,7 +1781,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { override func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, () -> (UIView?, UIView?))? { for contentNode in self.contentNodes { if let result = contentNode.transitionNode(messageId: id, media: media) { - if self.contentNodes.count == 1 && self.nameNode == nil && self.adminBadgeNode == nil && self.forwardInfoNode == nil && self.replyInfoNode == nil { + if self.contentNodes.count == 1 && self.contentNodes.first is ChatMessageMediaBubbleContentNode && self.nameNode == nil && self.adminBadgeNode == nil && self.forwardInfoNode == nil && self.replyInfoNode == nil { return (result.0, { [weak self] in guard let strongSelf = self, let resultView = result.1().0 else { return (nil, nil) @@ -1856,7 +1856,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { } - override func playMediaWithSound() -> (() -> Void, Bool, Bool, ASDisplayNode?)? { + override func playMediaWithSound() -> (() -> Void, Bool, Bool, Bool, ASDisplayNode?)? { for contentNode in self.contentNodes { if let playMediaWithSound = contentNode.playMediaWithSound() { return playMediaWithSound diff --git a/TelegramUI/ChatMessageInstantVideoItemNode.swift b/TelegramUI/ChatMessageInstantVideoItemNode.swift index c8347c69f5..575942e77a 100644 --- a/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -695,7 +695,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func playMediaWithSound() -> (() -> Void, Bool, Bool, ASDisplayNode?)? { + override func playMediaWithSound() -> (() -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.interactiveVideoNode.playMediaWithSound() } } diff --git a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift index 15bbb8cd83..34a3a5dbf2 100644 --- a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift +++ b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift @@ -370,7 +370,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } } - }), content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, telegramFile.fileId), fileReference: .message(message: MessageReference(item.message), media: telegramFile), streamVideo: false, enableSound: false, fetchAutomatically: false), priority: .embedded, autoplay: true) + }), content: NativeVideoContent(id: .message(item.message.id, item.message.stableId, telegramFile.fileId), fileReference: .message(message: MessageReference(item.message), media: telegramFile), streamVideo: true, enableSound: false, fetchAutomatically: false), priority: .embedded, autoplay: true) let previousVideoNode = strongSelf.videoNode strongSelf.videoNode = videoNode strongSelf.insertSubnode(videoNode, belowSubnode: previousVideoNode ?? strongSelf.dateAndStatusNode) @@ -453,7 +453,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { case .Local: displayMute = true default: - displayMute = false + displayMute = self.automaticDownload ?? false } case .playbackStatus: displayMute = false @@ -483,6 +483,10 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } + if self.automaticDownload ?? false { + progressRequired = false + } + if progressRequired { if self.statusNode == nil { let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.bubble.mediaOverlayControlBackgroundColor) @@ -688,8 +692,18 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } - func playMediaWithSound() -> (() -> Void, Bool, Bool, ASDisplayNode?)? { + func playMediaWithSound() -> (action: () -> Void, soundEnabled: Bool, isVideoMessage: Bool, isUnread: Bool, badgeNode: ASDisplayNode?)? { if let item = self.item { + var notConsumed = false + for attribute in item.message.attributes { + if let attribute = attribute as? ConsumableContentMessageAttribute { + if !attribute.consumed { + notConsumed = true + } + break + } + } + return ({ if !self.infoBackgroundNode.alpha.isZero { let _ = (item.context.sharedContext.mediaManager.globalMediaPlayerState @@ -711,7 +725,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } }) } - }, true, false, nil) + }, false, true, !notConsumed, nil) } else { return nil } diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index d6525888a1..9d38d46112 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -594,7 +594,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { strongSelf.videoNodeDecoration = decoration let mediaManager = context.sharedContext.mediaManager - let streamVideo = !updatedVideoFile.isAnimated && isMediaStreamable(message: message, media: updatedVideoFile) + let streamVideo = isMediaStreamable(message: message, media: updatedVideoFile) let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: decoration, content: NativeVideoContent(id: .message(message.id, message.stableId, updatedVideoFile.fileId), fileReference: .message(message: MessageReference(message), media: updatedVideoFile), streamVideo: streamVideo, enableSound: false, fetchAutomatically: false, onlyFullSizeThumbnail: (onlyFullSizeVideoThumbnail ?? false), continuePlayingWithoutSoundOnLostAudioSession: isInlinePlayableVideo, placeholderColor: emptyColor), priority: .embedded) videoNode.isUserInteractionEnabled = false videoNode.ownsContentNodeUpdated = { [weak self] owns in @@ -897,6 +897,10 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { active = true } + if let file = self.media as? TelegramMediaFile, file.isAnimated { + muted = false + } + if message.flags.contains(.Unsent) { automaticPlayback = false } @@ -918,11 +922,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) } - if let file = self.media as? TelegramMediaFile, (!file.isAnimated || message.flags.contains(.Unsent)) { + if let file = self.media as? TelegramMediaFile { if wideLayout { if let size = file.size { if let duration = file.duration, !message.flags.contains(.Unsent) { - let durationString = stringForDuration(playerDuration > 0 ? playerDuration : duration, position: playerPosition) + let durationString = file.isAnimated ? "GIF" : stringForDuration(playerDuration > 0 ? playerDuration : duration, position: playerPosition) let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true)) / \(dataSizeString(size, forceDecimal: true))" if isMediaStreamable(message: message, media: file) { badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: active ? sizeString : nil, muted: muted, active: active) @@ -1016,14 +1020,14 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { state = .play(bubbleTheme.mediaOverlayControlForegroundColor) } } - if let file = media as? TelegramMediaFile, let duration = file.duration, !file.isAnimated { - let durationString = stringForDuration(playerDuration > 0 ? playerDuration : duration, position: playerPosition) + if let file = media as? TelegramMediaFile, let duration = file.duration { + let durationString = file.isAnimated ? "GIF" : stringForDuration(playerDuration > 0 ? playerDuration : duration, position: playerPosition) badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: nil, muted: muted, active: false) } case .Remote: state = .download(bubbleTheme.mediaOverlayControlForegroundColor) - if let file = self.media as? TelegramMediaFile, !file.isAnimated { - let durationString = stringForDuration(playerDuration > 0 ? playerDuration : (file.duration ?? 0), position: playerPosition) + if let file = self.media as? TelegramMediaFile { + let durationString = file.isAnimated ? "GIF" : stringForDuration(playerDuration > 0 ? playerDuration : (file.duration ?? 0), position: playerPosition) if wideLayout { if isMediaStreamable(message: message, media: file) { state = automaticPlayback ? .none : .play(bubbleTheme.mediaOverlayControlForegroundColor) @@ -1201,12 +1205,17 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { }) } - func playMediaWithSound() -> (() -> Void, Bool, Bool, ASDisplayNode?)? { + func playMediaWithSound() -> (action: () -> Void, soundEnabled: Bool, isVideoMessage: Bool, isUnread: Bool, badgeNode: ASDisplayNode?)? { var isAnimated = false if let file = self.media as? TelegramMediaFile, file.isAnimated { isAnimated = true } + if let videoNode = self.videoNode, let context = self.context, (self.automaticPlayback ?? false) && !isAnimated { + var isHorizontal = false + if let file = self.media as? TelegramMediaFile, let dimensions = file.dimensions { + isHorizontal = dimensions.width >= dimensions.height + } return ({ let _ = (context.sharedContext.mediaManager.globalMediaPlayerState |> take(1) @@ -1226,7 +1235,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { videoNode.playOnceWithSound(playAndRecord: false, seekToStart: .none) } }) - }, false, false, self.badgeNode) + }, (self.playerStatus?.soundEnabled ?? false) && isHorizontal, false, false, self.badgeNode) } else { return nil } diff --git a/TelegramUI/ChatMessageItemView.swift b/TelegramUI/ChatMessageItemView.swift index 67d67e777e..274d7c7836 100644 --- a/TelegramUI/ChatMessageItemView.swift +++ b/TelegramUI/ChatMessageItemView.swift @@ -235,7 +235,7 @@ public class ChatMessageItemView: ListViewItemNode { func updateAutomaticMediaDownloadSettings() { } - func playMediaWithSound() -> (() -> Void, Bool, Bool, ASDisplayNode?)? { + func playMediaWithSound() -> (() -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return nil } diff --git a/TelegramUI/ChatMessageMediaBubbleContentNode.swift b/TelegramUI/ChatMessageMediaBubbleContentNode.swift index 99b90a57ab..242bdb341f 100644 --- a/TelegramUI/ChatMessageMediaBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMediaBubbleContentNode.swift @@ -316,7 +316,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { return mediaHidden } - override func playMediaWithSound() -> (() -> Void, Bool, Bool, ASDisplayNode?)? { + override func playMediaWithSound() -> (() -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.interactiveImageNode.playMediaWithSound() } diff --git a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index b7f27bc209..ecb15e7797 100644 --- a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -133,7 +133,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { break } if !isGallery { - item.controllerInteraction.openInstantPage(item.message) + item.controllerInteraction.openInstantPage(item.message, item.associatedData) return } } else if content.type == "telegram_background" { @@ -369,7 +369,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func playMediaWithSound() -> (() -> Void, Bool, Bool, ASDisplayNode?)? { + override func playMediaWithSound() -> (() -> Void, Bool, Bool, Bool, ASDisplayNode?)? { return self.contentNode.playMediaWithSound() } diff --git a/TelegramUI/ChatRecentActionsControllerNode.swift b/TelegramUI/ChatRecentActionsControllerNode.swift index 9be590c363..462e32dc27 100644 --- a/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/TelegramUI/ChatRecentActionsControllerNode.swift @@ -180,9 +180,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame) }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in self?.openUrl(url) - }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message in + }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { - openChatInstantPage(context: strongSelf.context, message: message, navigationController: navigationController) + openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) } }, openWallpaper: { [weak self] message in if let strongSelf = self{ @@ -756,7 +756,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { case let .stickerPack(name): strongSelf.presentController(StickerPackPreviewController(context: strongSelf.context, stickerPack: .name(name), parentNavigationController: strongSelf.getNavigationController()), nil) case let .instantView(webpage, anchor): - strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, anchor: anchor)) + strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourcePeerType: .channel, anchor: anchor)) case let .join(link): strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peerId in if let strongSelf = self { diff --git a/TelegramUI/FetchCachedRepresentations.swift b/TelegramUI/FetchCachedRepresentations.swift index 22a6f6fec8..8694ff2a0c 100644 --- a/TelegramUI/FetchCachedRepresentations.swift +++ b/TelegramUI/FetchCachedRepresentations.swift @@ -8,27 +8,6 @@ import Display import UIKit import AVFoundation -private func videoFirstFrameData(account: Account, resource: MediaResource, chunkSize: Int) -> Signal { - if let size = resource.size { - return account.postbox.mediaBox.resourceData(resource, size: size, in: 0 ..< min(size, chunkSize)) - |> mapToSignal { _ -> Signal in - return account.postbox.mediaBox.resourceData(resource, option: .incremental(waitUntilFetchStatus: false), attemptSynchronously: false) - |> mapToSignal { data -> Signal in - return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: data) - |> `catch` { _ -> Signal in - if chunkSize > size { - return .complete() - } else { - return videoFirstFrameData(account: account, resource: resource, chunkSize: chunkSize + chunkSize) - } - } - } - } - } else { - return .complete() - } -} - public func fetchCachedResourceRepresentation(account: Account, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal { if let representation = representation as? CachedStickerAJpegRepresentation { return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) @@ -96,6 +75,27 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR return .never() } +private func videoFirstFrameData(account: Account, resource: MediaResource, chunkSize: Int) -> Signal { + if let size = resource.size { + return account.postbox.mediaBox.resourceData(resource, size: size, in: 0 ..< min(size, chunkSize)) + |> mapToSignal { _ -> Signal in + return account.postbox.mediaBox.resourceData(resource, option: .incremental(waitUntilFetchStatus: false), attemptSynchronously: false) + |> mapToSignal { data -> Signal in + return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: data) + |> `catch` { _ -> Signal in + if chunkSize > size { + return .complete() + } else { + return videoFirstFrameData(account: account, resource: resource, chunkSize: chunkSize + chunkSize) + } + } + } + } + } else { + return .complete() + } +} + private func fetchCachedStickerAJpegRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedStickerAJpegRepresentation) -> Signal { return Signal({ subscriber in if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { @@ -275,8 +275,6 @@ private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource } let _ = try? FileManager.default.removeItem(atPath: tempFilePath) - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) - subscriber.putCompletion() } catch (let _) { let _ = try? FileManager.default.removeItem(atPath: tempFilePath) subscriber.putError(.generic) diff --git a/TelegramUI/GalleryController.swift b/TelegramUI/GalleryController.swift index 6abbd33ad0..72867334e4 100644 --- a/TelegramUI/GalleryController.swift +++ b/TelegramUI/GalleryController.swift @@ -131,7 +131,7 @@ private func galleryMessageCaptionText(_ message: Message) -> String { return message.text } -func galleryItemForEntry(context: AccountContext, presentationData: PresentationData, entry: MessageHistoryEntry, isCentral: Bool = false, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, tempFilePath: String? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }) -> GalleryItem? { +func galleryItemForEntry(context: AccountContext, presentationData: PresentationData, entry: MessageHistoryEntry, isCentral: Bool = false, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, tempFilePath: String? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }) -> GalleryItem? { switch entry { case let .MessageEntry(message, _, location, _, _): if let (media, mediaImage) = mediaForMessage(message: message) { @@ -158,7 +158,7 @@ func galleryItemForEntry(context: AccountContext, presentationData: Presentation } } let caption = galleryCaptionStringWithAppliedEntities(galleryMessageCaptionText(message), entities: entities) - return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions) + return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, landscape: landscape, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions) } else { if file.mimeType.hasPrefix("image/") && file.mimeType != "image/gif" { var pixelsCount: Int = 0 @@ -179,12 +179,12 @@ func galleryItemForEntry(context: AccountContext, presentationData: Presentation } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(webpageContent) = webpage.content { switch websiteType(of: webpageContent) { case .instagram where webpageContent.file != nil && webpageContent.image != nil && webpageContent.file!.isVideo: - return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: .message(message.id, message.stableId, webpageContent.file?.id ?? webpage.webpageId), fileReference: .message(message: MessageReference(message), media: webpageContent.file!), imageReference: webpageContent.image.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: true, enableSound: true), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, performAction: performAction, openActionOptions: openActionOptions) + return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: .message(message.id, message.stableId, webpageContent.file?.id ?? webpage.webpageId), fileReference: .message(message: MessageReference(message), media: webpageContent.file!), imageReference: webpageContent.image.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: true, enableSound: true), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: performAction, openActionOptions: openActionOptions) default: if let embedUrl = webpageContent.embedUrl, let image = webpageContent.image, URL(string: embedUrl)?.pathExtension == "mp4" { - return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: SystemVideoContent(url: embedUrl, imageReference: .webPage(webPage: WebpageReference(webpage), media: image), dimensions: webpageContent.embedSize ?? CGSize(width: 640.0, height: 640.0), duration: Int32(webpageContent.duration ?? 0)), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, performAction: performAction, openActionOptions: openActionOptions) + return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: SystemVideoContent(url: embedUrl, imageReference: .webPage(webPage: WebpageReference(webpage), media: image), dimensions: webpageContent.embedSize ?? CGSize(width: 640.0, height: 640.0), duration: Int32(webpageContent.duration ?? 0)), originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: performAction, openActionOptions: openActionOptions) } else if let content = WebEmbedVideoContent(webPage: webpage, webpageContent: webpageContent) { - return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, performAction: performAction, openActionOptions: openActionOptions) + return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.author?.displayTitle, timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: performAction, openActionOptions: openActionOptions) } } } @@ -286,6 +286,7 @@ class GalleryController: ViewController { var temporaryDoNotWaitForReady = false private let fromPlayingVideo: Bool + private let landscape: Bool private let accountInUseDisposable = MetaDisposable() private let disposable = MetaDisposable() @@ -311,7 +312,7 @@ class GalleryController: ViewController { private var performAction: (GalleryControllerInteractionTapAction) -> Void private var openActionOptions: (GalleryControllerInteractionTapAction) -> Void - init(context: AccountContext, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, fromPlayingVideo: Bool = false, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, ValuePromise?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) { + init(context: AccountContext, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, ValuePromise?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) { self.context = context self.source = source self.replaceRootController = replaceRootController @@ -319,6 +320,7 @@ class GalleryController: ViewController { self.actionInteraction = actionInteraction self.streamVideos = streamSingleVideo self.fromPlayingVideo = fromPlayingVideo + self.landscape = landscape self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -425,7 +427,7 @@ class GalleryController: ViewController { if case let .MessageEntry(message, _, _, _, _) = entry, message.stableId == strongSelf.centralEntryStableId { isCentral = true } - if let item = galleryItemForEntry(context: context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: streamSingleVideo, fromPlayingVideo: isCentral && fromPlayingVideo, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions) { + if let item = galleryItemForEntry(context: context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: streamSingleVideo, fromPlayingVideo: isCentral && fromPlayingVideo, landscape: isCentral && landscape, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions) { if isCentral { centralItemIndex = items.count } @@ -804,7 +806,7 @@ class GalleryController: ViewController { var items: [GalleryItem] = [] var centralItemIndex: Int? for entry in self.entries { - if let item = galleryItemForEntry(context: context, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, fromPlayingVideo: self.fromPlayingVideo, performAction: self.performAction, openActionOptions: self.openActionOptions) { + if let item = galleryItemForEntry(context: context, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, fromPlayingVideo: self.fromPlayingVideo, landscape: self.landscape, performAction: self.performAction, openActionOptions: self.openActionOptions) { if case let .MessageEntry(message, _, _, _, _) = entry, message.stableId == self.centralEntryStableId { centralItemIndex = items.count } diff --git a/TelegramUI/GalleryControllerNode.swift b/TelegramUI/GalleryControllerNode.swift index 5b3f23cde3..b261231ee0 100644 --- a/TelegramUI/GalleryControllerNode.swift +++ b/TelegramUI/GalleryControllerNode.swift @@ -59,6 +59,35 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog } } + self.pager.dismiss = { [weak self] in + if let strongSelf = self { + var interfaceAnimationCompleted = false + var contentAnimationCompleted = true + + strongSelf.scrollView.isScrollEnabled = false + let completion = { [weak self] in + if interfaceAnimationCompleted && contentAnimationCompleted { + if let dismiss = self?.dismiss { + self?.scrollView.isScrollEnabled = true + dismiss() + } + } + } + + if let centralItemNode = strongSelf.pager.centralItemNode(), let (transitionNodeForCentralItem, addToTransitionSurface) = strongSelf.transitionDataForCentralItem?(), let node = transitionNodeForCentralItem { + contentAnimationCompleted = false + centralItemNode.animateOut(to: node, addToTransitionSurface: addToTransitionSurface, completion: { + contentAnimationCompleted = true + completion() + }) + } + strongSelf.animateOut(animateContent: false, completion: { + interfaceAnimationCompleted = true + completion() + }) + } + } + self.pager.beginCustomDismiss = { [weak self] in if let strongSelf = self { strongSelf.beginCustomDismiss() diff --git a/TelegramUI/GalleryItemNode.swift b/TelegramUI/GalleryItemNode.swift index e7faf00515..d872f8f051 100644 --- a/TelegramUI/GalleryItemNode.swift +++ b/TelegramUI/GalleryItemNode.swift @@ -20,6 +20,7 @@ open class GalleryItemNode: ASDisplayNode { } var toggleControlsVisibility: () -> Void = { } + var dismiss: () -> Void = { } var beginCustomDismiss: () -> Void = { } var completeCustomDismiss: () -> Void = { } var baseNavigationController: () -> NavigationController? = { return nil } diff --git a/TelegramUI/GalleryPagerNode.swift b/TelegramUI/GalleryPagerNode.swift index 963a071a1f..824821e63a 100644 --- a/TelegramUI/GalleryPagerNode.swift +++ b/TelegramUI/GalleryPagerNode.swift @@ -58,6 +58,7 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { private var invalidatedItems = false var centralItemIndexOffsetUpdated: (([GalleryItem]?, Int, CGFloat)?) -> Void = { _ in } var toggleControlsVisibility: () -> Void = { } + var dismiss: () -> Void = { } var beginCustomDismiss: () -> Void = { } var completeCustomDismiss: () -> Void = { } var baseNavigationController: () -> NavigationController? = { return nil } @@ -232,6 +233,7 @@ final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate { private func makeNodeForItem(at index: Int) -> GalleryItemNode { let node = self.items[index].node() node.toggleControlsVisibility = self.toggleControlsVisibility + node.dismiss = self.dismiss node.beginCustomDismiss = self.beginCustomDismiss node.completeCustomDismiss = self.completeCustomDismiss node.baseNavigationController = self.baseNavigationController diff --git a/TelegramUI/GroupInfoController.swift b/TelegramUI/GroupInfoController.swift index c79a177e6b..8c2681e721 100644 --- a/TelegramUI/GroupInfoController.swift +++ b/TelegramUI/GroupInfoController.swift @@ -2233,7 +2233,7 @@ func handlePeerInfoAboutTextAction(context: AccountContext, peerId: PeerId, navi case let .stickerPack(name): controller.present(StickerPackPreviewController(context: context, stickerPack: .name(name), parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) case let .instantView(webpage, anchor): - (controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, anchor: anchor)) + (controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .group, anchor: anchor)) case let .join(link): controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId in openResolvedPeerImpl(peerId, .chat(textInputState: nil, messageId: nil)) diff --git a/TelegramUI/InstantPageController.swift b/TelegramUI/InstantPageController.swift index a410a4d90e..0988fd3a6d 100644 --- a/TelegramUI/InstantPageController.swift +++ b/TelegramUI/InstantPageController.swift @@ -7,6 +7,7 @@ import Display final class InstantPageController: ViewController { private let context: AccountContext private var webPage: TelegramMediaWebpage + private let sourcePeerType: MediaAutoDownloadPeerType private let anchor: String? private var presentationData: PresentationData @@ -26,12 +27,13 @@ final class InstantPageController: ViewController { private var settingsDisposable: Disposable? private var themeSettings: PresentationThemeSettings? - init(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String? = nil) { + init(context: AccountContext, webPage: TelegramMediaWebpage, sourcePeerType: MediaAutoDownloadPeerType, anchor: String? = nil) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.webPage = webPage self.anchor = anchor + self.sourcePeerType = sourcePeerType super.init(navigationBarPresentationData: nil) @@ -85,7 +87,7 @@ final class InstantPageController: ViewController { } override public func loadDisplayNode() { - self.displayNode = InstantPageControllerNode(context: self.context, settings: self.settings, themeSettings: self.themeSettings, presentationTheme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, statusBar: self.statusBar, getNavigationController: { [weak self] in + self.displayNode = InstantPageControllerNode(context: self.context, settings: self.settings, themeSettings: self.themeSettings, presentationTheme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, statusBar: self.statusBar, sourcePeerType: self.sourcePeerType, getNavigationController: { [weak self] in return self?.navigationController as? NavigationController }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) diff --git a/TelegramUI/InstantPageControllerNode.swift b/TelegramUI/InstantPageControllerNode.swift index cf40e779d6..af1bef6688 100644 --- a/TelegramUI/InstantPageControllerNode.swift +++ b/TelegramUI/InstantPageControllerNode.swift @@ -14,6 +14,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { private var strings: PresentationStrings private var dateTimeFormat: PresentationDateTimeFormat private var theme: InstantPageTheme? + private let sourcePeerType: MediaAutoDownloadPeerType private var manualThemeOverride: InstantPageThemeType? private let getNavigationController: () -> NavigationController? private let present: (ViewController, Any?) -> Void @@ -74,7 +75,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return InstantPageStoredState(contentOffset: Double(self.scrollNode.view.contentOffset.y), details: details) } - init(context: AccountContext, settings: InstantPagePresentationSettings?, themeSettings: PresentationThemeSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, statusBar: StatusBar, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) { + init(context: AccountContext, settings: InstantPagePresentationSettings?, themeSettings: PresentationThemeSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, statusBar: StatusBar, sourcePeerType: MediaAutoDownloadPeerType, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) { self.context = context self.presentationTheme = presentationTheme self.dateTimeFormat = dateTimeFormat @@ -85,7 +86,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { self.theme = settings.flatMap { settings in return instantPageThemeForType(instantPageThemeTypeForSettingsAndTime(themeSettings: themeSettings, settings: settings, time: themeReferenceDate), settings: settings) } - + self.sourcePeerType = sourcePeerType self.statusBar = statusBar self.getNavigationController = getNavigationController self.present = present @@ -1122,7 +1123,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { case let .result(webpage): if let webpage = webpage, case .Loaded = webpage.content { strongSelf.loadProgress.set(1.0) - strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, anchor: anchor)) + strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourcePeerType: strongSelf.sourcePeerType, anchor: anchor)) } break case let .progress(progress): diff --git a/TelegramUI/InstantPageImageNode.swift b/TelegramUI/InstantPageImageNode.swift index c0f0a437ec..7543ad04c3 100644 --- a/TelegramUI/InstantPageImageNode.swift +++ b/TelegramUI/InstantPageImageNode.swift @@ -5,6 +5,11 @@ import Postbox import TelegramCore import SwiftSignalKit +private struct FetchControls { + let fetch: (Bool) -> Void + let cancel: () -> Void +} + final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private let context: AccountContext private let webPage: TelegramMediaWebpage @@ -17,6 +22,8 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private let openMedia: (InstantPageMedia) -> Void private let longPressMedia: (InstantPageMedia) -> Void + private var fetchControls: FetchControls? + private let imageNode: TransformImageNode private let statusNode: RadialStatusNode private let linkIconNode: ASImageNode @@ -54,7 +61,18 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { if let image = media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, photoReference: imageReference)) - self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, photoReference: imageReference, storeToDownloadsPeerType: nil).start()) + + if false { + self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, photoReference: imageReference, storeToDownloadsPeerType: nil).start()) + } + + self.fetchControls = FetchControls(fetch: { [weak self] manual in + if let strongSelf = self { + strongSelf.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, photoReference: imageReference, storeToDownloadsPeerType: nil).start()) + } + }, cancel: { + chatMessagePhotoCancelInteractiveFetch(account: context.account, photoReference: imageReference) + }) if interactive { self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in @@ -143,9 +161,11 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { var state: RadialStatusNodeState = .none if let fetchStatus = self.fetchStatus { switch fetchStatus { - case let .Fetching(isActive, progress): + case let .Fetching(_, progress): let adjustedProgress = max(progress, 0.027) - state = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: false) + state = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) + case .Remote: + state = .download(.white) default: break } @@ -236,17 +256,28 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: - if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { - switch gesture { - case .tap: - if self.media.media is TelegramMediaImage && self.media.index == -1 { - return + if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation, let fetchStatus = self.fetchStatus { + switch fetchStatus { + case .Local: + switch gesture { + case .tap: + if self.media.media is TelegramMediaImage && self.media.index == -1 { + return + } + self.openMedia(self.media) + case .longTap: + self.longPressMedia(self.media) + default: + break + } + case .Remote: + if case .tap = gesture { + self.fetchControls?.fetch(true) + } + case .Fetching: + if case .tap = gesture { + self.fetchControls?.cancel() } - self.openMedia(self.media) - case .longTap: - self.longPressMedia(self.media) - default: - break } } default: diff --git a/TelegramUI/InstantPagePlayableVideoNode.swift b/TelegramUI/InstantPagePlayableVideoNode.swift index b96cd237c7..6f998b6d63 100644 --- a/TelegramUI/InstantPagePlayableVideoNode.swift +++ b/TelegramUI/InstantPagePlayableVideoNode.swift @@ -5,21 +5,30 @@ import Postbox import TelegramCore import SwiftSignalKit +private struct FetchControls { + let fetch: (Bool) -> Void + let cancel: () -> Void +} + final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode { private let context: AccountContext let media: InstantPageMedia private let interactive: Bool private let openMedia: (InstantPageMedia) -> Void + private var fetchControls: FetchControls? private let videoNode: UniversalVideoNode + private let statusNode: RadialStatusNode private var currentSize: CGSize? + private var fetchStatus: MediaResourceStatus? private var fetchedDisposable = MetaDisposable() + private var statusDisposable = MetaDisposable() private var localIsVisible = false - init(context: AccountContext, webPage: TelegramMediaWebpage, theme: InstantPageTheme, media: InstantPageMedia, interactive: Bool, openMedia: @escaping (InstantPageMedia) -> Void) { + init(context: AccountContext, webPage: TelegramMediaWebpage, theme: InstantPageTheme, media: InstantPageMedia, interactive: Bool, openMedia: @escaping (InstantPageMedia) -> Void) { self.context = context self.media = media self.interactive = interactive @@ -31,15 +40,31 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode { imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) } - self.videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), fileReference: .webPage(webPage: WebpageReference(webPage), media: media.media as! TelegramMediaFile), imageReference: imageReference, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor), priority: .embedded, autoplay: true) + var streamVideo = false + if let file = media.media as? TelegramMediaFile { + streamVideo = isMediaStreamable(media: file) + } + + self.videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), fileReference: .webPage(webPage: WebpageReference(webPage), media: media.media as! TelegramMediaFile), imageReference: imageReference, streamVideo: streamVideo, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor), priority: .embedded, autoplay: true) self.videoNode.isUserInteractionEnabled = false + self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) + super.init() self.addSubnode(self.videoNode) if let file = media.media as? TelegramMediaFile { self.fetchedDisposable.set(fetchedMediaResource(postbox: context.account.postbox, reference: AnyMediaReference.webPage(webPage: WebpageReference(webPage), media: file).resourceReference(file.resource)).start()) + + self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(file.resource) |> deliverOnMainQueue).start(next: { [weak self] status in + displayLinkDispatcher.dispatch { + if let strongSelf = self { + strongSelf.fetchStatus = status + strongSelf.updateFetchStatus() + } + } + })) } } @@ -69,6 +94,26 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode { func update(strings: PresentationStrings, theme: InstantPageTheme) { } + private func updateFetchStatus() { + var state: RadialStatusNodeState = .none + if let fetchStatus = self.fetchStatus { + switch fetchStatus { + case let .Fetching(_, progress): + let adjustedProgress = max(progress, 0.027) + state = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) + case .Remote: + state = .download(.white) + default: + break + } + } + self.statusNode.transitionToState(state, completion: { [weak statusNode] in + if state == .none { + statusNode?.removeFromSupernode() + } + }) + } + override func layout() { super.layout() @@ -79,6 +124,9 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode { self.videoNode.frame = CGRect(origin: CGPoint(), size: size) self.videoNode.updateLayout(size: size, transition: .immediate) + + let radialStatusSize: CGFloat = 50.0 + self.statusNode.frame = CGRect(x: floorToScreenPixels((size.width - radialStatusSize) / 2.0), y: floorToScreenPixels((size.height - radialStatusSize) / 2.0), width: radialStatusSize, height: radialStatusSize) } } @@ -98,8 +146,15 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode { } @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.openMedia(self.media) + if case .ended = recognizer.state, let fetchStatus = self.fetchStatus { + switch fetchStatus { + case .Local: + self.openMedia(self.media) + case .Remote: + self.fetchControls?.fetch(true) + case .Fetching: + self.fetchControls?.cancel() + } } } } diff --git a/TelegramUI/IsMediaStreamable.swift b/TelegramUI/IsMediaStreamable.swift index f12565815d..0e447552b9 100644 --- a/TelegramUI/IsMediaStreamable.swift +++ b/TelegramUI/IsMediaStreamable.swift @@ -15,9 +15,6 @@ func isMediaStreamable(message: Message, media: TelegramMediaFile) -> Bool { if size < 1 * 1024 * 1024 { return false } -// if media.isAnimated { -// return false -// } for attribute in media.attributes { if case let .Video(video) = attribute { if video.flags.contains(.supportsStreaming) { @@ -41,9 +38,6 @@ func isMediaStreamable(media: TelegramMediaFile) -> Bool { if size < 1 * 1024 * 1024 { return false } -// if media.isAnimated { -// return false -// } for attribute in media.attributes { if case let .Video(video) = attribute { if video.flags.contains(.supportsStreaming) { diff --git a/TelegramUI/ItemListController.swift b/TelegramUI/ItemListController.swift index 27ff26f925..94c494832c 100644 --- a/TelegramUI/ItemListController.swift +++ b/TelegramUI/ItemListController.swift @@ -203,6 +203,7 @@ class ItemListController: ViewController { var commitPreview: ((UIViewController) -> Void)? var willDisappear: ((Bool) -> Void)? + var didDisappear: ((Bool) -> Void)? convenience init(context: AccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, Entry.ItemGenerationArguments)), NoError>, tabBarItem: Signal? = nil) { self.init(sharedContext: context.sharedContext, state: state, tabBarItem: tabBarItem) @@ -460,6 +461,12 @@ class ItemListController: ViewController { self.willDisappear?(animated) } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + self.didDisappear?(animated) + } + override func dismiss(completion: (() -> Void)? = nil) { (self.displayNode as! ItemListControllerNode).animateOut(completion: completion) } diff --git a/TelegramUI/ListMessageSnippetItemNode.swift b/TelegramUI/ListMessageSnippetItemNode.swift index 36cba4a342..2590f70a98 100644 --- a/TelegramUI/ListMessageSnippetItemNode.swift +++ b/TelegramUI/ListMessageSnippetItemNode.swift @@ -484,10 +484,10 @@ final class ListMessageSnippetItemNode: ListMessageNode { if content.instantPage != nil { if websiteType(of: content) == .instagram { if !item.controllerInteraction.openMessage(item.message, .default) { - item.controllerInteraction.openInstantPage(item.message) + item.controllerInteraction.openInstantPage(item.message, nil) } } else { - item.controllerInteraction.openInstantPage(item.message) + item.controllerInteraction.openInstantPage(item.message, nil) } } else { if isTelegramMeLink(content.url) || !item.controllerInteraction.openMessage(item.message, .default) { diff --git a/TelegramUI/MediaAutoDownloadSettings.swift b/TelegramUI/MediaAutoDownloadSettings.swift index 5ea4b01118..efabcec2f6 100644 --- a/TelegramUI/MediaAutoDownloadSettings.swift +++ b/TelegramUI/MediaAutoDownloadSettings.swift @@ -322,7 +322,7 @@ func isAutodownloadEnabledForAnyPeerType(category: MediaAutoDownloadCategory) -> return category.contacts || category.otherPrivate || category.groups || category.channels } -public func shouldDownloadMediaAutomatically(settings: MediaAutoDownloadSettings, peerType: MediaAutoDownloadPeerType, networkType: MediaAutoDownloadNetworkType, authorPeerId: PeerId?, contactsPeerIds: Set, media: Media) -> Bool { +public func shouldDownloadMediaAutomatically(settings: MediaAutoDownloadSettings, peerType: MediaAutoDownloadPeerType, networkType: MediaAutoDownloadNetworkType, authorPeerId: PeerId? = nil, contactsPeerIds: Set = Set(), media: Media) -> Bool { if (networkType == .cellular && !settings.cellular.enabled) || (networkType == .wifi && !settings.wifi.enabled) { return false } @@ -340,7 +340,11 @@ public func shouldDownloadMediaAutomatically(settings: MediaAutoDownloadSettings return false } if let size = size { - return size <= category.sizeLimit + var sizeLimit = category.sizeLimit + if let file = media as? TelegramMediaFile, file.isVoice { + sizeLimit = max(2 * 1024 * 1024, sizeLimit) + } + return size <= sizeLimit } else if category.sizeLimit == Int32.max { return true } else { diff --git a/TelegramUI/OpenChatMessage.swift b/TelegramUI/OpenChatMessage.swift index d81c98f9c6..c07fed7181 100644 --- a/TelegramUI/OpenChatMessage.swift +++ b/TelegramUI/OpenChatMessage.swift @@ -20,7 +20,7 @@ private enum ChatMessageGalleryControllerData { case chatAvatars(AvatarGalleryController, Media) } -private func chatMessageGalleryControllerData(context: AccountContext, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, stream: Bool, fromPlayingVideo: Bool, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { +private func chatMessageGalleryControllerData(context: AccountContext, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { var galleryMedia: Media? var otherMedia: Media? var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])? @@ -69,6 +69,21 @@ private func chatMessageGalleryControllerData(context: AccountContext, message: } } + var stream = false + var fromPlayingVideo = false + var landscape = false + + if case .stream = mode { + stream = true + } + if case .automaticPlayback = mode { + fromPlayingVideo = true + } + if case .landscape = mode { + fromPlayingVideo = true + landscape = true + } + if let (webPage, instantPageMedia) = instantPageMedia, let galleryMedia = galleryMedia { var centralIndex: Int = 0 for i in 0 ..< instantPageMedia.count { @@ -109,7 +124,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message: } #if DEBUG if ext == "mkv" { - let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: fromPlayingVideo, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in + let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: fromPlayingVideo, landscape: landscape, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in navigationController?.replaceTopController(controller, animated: false, ready: ready) }, baseNavigationController: navigationController, actionInteraction: actionInteraction) return .gallery(gallery) @@ -126,7 +141,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message: let gallery = SecretMediaPreviewController(context: context, messageId: message.id) return .secretGallery(gallery) } else { - let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: fromPlayingVideo, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in + let gallery = GalleryController(context: context, source: standalone ? .standaloneMessage(message) : .peerMessagesAtId(message.id), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: fromPlayingVideo, landscape: landscape, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in navigationController?.replaceTopController(controller, animated: false, ready: ready) }, baseNavigationController: navigationController, actionInteraction: actionInteraction) gallery.temporaryDoNotWaitForReady = fromPlayingVideo @@ -147,7 +162,7 @@ enum ChatMessagePreviewControllerData { } func chatMessagePreviewControllerData(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? { - if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, stream: false, fromPlayingVideo: false, synchronousLoad: true, actionInteraction: nil) { + if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, synchronousLoad: true, actionInteraction: nil) { switch mediaData { case let .gallery(gallery): return .gallery(gallery) @@ -160,8 +175,8 @@ func chatMessagePreviewControllerData(context: AccountContext, message: Message, return nil } -func openChatMessage(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, stream: Bool = false, fromPlayingVideo: Bool = false, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> (UIView?, UIView?))?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal, Media) -> Void, actionInteraction: GalleryControllerActionInteraction? = nil) -> Bool { - if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, stream: stream, fromPlayingVideo: fromPlayingVideo, synchronousLoad: false, actionInteraction: actionInteraction) { +func openChatMessage(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode = .default, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> (UIView?, UIView?))?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal, Media) -> Void, actionInteraction: GalleryControllerActionInteraction? = nil) -> Bool { + if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: mode, synchronousLoad: false, actionInteraction: actionInteraction) { switch mediaData { case let .url(url): openUrl(url) @@ -360,7 +375,7 @@ func openChatMessage(context: AccountContext, message: Message, standalone: Bool return false } -func openChatInstantPage(context: AccountContext, message: Message, navigationController: NavigationController) { +func openChatInstantPage(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?, navigationController: NavigationController) { for media in message.media { if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { if let _ = content.instantPage { @@ -402,7 +417,7 @@ func openChatInstantPage(context: AccountContext, message: Message, navigationCo anchor = String(textUrl[anchorRange.upperBound...]) } - let pageController = InstantPageController(context: context, webPage: webpage, anchor: anchor) + let pageController = InstantPageController(context: context, webPage: webpage, sourcePeerType: sourcePeerType ?? .channel, anchor: anchor) navigationController.pushViewController(pageController) } break diff --git a/TelegramUI/OpenResolvedUrl.swift b/TelegramUI/OpenResolvedUrl.swift index a705041ab1..29c9c381cb 100644 --- a/TelegramUI/OpenResolvedUrl.swift +++ b/TelegramUI/OpenResolvedUrl.swift @@ -77,7 +77,7 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlCon controller.sendSticker = sendFile present(controller, nil) case let .instantView(webpage, anchor): - navigationController?.pushViewController(InstantPageController(context: context, webPage: webpage, anchor: anchor)) + navigationController?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .channel, anchor: anchor)) case let .join(link): dismissInput() present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId in diff --git a/TelegramUI/OverlayPlayerControllerNode.swift b/TelegramUI/OverlayPlayerControllerNode.swift index 809d02792a..c69223eb22 100644 --- a/TelegramUI/OverlayPlayerControllerNode.swift +++ b/TelegramUI/OverlayPlayerControllerNode.swift @@ -54,7 +54,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec } else { return false } - }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, navigationController: { return nil }, presentGlobalOverlayController: { _, _ in diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index b771dd8212..77f6083bb6 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -187,9 +187,9 @@ public class PeerMediaCollectionController: TelegramController { }, shareCurrentLocation: { }, shareAccountContact: { }, sendBotCommand: { _, _ in - }, openInstantPage: { [weak self] message in + }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.navigationController as? NavigationController, let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { - openChatInstantPage(context: strongSelf.context, message: message, navigationController: navigationController) + openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController) } }, openWallpaper: { [weak self] message in if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message { diff --git a/TelegramUI/PresentationCall.swift b/TelegramUI/PresentationCall.swift index 6dcdb049f4..c6f3b2f5db 100644 --- a/TelegramUI/PresentationCall.swift +++ b/TelegramUI/PresentationCall.swift @@ -521,7 +521,11 @@ public final class PresentationCall { case .accepting, .active, .dropping, .requesting: switch state { case .connecting: - tone = .connecting + if case .requesting = previous.state { + tone = .ringing + } else { + tone = .connecting + } case .requesting(true): tone = .ringing case let .terminated(_, reason, _): @@ -529,10 +533,10 @@ public final class PresentationCall { switch reason { case let .ended(type): switch type { - case .busy: - tone = .busy - case .hungUp, .missed: - tone = .ended + case .busy: + tone = .busy + case .hungUp, .missed: + tone = .ended } case .error: tone = .failed diff --git a/TelegramUI/SearchBarPlaceholderNode.swift b/TelegramUI/SearchBarPlaceholderNode.swift index f1af3640b5..e4cc407e42 100644 --- a/TelegramUI/SearchBarPlaceholderNode.swift +++ b/TelegramUI/SearchBarPlaceholderNode.swift @@ -72,13 +72,18 @@ class SearchBarPlaceholderNode: ASDisplayNode { super.didLoad() let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.backgroundTap(_:))) - gestureRecognizer.highlight = { [weak self] _ in + gestureRecognizer.highlight = { [weak self] point in guard let strongSelf = self else { return } - - strongSelf.backgroundNode.layer.animate(from: (strongSelf.backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9).cgColor, keyPath: "backgroundColor", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: 0.3) - strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9) + if let _ = point { + strongSelf.backgroundNode.layer.animate(from: (strongSelf.backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9).cgColor, keyPath: "backgroundColor", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: 0.3) + strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9) + } else { + strongSelf.backgroundNode.layer.animate(from: (strongSelf.backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.cgColor, keyPath: "backgroundColor", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: 0.2, completion: { _ in + strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor + }) + } } gestureRecognizer.tapActionAtPoint = { _ in return .waitForSingleTap @@ -161,13 +166,7 @@ class SearchBarPlaceholderNode: ASDisplayNode { @objc private func backgroundTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { if case .ended = recognizer.state { - self.backgroundNode.layer.animate(from: (self.backgroundNode.backgroundColor ?? self.foregroundColor).cgColor, to: self.foregroundColor.cgColor, keyPath: "backgroundColor", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: 0.2, completion: { _ in - self.backgroundNode.backgroundColor = self.foregroundColor - }) - - if let activate = self.activate { - activate() - } + self.activate?() } } } diff --git a/TelegramUI/UniversalVideoGalleryItem.swift b/TelegramUI/UniversalVideoGalleryItem.swift index 15a5c0b73e..33a66cdfdb 100644 --- a/TelegramUI/UniversalVideoGalleryItem.swift +++ b/TelegramUI/UniversalVideoGalleryItem.swift @@ -22,11 +22,12 @@ class UniversalVideoGalleryItem: GalleryItem { let credit: NSAttributedString? let hideControls: Bool let fromPlayingVideo: Bool + let landscape: Bool let playbackCompleted: () -> Void let performAction: (GalleryControllerInteractionTapAction) -> Void let openActionOptions: (GalleryControllerInteractionTapAction) -> Void - init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, hideControls: Bool = false, fromPlayingVideo: Bool = false, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void) { + init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, hideControls: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void) { self.context = context self.presentationData = presentationData self.content = content @@ -37,6 +38,7 @@ class UniversalVideoGalleryItem: GalleryItem { self.credit = credit self.hideControls = hideControls self.fromPlayingVideo = fromPlayingVideo + self.landscape = landscape self.playbackCompleted = playbackCompleted self.performAction = performAction self.openActionOptions = openActionOptions @@ -159,6 +161,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private var validLayout: (ContainerViewLayout, CGFloat)? private var didPause = false private var isPaused = true + private var dismissOnOrientationChange = false + private var keepSoundOnDismiss = false private var requiresDownload = false @@ -260,6 +264,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) + var dismiss = false + if let (previousLayout, _) = self.validLayout, self.dismissOnOrientationChange, previousLayout.size.width > previousLayout.size.height && previousLayout.size.height == layout.size.width { + dismiss = true + } self.validLayout = (layout, navigationBarHeight) let statusDiameter: CGFloat = 50.0 @@ -274,6 +282,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { pictureInPictureNode.updateLayout(placeholderSize, transition: transition) } } + + if dismiss { + self.keepSoundOnDismiss = true + self.dismiss() + } } func setupItem(_ item: UniversalVideoGalleryItem) { @@ -282,6 +295,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.statusButtonNode.isHidden = true } + self.dismissOnOrientationChange = item.landscape + var disablePlayerControls = false var isAnimated = false if let content = item.content as? NativeVideoContent { @@ -548,8 +563,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.initiallyActivated = true videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: .stop) } - } else if videoNode.ownsContentNode { - videoNode.pause() + } else { + self.dismissOnOrientationChange = false + if videoNode.ownsContentNode { + videoNode.pause() + } } } } @@ -623,8 +641,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.context.sharedContext.mediaManager.setOverlayVideoNode(nil) } else { var transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view) - let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview) - let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view) + var transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview) + var transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view) let transformedCopyViewFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view) let (maybeSurfaceCopyView, _) = node.1() @@ -640,6 +658,18 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let contentSurface = surfaceCopyView.superview { transformedSurfaceFrame = node.0.view.convert(node.0.view.bounds, to: contentSurface) transformedSurfaceFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: contentSurface) + + if let frame = transformedSurfaceFrame, frame.minY < 0.0 { + transformedSurfaceFrame = CGRect(x: frame.minX, y: 0.0, width: frame.width, height: frame.height) + } + } + + if transformedSelfFrame.maxY < 0.0 { + transformedSelfFrame = CGRect(x: transformedSelfFrame.minX, y: 0.0, width: transformedSelfFrame.width, height: transformedSelfFrame.height) + } + + if transformedSuperFrame.maxY < 0.0 { + transformedSuperFrame = CGRect(x: transformedSuperFrame.minX, y: 0.0, width: transformedSuperFrame.width, height: transformedSuperFrame.height) } if let transformedSurfaceFrame = transformedSurfaceFrame { @@ -806,7 +836,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { toTransform = CATransform3DScale(videoNode.layer.transform, transformScale, transformScale, 1.0) if videoNode.hasAttachedContext { - videoNode.continuePlayingWithoutSound() + if self.isPaused || !self.keepSoundOnDismiss { + videoNode.continuePlayingWithoutSound() + } } } else { videoNode.allowsGroupOpacity = true