Enabled streaming for GIFs and round videos

Play video in fullscreen on switching to landscape
Use autodownload settings in IV
This commit is contained in:
Ilya Laktyushin 2019-03-01 22:48:45 +04:00
parent df83998c74
commit 8c860dc9aa
38 changed files with 401 additions and 142 deletions

View File

@ -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<Void, NoError> in
let initialConnection = initialValue.connectionSettings(for: connectionType.automaticDownloadNetworkType)
let currentConnection = currentValue.connectionSettings(for: connectionType.automaticDownloadNetworkType)

View File

@ -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
}
}
}

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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 {

View File

@ -8,27 +8,6 @@ import Display
import UIKit
import AVFoundation
private func videoFirstFrameData(account: Account, resource: MediaResource, chunkSize: Int) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
if let size = resource.size {
return account.postbox.mediaBox.resourceData(resource, size: size, in: 0 ..< min(size, chunkSize))
|> mapToSignal { _ -> Signal<CachedMediaResourceRepresentationResult, NoError> in
return account.postbox.mediaBox.resourceData(resource, option: .incremental(waitUntilFetchStatus: false), attemptSynchronously: false)
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: data)
|> `catch` { _ -> Signal<CachedMediaResourceRepresentationResult, NoError> 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<CachedMediaResourceRepresentationResult, NoError> {
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<CachedMediaResourceRepresentationResult, NoError> {
if let size = resource.size {
return account.postbox.mediaBox.resourceData(resource, size: size, in: 0 ..< min(size, chunkSize))
|> mapToSignal { _ -> Signal<CachedMediaResourceRepresentationResult, NoError> in
return account.postbox.mediaBox.resourceData(resource, option: .incremental(waitUntilFetchStatus: false), attemptSynchronously: false)
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: data)
|> `catch` { _ -> Signal<CachedMediaResourceRepresentationResult, NoError> 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<CachedMediaResourceRepresentationResult, NoError> {
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)

View File

@ -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<Bool>?) -> 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<Bool>?) -> 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
}

View File

@ -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()

View File

@ -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 }

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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):

View File

@ -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))
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,7 +256,9 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
@objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation {
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 {
@ -248,6 +270,15 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
default:
break
}
case .Remote:
if case .tap = gesture {
self.fetchControls?.fetch(true)
}
case .Fetching:
if case .tap = gesture {
self.fetchControls?.cancel()
}
}
}
default:
break

View File

@ -5,17 +5,26 @@ 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
@ -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 {
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()
}
}
}
}

View File

@ -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) {

View File

@ -203,6 +203,7 @@ class ItemListController<Entry: ItemListNodeEntry>: ViewController {
var commitPreview: ((UIViewController) -> Void)?
var willDisappear: ((Bool) -> Void)?
var didDisappear: ((Bool) -> Void)?
convenience init(context: AccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState<Entry>, Entry.ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>? = nil) {
self.init(sharedContext: context.sharedContext, state: state, tabBarItem: tabBarItem)
@ -460,6 +461,12 @@ class ItemListController<Entry: ItemListNodeEntry>: 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<Entry>).animateOut(completion: completion)
}

View File

@ -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) {

View File

@ -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<PeerId>, media: Media) -> Bool {
public func shouldDownloadMediaAutomatically(settings: MediaAutoDownloadSettings, peerType: MediaAutoDownloadPeerType, networkType: MediaAutoDownloadNetworkType, authorPeerId: PeerId? = nil, contactsPeerIds: Set<PeerId> = 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 {

View File

@ -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<InstantPageGalleryEntry?, NoError>, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, 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<InstantPageGalleryEntry?, NoError>, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, 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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -521,7 +521,11 @@ public final class PresentationCall {
case .accepting, .active, .dropping, .requesting:
switch state {
case .connecting:
if case .requesting = previous.state {
tone = .ringing
} else {
tone = .connecting
}
case .requesting(true):
tone = .ringing
case let .terminated(_, reason, _):

View File

@ -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
}
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?()
}
}
}

View File

@ -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,12 +563,15 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.initiallyActivated = true
videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: .stop)
}
} else if videoNode.ownsContentNode {
} else {
self.dismissOnOrientationChange = false
if videoNode.ownsContentNode {
videoNode.pause()
}
}
}
}
}
override func visibilityUpdated(isVisible: Bool) {
super.visibilityUpdated(isVisible: isVisible)
@ -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,8 +836,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
toTransform = CATransform3DScale(videoNode.layer.transform, transformScale, transformScale, 1.0)
if videoNode.hasAttachedContext {
if self.isPaused || !self.keepSoundOnDismiss {
videoNode.continuePlayingWithoutSound()
}
}
} else {
videoNode.allowsGroupOpacity = true
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in