From 518653f549960ea9669ce15ff7eb9c7b0d0bb2d1 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 15 Feb 2019 14:12:36 +0400 Subject: [PATCH] Inline video playback fixes --- TelegramUI/CallFeedbackController.swift | 4 +- TelegramUI/CallRatingController.swift | 8 +- TelegramUI/ChatBubbleVideoDecoration.swift | 12 ++- TelegramUI/ChatController.swift | 2 +- TelegramUI/ChatControllerInteraction.swift | 1 + TelegramUI/ChatMessageBubbleItemNode.swift | 2 +- .../ChatMessageInteractiveMediaBadge.swift | 32 ++++---- .../ChatMessageInteractiveMediaNode.swift | 35 +++++++-- TelegramUI/ChatMessageItemView.swift | 2 +- .../ChatMessageMediaBubbleContentNode.swift | 26 ++++++- TelegramUI/GalleryController.swift | 20 ++--- TelegramUI/GalleryVideoDecoration.swift | 21 +++++- TelegramUI/OpenChatMessage.swift | 13 ++-- TelegramUI/SettingsController.swift | 30 ++++++-- TelegramUI/UniversalVideoGalleryItem.swift | 74 +++++++++++++------ TelegramUI/UniversalVideoNode.swift | 1 + TelegramUI/WallpaperUploadManager.swift | 21 +++++- 17 files changed, 219 insertions(+), 85 deletions(-) diff --git a/TelegramUI/CallFeedbackController.swift b/TelegramUI/CallFeedbackController.swift index 39656f649b..9cd5b5c64f 100644 --- a/TelegramUI/CallFeedbackController.swift +++ b/TelegramUI/CallFeedbackController.swift @@ -204,7 +204,7 @@ private func callFeedbackControllerEntries(theme: PresentationTheme, strings: Pr return entries } -public func callFeedbackController(sharedContext: SharedAccountContext, account: Account, callId: CallId, rating: Int) -> ViewController { +public func callFeedbackController(sharedContext: SharedAccountContext, account: Account, callId: CallId, rating: Int, userInitiated: Bool) -> ViewController { let initialState = CallFeedbackState() let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -255,7 +255,7 @@ public func callFeedbackController(sharedContext: SharedAccountContext, account: } comment.append(hashtags) - let _ = rateCallAndSendLogs(account: account, callId: callId, starsCount: rating, comment: comment, includeLogs: state.includeLogs).start() + let _ = rateCallAndSendLogs(account: account, callId: callId, starsCount: rating, comment: comment, userInitiated: userInitiated, includeLogs: state.includeLogs).start() dismissImpl?() presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .starSuccess(presentationData.strings.CallFeedback_Success))) diff --git a/TelegramUI/CallRatingController.swift b/TelegramUI/CallRatingController.swift index 4408516d6c..f376d6343c 100644 --- a/TelegramUI/CallRatingController.swift +++ b/TelegramUI/CallRatingController.swift @@ -305,10 +305,10 @@ private final class CallRatingAlertContentNode: AlertContentNode { } } -func rateCallAndSendLogs(account: Account, callId: CallId, starsCount: Int, comment: String, includeLogs: Bool) -> Signal { +func rateCallAndSendLogs(account: Account, callId: CallId, starsCount: Int, comment: String, userInitiated: Bool, includeLogs: Bool) -> Signal { let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 4244000) - let rate = rateCall(account: account, callId: callId, starsCount: Int32(starsCount), comment: comment) + let rate = rateCall(account: account, callId: callId, starsCount: Int32(starsCount), comment: comment, userInitiated: userInitiated) if includeLogs { let id = arc4random64() let name = "\(callId.id)_\(callId.accessHash).log" @@ -347,10 +347,10 @@ func callRatingController(sharedContext: SharedAccountContext, account: Account, }, apply: { rating in dismissImpl?(true) if rating < 4 { - let controller = callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating) + let controller = callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating, userInitiated: userInitiated) present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } else { - let _ = rateCallAndSendLogs(account: account, callId: callId, starsCount: rating, comment: "", includeLogs: false).start() + let _ = rateCallAndSendLogs(account: account, callId: callId, starsCount: rating, comment: "", userInitiated: userInitiated, includeLogs: false).start() } }) diff --git a/TelegramUI/ChatBubbleVideoDecoration.swift b/TelegramUI/ChatBubbleVideoDecoration.swift index a24b93cc91..52598b32af 100644 --- a/TelegramUI/ChatBubbleVideoDecoration.swift +++ b/TelegramUI/ChatBubbleVideoDecoration.swift @@ -25,9 +25,16 @@ final class ChatBubbleVideoDecoration: UniversalVideoDecoration { self.contentContainerNode.backgroundColor = backgroundColor self.contentContainerNode.clipsToBounds = true + self.updateCorners(corners) + } + + func updateCorners(_ corners: ImageCorners) { if isRoundEqualCorners(corners) { self.contentContainerNode.cornerRadius = corners.topLeft.radius + self.contentContainerNode.layer.mask = nil } else { + self.contentContainerNode.cornerRadius = 0 + let boundingSize: CGSize = CGSize(width: max(corners.topLeft.radius, corners.bottomLeft.radius) + max(corners.topRight.radius, corners.bottomRight.radius), height: max(corners.topLeft.radius, corners.topRight.radius) + max(corners.bottomLeft.radius, corners.bottomRight.radius)) let size: CGSize = CGSize(width: boundingSize.width + corners.extendedEdges.left + corners.extendedEdges.right, height: boundingSize.height + corners.extendedEdges.top + corners.extendedEdges.bottom) let arguments = TransformImageArguments(corners: corners, imageSize: size, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) @@ -37,14 +44,15 @@ final class ChatBubbleVideoDecoration: UniversalVideoDecoration { ctx.fill(arguments.drawingRect) } addCorners(context, arguments: arguments) - + if let maskImage = context.generateImage() { let mask = CALayer() mask.contents = maskImage.cgImage mask.contentsScale = maskImage.scale mask.contentsCenter = CGRect(x: max(corners.topLeft.radius, corners.bottomLeft.radius) / maskImage.size.width, y: max(corners.topLeft.radius, corners.topRight.radius) / maskImage.size.height, width: (maskImage.size.width - max(corners.topLeft.radius, corners.bottomLeft.radius) - max(corners.topRight.radius, corners.bottomRight.radius)) / maskImage.size.width, height: (maskImage.size.height - max(corners.topLeft.radius, corners.topRight.radius) - max(corners.bottomLeft.radius, corners.bottomRight.radius)) / maskImage.size.height) - + self.contentContainerNode.layer.mask = mask + self.contentContainerNode.layer.mask?.frame = self.contentContainerNode.bounds } } } diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index c31097445e..7a488615d2 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -323,7 +323,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal } } - return openChatMessage(context: context, message: message, standalone: false, reverseMessageGalleryOrder: false, stream: mode == .stream, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: { + return openChatMessage(context: context, message: message, standalone: false, reverseMessageGalleryOrder: false, stream: mode == .stream, fromPlayingVideo: mode == .automaticPlayback, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: { self?.chatDisplayNode.dismissInput() }, present: { c, a in self?.present(c, in: .window(.root), with: a, blockInteraction: true) diff --git a/TelegramUI/ChatControllerInteraction.swift b/TelegramUI/ChatControllerInteraction.swift index 0700a265da..c54a1bcbb5 100644 --- a/TelegramUI/ChatControllerInteraction.swift +++ b/TelegramUI/ChatControllerInteraction.swift @@ -41,6 +41,7 @@ public enum ChatControllerInteractionOpenMessageMode { case `default` case stream case shared + case automaticPlayback } struct ChatInterfacePollActionState: Equatable { diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index 421e43e88f..f8c0280714 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -443,7 +443,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView { tmpWidth -= 38.0 } } else { - tmpWidth = isInlinePlayableVideo ? min(570.0, baseWidth - 36.0) : layoutConstants.bubble.maximumWidthFill.widthFor(baseWidth) + tmpWidth = layoutConstants.bubble.maximumWidthFill.widthFor(baseWidth) if needShareButton && tmpWidth + 32.0 > baseWidth { tmpWidth = baseWidth - 32.0 } diff --git a/TelegramUI/ChatMessageInteractiveMediaBadge.swift b/TelegramUI/ChatMessageInteractiveMediaBadge.swift index 00bc2c0320..99fc1bb633 100644 --- a/TelegramUI/ChatMessageInteractiveMediaBadge.swift +++ b/TelegramUI/ChatMessageInteractiveMediaBadge.swift @@ -203,23 +203,25 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode { textTransition.updateFrame(node: self.durationNode, frame: CGRect(x: active ? 42.0 : 7.0, y: active ? 7.0 : 2.0, width: durationSize.width, height: durationSize.height)) + let iconNode: ASImageNode + if let current = self.iconNode { + iconNode = current + } else { + iconNode = ASImageNode() + iconNode.frame = CGRect(x: 0.0, y: 0.0, width: 14.0, height: 9.0) + self.iconNode = iconNode + self.backgroundNode.addSubnode(iconNode) + } + + if self.foregroundColor != foregroundColor { + self.foregroundColor = foregroundColor + iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/InlineVideoMute"), color: foregroundColor) + } + + transition.updatePosition(node: iconNode, position: CGPoint(x: (active ? 42.0 : 7.0) + floor(durationSize.width) + 4.0 + 7.0, y: (active ? 9.0 : 4.0) + 5.0)) + if muted { - let iconNode: ASImageNode - if let current = self.iconNode { - iconNode = current - } else { - iconNode = ASImageNode() - iconNode.frame = CGRect(x: 0.0, y: 0.0, width: 14.0, height: 9.0) - self.iconNode = iconNode - self.backgroundNode.addSubnode(iconNode) - } - - if self.foregroundColor != foregroundColor { - self.foregroundColor = foregroundColor - iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/InlineVideoMute"), color: foregroundColor) - } transition.updateAlpha(node: iconNode, alpha: 1.0) - transition.updatePosition(node: iconNode, position: CGPoint(x: (active ? 42.0 : 7.0) + floor(durationSize.width) + 4.0 + 7.0, y: (active ? 9.0 : 4.0) + 5.0)) transition.updateTransformScale(node: iconNode, scale: 1.0) } else if let iconNode = self.iconNode { transition.updateAlpha(node: iconNode, alpha: 0.0) diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index bb919e99e3..94fd6230e5 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -23,6 +23,7 @@ enum InteractiveMediaNodeContentMode { enum InteractiveMediaNodeActivateContent { case `default` case stream + case automaticPlayback } enum InteractiveMediaNodeAutodownloadMode { @@ -119,10 +120,15 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { private func progressPressed(canActivate: Bool) { if let fetchStatus = self.fetchStatus { + var isAnimated = false + if let file = self.media as? TelegramMediaFile, file.isAnimated { + isAnimated = true + } + var activateContent = false if let state = self.statusNode?.state, case .play = state { activateContent = true - } else if let message = self.message, !message.flags.isSending && (self.automaticPlayback ?? false) { + } else if let message = self.message, !message.flags.isSending && (self.automaticPlayback ?? false) && !isAnimated { activateContent = true } if canActivate, activateContent { @@ -165,7 +171,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { if case .ended = recognizer.state { let point = recognizer.location(in: self.imageNode.view) if let fetchStatus = self.fetchStatus, case .Local = fetchStatus { - self.activateLocalContent(.default) + self.activateLocalContent((self.automaticPlayback ?? false) ? .automaticPlayback : .default) } else { if let message = self.message, message.flags.isSending { if let statusNode = self.statusNode, statusNode.frame.contains(point) { @@ -227,7 +233,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { unboundSize = CGSize(width: floor(dimensions.width * 0.5), height: floor(dimensions.height * 0.5)) if file.isAnimated { unboundSize = unboundSize.aspectFilled(CGSize(width: 480.0, height: 480.0)) - } else if file.isVideo && automaticPlayback && !file.isAnimated, case let .constrained(constrainedSize) = sizeCalculation { + } else if file.isVideo && !file.isAnimated, case let .constrained(constrainedSize) = sizeCalculation { maxDimensions = constrainedSize } else if file.isSticker { unboundSize = unboundSize.aspectFilled(CGSize(width: 162.0, height: 162.0)) @@ -414,6 +420,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { if file.resource is VideoLibraryMediaResource { uploading = true } + if file.isVideo && !isSecretMedia && automaticPlayback && !uploading { updateVideoFile = file if hasCurrentVideoNode { @@ -561,7 +568,9 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { let decoration = ChatBubbleVideoDecoration(corners: arguments.corners, nativeSize: nativeSize, contentMode: contentMode, backgroundColor: arguments.emptyColor ?? .black) strongSelf.videoNodeDecoration = decoration let mediaManager = context.sharedContext.mediaManager - 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: !updatedVideoFile.isAnimated, enableSound: false, fetchAutomatically: false), priority: .embedded) + + let streamVideo = !updatedVideoFile.isAnimated && message.id.namespace != Namespaces.Message.SecretIncoming + 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), priority: .embedded) videoNode.isUserInteractionEnabled = false strongSelf.videoNode = videoNode @@ -572,6 +581,10 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } if let videoNode = strongSelf.videoNode { + if replaceVideoNode == nil, let decoration = videoNode.decoration as? ChatBubbleVideoDecoration, decoration.corners != corners { + decoration.updateCorners(corners) + } + videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate) videoNode.frame = imageFrame @@ -649,7 +662,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } } } - } else if case .prefetch = automaticDownload { + } else if case .prefetch = automaticDownload, message.id.namespace != Namespaces.Message.SecretIncoming { if let file = media as? TelegramMediaFile, let fileSize = file.size { let fetchHeadRange: Range = 0 ..< 2 * 1024 * 1024 let fetchTailRange: Range = fileSize - 64 * 1024 ..< Int(Int32.max) @@ -709,7 +722,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } private func updateFetchStatus() { - guard let (theme, strings) = self.themeAndStrings, let sizeCalculation = self.sizeCalculation, let message = self.message, let automaticPlayback = self.automaticPlayback else { + guard let (theme, strings) = self.themeAndStrings, let sizeCalculation = self.sizeCalculation, let message = self.message, var automaticPlayback = self.automaticPlayback else { return } @@ -726,6 +739,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { break } } + automaticPlayback = false } var webpage: TelegramMediaWebpage? @@ -839,6 +853,10 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { active = true } + if message.flags.contains(.Unsent) { + automaticPlayback = false + } + if let actualFetchStatus = self.actualFetchStatus, automaticPlayback { fetchStatus = actualFetchStatus } @@ -856,7 +874,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { state = .progress(color: bubbleTheme.mediaOverlayControlForegroundColor, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: true) } - if let file = media as? TelegramMediaFile, (!file.isAnimated || message.flags.contains(.Unsent)) { + if let file = self.media as? TelegramMediaFile, (!file.isAnimated || message.flags.contains(.Unsent)) { if case .constrained = sizeCalculation { if let size = file.size { if let duration = file.duration, !message.flags.contains(.Unsent) { @@ -954,7 +972,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: dataSizeString(file.size ?? 0), muted: muted, active: true) mediaDownloadState = .remote } else { - badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: nil, muted: false, active: false) + state = automaticPlayback ? .none : state + badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: nil, muted: muted, active: false) } } else { if isMediaStreamable(message: message, media: file) { diff --git a/TelegramUI/ChatMessageItemView.swift b/TelegramUI/ChatMessageItemView.swift index c73db70d60..36f1c7d574 100644 --- a/TelegramUI/ChatMessageItemView.swift +++ b/TelegramUI/ChatMessageItemView.swift @@ -70,7 +70,7 @@ struct ChatMessageItemLayoutConstants { self.avatarDiameter = 37.0 self.timestampHeaderHeight = 34.0 - self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 40.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel) + self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel) self.text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0)) self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 1.0 + UIScreenPixel, left: 1.0 + UIScreenPixel, bottom: 1.0 + UIScreenPixel, right: 1.0 + UIScreenPixel), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 17.0, mergedCornerRadius: 5.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 300.0, height: 300.0), minDimensions: CGSize(width: 74.0, height: 74.0)) self.file = ChatMessageItemFileLayoutConstants(bubbleInsets: UIEdgeInsets(top: 15.0, left: 9.0, bottom: 15.0, right: 12.0)) diff --git a/TelegramUI/ChatMessageMediaBubbleContentNode.swift b/TelegramUI/ChatMessageMediaBubbleContentNode.swift index d2ed1186be..8ee79049b0 100644 --- a/TelegramUI/ChatMessageMediaBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMediaBubbleContentNode.swift @@ -34,7 +34,16 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { self.interactiveImageNode.activateLocalContent = { [weak self] mode in if let strongSelf = self { if let item = strongSelf.item { - let _ = item.controllerInteraction.openMessage(item.message, mode == .stream ? .stream : .default) + let openChatMessageMode: ChatControllerInteractionOpenMessageMode + switch mode { + case .default: + openChatMessageMode = .default + case .stream: + openChatMessageMode = .stream + case .automaticPlayback: + openChatMessageMode = .automaticPlayback + } + let _ = item.controllerInteraction.openMessage(item.message, openChatMessageMode) } } } @@ -70,10 +79,19 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { if telegramFile.isAnimated { automaticPlayback = item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs + contentMode = .aspectFill } else { - if case .full = automaticDownload, item.controllerInteraction.automaticMediaDownloadSettings.autoplayVideos { - automaticPlayback = true - contentMode = .aspectFill + if item.controllerInteraction.automaticMediaDownloadSettings.autoplayVideos { + var willDownloadOrLocal = false + if case .full = automaticDownload { + willDownloadOrLocal = true + } else { + willDownloadOrLocal = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil + } + if willDownloadOrLocal { + automaticPlayback = true + contentMode = .aspectFill + } } } } diff --git a/TelegramUI/GalleryController.swift b/TelegramUI/GalleryController.swift index 3651a8f977..45ed20abb5 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, 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, 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) { @@ -164,7 +164,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, 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, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions) } else { if file.mimeType.hasPrefix("image/") && file.mimeType != "image/gif" { var pixelsCount: Int = 0 @@ -185,12 +185,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: ""), 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, 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: ""), 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, 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: ""), 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, performAction: performAction, openActionOptions: openActionOptions) } } } @@ -289,7 +289,8 @@ class GalleryController: ViewController { private var adjustedForInitialPreviewingLayout = false - var temporaryDoNotWaitForReady = true + var temporaryDoNotWaitForReady = false + private let fromPlayingVideo: Bool private let accountInUseDisposable = MetaDisposable() private let disposable = MetaDisposable() @@ -315,12 +316,13 @@ class GalleryController: ViewController { private var performAction: (GalleryControllerInteractionTapAction) -> Void private var openActionOptions: (GalleryControllerInteractionTapAction) -> Void - init(context: AccountContext, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: 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, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, ValuePromise?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) { self.context = context self.replaceRootController = replaceRootController self.baseNavigationController = baseNavigationController self.actionInteraction = actionInteraction self.streamVideos = streamSingleVideo + self.fromPlayingVideo = fromPlayingVideo self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -427,7 +429,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, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions) { + 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 isCentral { centralItemIndex = items.count } @@ -806,7 +808,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, performAction: self.performAction, openActionOptions: self.openActionOptions) { + if let item = galleryItemForEntry(context: context, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, fromPlayingVideo: self.fromPlayingVideo, performAction: self.performAction, openActionOptions: self.openActionOptions) { if case let .MessageEntry(message, _, _, _) = entry, message.stableId == self.centralEntryStableId { centralItemIndex = items.count } diff --git a/TelegramUI/GalleryVideoDecoration.swift b/TelegramUI/GalleryVideoDecoration.swift index 3f2157877d..57a2b62d4e 100644 --- a/TelegramUI/GalleryVideoDecoration.swift +++ b/TelegramUI/GalleryVideoDecoration.swift @@ -57,7 +57,7 @@ final class GalleryVideoDecoration: UniversalVideoDecoration { if let maskImage = context.generateImage() { let mask = CALayer() mask.contents = maskImage.cgImage - mask.contentsScale = 2.0// maskImage.scale + mask.contentsScale = maskImage.scale mask.contentsCenter = CGRect(x: max(corners.topLeft.radius, corners.bottomLeft.radius) / maskImage.size.width, y: max(corners.topLeft.radius, corners.topRight.radius) / maskImage.size.height, width: (maskImage.size.width - max(corners.topLeft.radius, corners.bottomLeft.radius) - max(corners.topRight.radius, corners.bottomRight.radius)) / maskImage.size.width, height: (maskImage.size.height - max(corners.topLeft.radius, corners.topRight.radius) - max(corners.bottomLeft.radius, corners.bottomRight.radius)) / maskImage.size.height) self.contentContainerNode.layer.mask = mask @@ -66,6 +66,25 @@ final class GalleryVideoDecoration: UniversalVideoDecoration { } } + func updateClippingFrame(_ frame: CGRect, completion: (() -> Void)?) { + self.contentContainerNode.layer.animate(from: NSValue(cgRect: self.contentContainerNode.bounds), to: NSValue(cgRect: frame), keyPath: "bounds", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + }) + + if let maskLayer = self.contentContainerNode.layer.mask { + maskLayer.animate(from: NSValue(cgRect: self.contentContainerNode.bounds), to: NSValue(cgRect: frame), keyPath: "bounds", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + }) + + maskLayer.animate(from: NSValue(cgPoint: maskLayer.position), to: NSValue(cgPoint: frame.center), keyPath: "position", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + }) + } + + if let contentNode = self.contentNode { + contentNode.layer.animate(from: NSValue(cgPoint: contentNode.layer.position), to: NSValue(cgPoint: frame.center), keyPath: "position", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + completion?() + }) + } + } + func updateContentNodeSnapshot(_ snapshot: UIView?) { } diff --git a/TelegramUI/OpenChatMessage.swift b/TelegramUI/OpenChatMessage.swift index 7849c74c94..b6b52e73c8 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, synchronousLoad: Bool, excludeWebPageMedia: Bool = false, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { +private func chatMessageGalleryControllerData(context: AccountContext, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, stream: Bool, fromPlayingVideo: Bool, synchronousLoad: Bool, excludeWebPageMedia: Bool = false, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? { var galleryMedia: Media? var otherMedia: Media? var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])? @@ -109,7 +109,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, 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, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in navigationController?.replaceTopController(controller, animated: false, ready: ready) }, baseNavigationController: navigationController, actionInteraction: actionInteraction) return .gallery(gallery) @@ -126,9 +126,10 @@ 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, 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, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in navigationController?.replaceTopController(controller, animated: false, ready: ready) }, baseNavigationController: navigationController, actionInteraction: actionInteraction) + gallery.temporaryDoNotWaitForReady = fromPlayingVideo return .gallery(gallery) } } @@ -146,7 +147,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, synchronousLoad: true, actionInteraction: nil) { + if let mediaData = chatMessageGalleryControllerData(context: context, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, stream: false, fromPlayingVideo: false, synchronousLoad: true, actionInteraction: nil) { switch mediaData { case let .gallery(gallery): return .gallery(gallery) @@ -159,8 +160,8 @@ func chatMessagePreviewControllerData(context: AccountContext, message: Message, return nil } -func openChatMessage(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, stream: Bool = false, excludeWebPageMedia: Bool = false, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> 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, synchronousLoad: false, excludeWebPageMedia: excludeWebPageMedia, actionInteraction: actionInteraction) { +func openChatMessage(context: AccountContext, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, stream: Bool = false, fromPlayingVideo: Bool = false, excludeWebPageMedia: Bool = false, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> 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, excludeWebPageMedia: excludeWebPageMedia, actionInteraction: actionInteraction) { switch mediaData { case let .url(url): openUrl(url) diff --git a/TelegramUI/SettingsController.swift b/TelegramUI/SettingsController.swift index f69f008ba2..1c339199f7 100644 --- a/TelegramUI/SettingsController.swift +++ b/TelegramUI/SettingsController.swift @@ -589,11 +589,12 @@ public func settingsController(context: AccountContext, accountManager: AccountM let auxiliaryMethods = context.account.auxiliaryMethods let rootPath = rootPathForBasePath(context.sharedContext.applicationBindings.containerPath) + let sharedContext = context.sharedContext let accountsAndPeersSignal: Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError> = context.sharedContext.activeAccounts |> mapToSignal { primary, activeAccounts, _ -> Signal<((Account, Peer)?, [(Account, Peer, Int32)]), NoError> in var accounts: [Signal<(Account, Peer, Int32)?, NoError>] = [] func accountWithPeer(_ account: Account) -> Signal<(Account, Peer, Int32)?, NoError> { - return combineLatest(account.postbox.peerView(id: account.peerId), renderedTotalUnreadCount(accountManager: context.sharedContext.accountManager, postbox: account.postbox)) + return combineLatest(account.postbox.peerView(id: account.peerId), renderedTotalUnreadCount(accountManager: sharedContext.accountManager, postbox: account.postbox)) |> map { view, totalUnreadCount -> (Peer?, Int32) in return (view.peers[view.peerId], totalUnreadCount.0) } @@ -701,7 +702,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM }, openProxy: { let _ = (contextValue.get() |> deliverOnMainQueue - |> take(1)).start(next: { account in + |> take(1)).start(next: { context in pushControllerImpl?(proxySettingsController(context: context)) }) }, openSavedMessages: { @@ -709,7 +710,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM }, openRecentCalls: { let _ = (contextValue.get() |> deliverOnMainQueue - |> take(1)).start(next: { account in + |> take(1)).start(next: { context in pushControllerImpl?(CallListController(context: context, mode: .navigation)) }) }, openPrivacyAndSecurity: { @@ -1296,7 +1297,11 @@ public func settingsController(context: AccountContext, accountManager: AccountM }) } switchToAccountImpl = { [weak controller] id in - context.sharedContext.switchToAccount(id: id, fromSettingsController: controller) + let _ = (contextValue.get() + |> take(1) + |> deliverOnMainQueue).start(next: { context in + context.sharedContext.switchToAccount(id: id, fromSettingsController: controller) + }) } controller.didAppear = { _ in updatePassport() @@ -1315,17 +1320,28 @@ public func settingsController(context: AccountContext, accountManager: AccountM } } }) - if let selectedAccount = selectedAccount { - let accountContext = AccountContext(sharedContext: context.sharedContext, account: selectedAccount, limitsConfiguration: LimitsConfiguration.defaultValue) + var sharedContext: SharedAccountContext? + let _ = (contextValue.get() + |> deliverOnMainQueue + |> take(1)).start(next: { context in + sharedContext = context.sharedContext + }) + if let selectedAccount = selectedAccount, let sharedContext = sharedContext { + let accountContext = AccountContext(sharedContext: sharedContext, account: selectedAccount, limitsConfiguration: LimitsConfiguration.defaultValue) let chatListController = ChatListController(context: accountContext, groupId: nil, controlsHistoryPreload: false, hideNetworkActivityStatus: true) return chatListController + } } return nil } controller.commitPreview = { previewController in if let chatListController = previewController as? ChatListController { - context.sharedContext.switchToAccount(id: chatListController.context.account.id, withChatListController: chatListController) + let _ = (contextValue.get() + |> deliverOnMainQueue + |> take(1)).start(next: { context in + context.sharedContext.switchToAccount(id: chatListController.context.account.id, withChatListController: chatListController) + }) } } controller.switchToAccount = { id in diff --git a/TelegramUI/UniversalVideoGalleryItem.swift b/TelegramUI/UniversalVideoGalleryItem.swift index e07106b59a..0ed54c6f9a 100644 --- a/TelegramUI/UniversalVideoGalleryItem.swift +++ b/TelegramUI/UniversalVideoGalleryItem.swift @@ -21,11 +21,12 @@ class UniversalVideoGalleryItem: GalleryItem { let caption: NSAttributedString let credit: NSAttributedString? let hideControls: Bool + let fromPlayingVideo: 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, 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, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void) { self.context = context self.presentationData = presentationData self.content = content @@ -35,6 +36,7 @@ class UniversalVideoGalleryItem: GalleryItem { self.caption = caption self.credit = credit self.hideControls = hideControls + self.fromPlayingVideo = fromPlayingVideo self.playbackCompleted = playbackCompleted self.performAction = performAction self.openActionOptions = openActionOptions @@ -309,8 +311,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.videoNode = videoNode videoNode.isUserInteractionEnabled = disablePlayerControls videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335) - videoNode.canAttachContent = false - //self.updateDisplayPlaceholder(!videoNode.ownsContentNode) + if item.fromPlayingVideo { + videoNode.canAttachContent = false + } else { + videoNode.canAttachContent = true + self.updateDisplayPlaceholder(!videoNode.ownsContentNode) + } self.scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in if let value = value, !value.duration.isZero { @@ -620,7 +626,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) - Queue.mainQueue().after(0.0001) { + Queue.mainQueue().after(0.001) { videoNode.canAttachContent = true } @@ -645,13 +651,14 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { return } - 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 transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view) + var 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) let transformedCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view) var positionCompleted = false - var boundsCompleted = false + var transformCompleted = false + var boundsCompleted = true var copyCompleted = false let copyView = node.1()! @@ -670,9 +677,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { copyView.frame = transformedSelfFrame let intermediateCompletion = { [weak copyView, weak surfaceCopyView] in - if positionCompleted && boundsCompleted && copyCompleted { + if positionCompleted && transformCompleted && boundsCompleted && copyCompleted { copyView?.removeFromSuperview() surfaceCopyView?.removeFromSuperview() + videoNode.canAttachContent = false + videoNode.removeFromSupernode() completion() } } @@ -693,11 +702,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) } - videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - positionCompleted = true - intermediateCompletion() - }) - self.statusButtonNode.layer.animatePosition(from: self.statusButtonNode.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in //positionCompleted = true //intermediateCompletion() @@ -705,28 +709,52 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false) - var animatedVideoNode = false + let initialScale = videoNode.layer.bounds.size.width / node.0.view.bounds.width + + let fromTransform: CATransform3D + let toTransform: CATransform3D + if let interactiveMediaNode = node.0 as? ChatMessageInteractiveMediaNode, interactiveMediaNode.automaticPlayback ?? false { - let scale = videoNode.layer.bounds.size.width / node.0.view.bounds.width + let targetScale = max(transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height) + videoNode.backgroundColor = .clear if let bubbleDecoration = interactiveMediaNode.videoNodeDecoration, let decoration = videoNode.decoration as? GalleryVideoDecoration { - decoration.updateCorners(bubbleDecoration.corners.scaledBy(scale * 0.6666)) + transformedSuperFrame = transformedSuperFrame.offsetBy(dx: bubbleDecoration.corners.extendedEdges.right / 2.0 - bubbleDecoration.corners.extendedEdges.left / 2.0, dy: 0.0) + if let item = self.item { + let newSize = item.content.dimensions.aspectFilled(bubbleDecoration.contentContainerNode.frame.size) + videoNode.updateLayout(size: newSize, transition: .immediate) + videoNode.bounds = CGRect(origin: CGPoint(), size: newSize) + + boundsCompleted = false + decoration.updateCorners(bubbleDecoration.corners) + decoration.updateClippingFrame(bubbleDecoration.contentContainerNode.bounds, completion: { + boundsCompleted = true + intermediateCompletion() + }) + } } - animatedVideoNode = true - } - if !animatedVideoNode { + let transformScale: CGFloat = initialScale * targetScale + fromTransform = CATransform3DScale(videoNode.layer.transform, initialScale, initialScale, 1.0) + toTransform = CATransform3DScale(videoNode.layer.transform, transformScale, transformScale, 1.0) + + } else { videoNode.allowsGroupOpacity = true videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in videoNode?.allowsGroupOpacity = false }) + + fromTransform = videoNode.layer.transform + toTransform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) } - transformedFrame.origin = CGPoint() + videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + positionCompleted = true + intermediateCompletion() + }) - let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) - videoNode.layer.animate(from: NSValue(caTransform3D: videoNode.layer.transform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in - boundsCompleted = true + videoNode.layer.animate(from: NSValue(caTransform3D: fromTransform), to: NSValue(caTransform3D: toTransform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + transformCompleted = true intermediateCompletion() }) diff --git a/TelegramUI/UniversalVideoNode.swift b/TelegramUI/UniversalVideoNode.swift index 17ed8dea6e..2ae76c10ed 100644 --- a/TelegramUI/UniversalVideoNode.swift +++ b/TelegramUI/UniversalVideoNode.swift @@ -203,6 +203,7 @@ final class UniversalVideoNode: ASDisplayNode { } } if let (contentNode, initiatedCreation) = contentNode { + contentNode.layer.removeAllAnimations() self._ready.set(contentNode.ready) if initiatedCreation && self.autoplay { self.play() diff --git a/TelegramUI/WallpaperUploadManager.swift b/TelegramUI/WallpaperUploadManager.swift index c82dcf122c..55e0cb7115 100644 --- a/TelegramUI/WallpaperUploadManager.swift +++ b/TelegramUI/WallpaperUploadManager.swift @@ -73,6 +73,10 @@ final class WallpaperUploadManager { })) } + deinit { + self.presentationDataDisposable.dispose() + } + func stateSignal() -> Signal { return self.statePromise.get() } @@ -95,7 +99,22 @@ final class WallpaperUploadManager { let sharedContext = self.sharedContext let account = self.account - disposable.set(uploadWallpaper(account: account, resource: currentResource, settings: currentWallpaper.settings ?? WallpaperSettings()).start(next: { [weak self] status in + + let uploadSignal = uploadWallpaper(account: account, resource: currentResource, settings: currentWallpaper.settings ?? WallpaperSettings()) + |> map { result -> UploadWallpaperStatus in + switch result { + case let .complete(wallpaper): + if case let .file(_, _, _, _, _, _, _, file, _) = wallpaper { + sharedContext.accountManager.mediaBox.moveResourceData(from: currentResource.id, to: file.resource.id) + account.postbox.mediaBox.moveResourceData(from: currentResource.id, to: file.resource.id) + } + default: + break + } + return result + } + + disposable.set(uploadSignal.start(next: { [weak self] status in guard let strongSelf = self else { return }