diff --git a/TelegramUI/AutodownloadConnectionTypeController.swift b/TelegramUI/AutodownloadConnectionTypeController.swift index af1a8134da..e70177de71 100644 --- a/TelegramUI/AutodownloadConnectionTypeController.swift +++ b/TelegramUI/AutodownloadConnectionTypeController.swift @@ -39,7 +39,7 @@ private enum AutodownloadMediaCategorySection: Int32 { private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry { case master(PresentationTheme, String, Bool) case dataUsageHeader(PresentationTheme, String) - case dataUsageItem(PresentationTheme, PresentationStrings, AutomaticDownloadDataUsage, Int?) + case dataUsageItem(PresentationTheme, PresentationStrings, AutomaticDownloadDataUsage, Int?, Bool) case typesHeader(PresentationTheme, String) case photos(PresentationTheme, String, String, Bool) case videos(PresentationTheme, String, String, Bool) @@ -92,8 +92,8 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry { } else { return false } - case let .dataUsageItem(lhsTheme, lhsStrings, lhsValue, lhsCustomPosition): - if case let .dataUsageItem(rhsTheme, rhsStrings, rhsValue, rhsCustomPosition) = rhs, lhsTheme === rhsTheme, lhsStrings == rhsStrings, lhsValue == rhsValue, lhsCustomPosition == rhsCustomPosition { + case let .dataUsageItem(lhsTheme, lhsStrings, lhsValue, lhsCustomPosition, lhsEnabled): + if case let .dataUsageItem(rhsTheme, rhsStrings, rhsValue, rhsCustomPosition, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsStrings == rhsStrings, lhsValue == rhsValue, lhsCustomPosition == rhsCustomPosition, lhsEnabled == rhsEnabled { return true } else { return false @@ -143,8 +143,8 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry { }) case let .dataUsageHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) - case let .dataUsageItem(theme, strings, value, customPosition): - return AutodownloadDataUsagePickerItem(theme: theme, strings: strings, value: value, customPosition: customPosition, sectionId: self.section, updated: { preset in + case let .dataUsageItem(theme, strings, value, customPosition, enabled): + return AutodownloadDataUsagePickerItem(theme: theme, strings: strings, value: value, customPosition: customPosition, enabled: enabled, sectionId: self.section, updated: { preset in arguments.changePreset(preset) }) case let .typesHeader(theme, text): @@ -258,7 +258,7 @@ private func autodownloadMediaConnectionTypeControllerEntries(presentationData: customPosition = sortedPresets.firstIndex(of: custom) ?? 0 } - entries.append(.dataUsageItem(presentationData.theme, presentationData.strings, AutomaticDownloadDataUsage(preset: connection.preset), customPosition)) + entries.append(.dataUsageItem(presentationData.theme, presentationData.strings, AutomaticDownloadDataUsage(preset: connection.preset), customPosition, master)) entries.append(.typesHeader(presentationData.theme, presentationData.strings.AutoDownloadSettings_MediaTypes)) entries.append(.photos(presentationData.theme, presentationData.strings.AutoDownloadSettings_Photos, stringForAutomaticDownloadPeers(strings: presentationData.strings, peers: photo, category: .photo), master)) diff --git a/TelegramUI/AutodownloadDataUsagePickerItem.swift b/TelegramUI/AutodownloadDataUsagePickerItem.swift index 28459c734d..52193ec0f3 100644 --- a/TelegramUI/AutodownloadDataUsagePickerItem.swift +++ b/TelegramUI/AutodownloadDataUsagePickerItem.swift @@ -31,14 +31,16 @@ class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem { let strings: PresentationStrings let value: AutomaticDownloadDataUsage let customPosition: Int? + let enabled: Bool let sectionId: ItemListSectionId let updated: (AutomaticDownloadDataUsage) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, value: AutomaticDownloadDataUsage, customPosition: Int?, sectionId: ItemListSectionId, updated: @escaping (AutomaticDownloadDataUsage) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, value: AutomaticDownloadDataUsage, customPosition: Int?, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (AutomaticDownloadDataUsage) -> Void) { self.theme = theme self.strings = strings self.value = value self.customPosition = customPosition + self.enabled = enabled self.sectionId = sectionId self.updated = updated } @@ -297,6 +299,10 @@ class AutodownloadDataUsagePickerItemNode: ListViewItemNode { sliderView.knobImage = generateKnobImage() } + sliderView.isUserInteractionEnabled = item.enabled + sliderView.alpha = item.enabled ? 1.0 : 0.4 + sliderView.layer.allowsGroupOpacity = !item.enabled + sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)) sliderView.hitTestEdgeInsets = UIEdgeInsetsMake(-sliderView.frame.minX, 0.0, 0.0, -sliderView.frame.minX) diff --git a/TelegramUI/AutodownloadMediaCategoryController.swift b/TelegramUI/AutodownloadMediaCategoryController.swift index fc7254e88c..a8ef418dc7 100644 --- a/TelegramUI/AutodownloadMediaCategoryController.swift +++ b/TelegramUI/AutodownloadMediaCategoryController.swift @@ -271,7 +271,7 @@ private func autodownloadMediaCategoryControllerEntries(presentationData: Presen let text = presentationData.strings.AutoDownloadSettings_UpTo(sizeText).0 entries.append(.sizeItem(presentationData.theme, text, size)) if category == .video { - entries.append(.sizePreload(presentationData.theme, presentationData.strings.AutoDownloadSettings_PreloadVideo, predownload, size > 3 * 1024 * 1024)) + entries.append(.sizePreload(presentationData.theme, presentationData.strings.AutoDownloadSettings_PreloadVideo, predownload, size > 2 * 1024 * 1024)) entries.append(.sizePreloadInfo(presentationData.theme, presentationData.strings.AutoDownloadSettings_PreloadVideoInfo(sizeText).0)) } default: 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 db4a1f8c4e..52598b32af 100644 --- a/TelegramUI/ChatBubbleVideoDecoration.swift +++ b/TelegramUI/ChatBubbleVideoDecoration.swift @@ -3,29 +3,21 @@ import AsyncDisplayKit import Display import SwiftSignalKit -private func isRoundEqualCorners(_ corners: ImageCorners) -> Bool { - if case .Corner = corners.topLeft, case .Corner = corners.topRight, case .Corner = corners.bottomLeft, case .Corner = corners.bottomRight { - if corners.topLeft.radius == corners.topRight.radius && corners.topRight.radius == corners.bottomLeft.radius && corners.bottomLeft.radius == corners.bottomRight.radius { - return true - } - } - return false -} - final class ChatBubbleVideoDecoration: UniversalVideoDecoration { private let nativeSize: CGSize private let contentMode: InteractiveMediaNodeContentMode + let corners: ImageCorners let backgroundNode: ASDisplayNode? = nil let contentContainerNode: ASDisplayNode let foregroundNode: ASDisplayNode? = nil private var contentNode: (ASDisplayNode & UniversalVideoContentNode)? - private var contentNodeSnapshot: UIView? private var validLayoutSize: CGSize? init(corners: ImageCorners, nativeSize: CGSize, contentMode: InteractiveMediaNodeContentMode, backgroundColor: UIColor) { + self.corners = corners self.nativeSize = nativeSize self.contentMode = contentMode @@ -33,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()) @@ -45,7 +44,7 @@ final class ChatBubbleVideoDecoration: UniversalVideoDecoration { ctx.fill(arguments.drawingRect) } addCorners(context, arguments: arguments) - + if let maskImage = context.generateImage() { let mask = CALayer() mask.contents = maskImage.cgImage @@ -53,6 +52,7 @@ final class ChatBubbleVideoDecoration: UniversalVideoDecoration { 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 } } } @@ -95,33 +95,6 @@ final class ChatBubbleVideoDecoration: UniversalVideoDecoration { } func updateContentNodeSnapshot(_ snapshot: UIView?) { - if self.contentNodeSnapshot !== snapshot { - self.contentNodeSnapshot?.removeFromSuperview() - self.contentNodeSnapshot = snapshot - - if let snapshot = snapshot { - self.contentContainerNode.view.addSubview(snapshot) - if let size = self.validLayoutSize { - var scaledSize: CGSize - switch self.contentMode { - case .aspectFit: - scaledSize = snapshot.frame.size.aspectFitted(size) - case .aspectFill: - scaledSize = snapshot.frame.size.aspectFilled(size) - } - if abs(scaledSize.width - size.width) < 2.0 { - scaledSize.width = size.width - } - if abs(scaledSize.height - size.height) < 2.0 { - scaledSize.height = size.height - } - - let scale = scaledSize.width / snapshot.frame.width - snapshot.transform = CGAffineTransform(scaleX: scale, y: scale) - snapshot.center = CGPoint(x: size.width / 2.0, y: size.height / 2.0) - } - } - } } func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index edcc1bbcab..ae7e9acc3b 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/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 281bac4289..68288d9591 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -1003,4 +1003,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } } + + func playMediaWithSound() -> (() -> Void)? { + return self.contentImageNode?.playMediaWithSound() + } } diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index c97eba1965..4cbb427bf0 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -445,7 +445,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 77439d2941..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 { @@ -36,6 +37,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { private var currentImageArguments: TransformImageArguments? private var videoNode: UniversalVideoNode? private var statusNode: RadialStatusNode? + var videoNodeDecoration: ChatBubbleVideoDecoration? private var badgeNode: ChatMessageInteractiveMediaBadge? private var tapRecognizer: UITapGestureRecognizer? @@ -45,7 +47,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { private var themeAndStrings: (PresentationTheme, PresentationStrings)? private var sizeCalculation: InteractiveMediaNodeSizeCalculation? private var automaticDownload: InteractiveMediaNodeAutodownloadMode? - private var automaticPlayback: Bool? + var automaticPlayback: Bool? private let statusDisposable = MetaDisposable() private let fetchControls = Atomic(value: nil) @@ -54,7 +56,20 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { private let fetchDisposable = MetaDisposable() private let playerStatusDisposable = MetaDisposable() - private var playerStatus: MediaPlayerStatus? + + private var updateTimer: SwiftSignalKit.Timer? + private var playerStatus: MediaPlayerStatus? { + didSet { + if self.playerStatus != oldValue { + if let playerStatus = playerStatus, case .playing = playerStatus.status { + self.ensureHasTimer() + } else { + self.stopTimer() + } + self.updateFetchStatus() + } + } + } private var secretTimer: SwiftSignalKit.Timer? @@ -105,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 { @@ -151,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) { @@ -213,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)) @@ -396,7 +416,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } } - if file.isVideo && !isSecretMedia && automaticPlayback && !message.flags.isSending { + var uploading = false + if file.resource is VideoLibraryMediaResource { + uploading = true + } + + if file.isVideo && !isSecretMedia && automaticPlayback && !uploading { updateVideoFile = file if hasCurrentVideoNode { if let currentFile = currentMedia as? TelegramMediaFile, currentFile.resource is EmptyMediaResource { @@ -540,8 +565,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } if replaceVideoNode, let updatedVideoFile = updateVideoFile { + 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: ChatBubbleVideoDecoration(corners: arguments.corners, nativeSize: nativeSize, contentMode: contentMode, backgroundColor: arguments.emptyColor ?? .black), content: NativeVideoContent(id: .message(message.id, message.stableId, updatedVideoFile.fileId), fileReference: .message(message: MessageReference(message), media: updatedVideoFile), streamVideo: true, 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 @@ -552,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 @@ -595,7 +628,6 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { displayLinkDispatcher.dispatch { if let strongSelf = strongSelf { strongSelf.playerStatus = status - strongSelf.updateFetchStatus() } } })) @@ -630,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) @@ -674,8 +706,23 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } } + private func ensureHasTimer() { + if self.updateTimer == nil { + let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in + self?.updateFetchStatus() + }, queue: Queue.mainQueue()) + self.updateTimer = timer + timer.start() + } + } + + private func stopTimer() { + self.updateTimer?.invalidate() + self.updateTimer = nil + } + 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 } @@ -692,6 +739,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { break } } + automaticPlayback = false } var webpage: TelegramMediaWebpage? @@ -789,7 +837,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { var active = false var muted = automaticPlayback if let playerStatus = self.playerStatus { - playerPosition = Int32(playerStatus.timestamp) + if !playerStatus.generationTimestamp.isZero, case .playing = playerStatus.status { + playerPosition = Int32(playerStatus.timestamp + (CACurrentMediaTime() - playerStatus.generationTimestamp)) + } else { + playerPosition = Int32(playerStatus.timestamp) + } playerDuration = Int32(playerStatus.duration) if case .buffering = playerStatus.status { active = true @@ -798,7 +850,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { muted = false } } else if case .Fetching = fetchStatus, !message.flags.contains(.Unsent) { - active = automaticPlayback + active = true + } + + if message.flags.contains(.Unsent) { + automaticPlayback = false } if let actualFetchStatus = self.actualFetchStatus, automaticPlayback { @@ -818,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) { @@ -916,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 7c9c313f8f..66c4c6bf32 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/ChatMessageWebpageBubbleContentNode.swift b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index f6d252540d..1a7885c8c6 100644 --- a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -353,6 +353,10 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } + override func playMediaWithSound() -> (() -> Void)? { + return self.contentNode.playMediaWithSound() + } + override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction { if self.bounds.contains(point) { let contentNodeFrame = self.contentNode.frame diff --git a/TelegramUI/GalleryController.swift b/TelegramUI/GalleryController.swift index 707598a082..6ac8da8559 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: NativeVideoContentId.message(message.id, message.stableId, 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) } } } @@ -290,6 +290,7 @@ class GalleryController: ViewController { private var adjustedForInitialPreviewingLayout = false 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 4e9488932f..57a2b62d4e 100644 --- a/TelegramUI/GalleryVideoDecoration.swift +++ b/TelegramUI/GalleryVideoDecoration.swift @@ -39,19 +39,69 @@ final class GalleryVideoDecoration: UniversalVideoDecoration { } } + func updateCorners(_ corners: ImageCorners) { + self.contentContainerNode.clipsToBounds = true + if isRoundEqualCorners(corners) { + self.contentContainerNode.cornerRadius = corners.topLeft.radius + } else { + 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()) + let context = DrawingContext(size: size, clear: true) + context.withContext { ctx in + ctx.setFillColor(UIColor.black.cgColor) + 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 + } + } + } + + 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?) { } func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { self.validLayoutSize = size + let bounds = CGRect(origin: CGPoint(), size: size) if let backgroundNode = self.backgroundNode { - transition.updateFrame(node: backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: backgroundNode, frame: bounds) } if let foregroundNode = self.foregroundNode { - transition.updateFrame(node: foregroundNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: foregroundNode, frame: bounds) + } + transition.updateFrame(node: self.contentContainerNode, frame: bounds) + if let maskLayer = self.contentContainerNode.layer.mask { + transition.updateFrame(layer: maskLayer, frame: bounds) } - transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: CGPoint(), size: size)) if let contentNode = self.contentNode { transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(), size: size)) contentNode.updateLayout(size: size, transition: transition) diff --git a/TelegramUI/ImageNode.swift b/TelegramUI/ImageNode.swift index 532fc94fa7..00cf8e2d3f 100644 --- a/TelegramUI/ImageNode.swift +++ b/TelegramUI/ImageNode.swift @@ -36,6 +36,15 @@ public enum ImageCorner: Equatable { return radius } } + + public func scaledBy(_ scale: CGFloat) -> ImageCorner { + switch self { + case let .Corner(radius): + return .Corner(radius * scale) + case let .Tail(radius, enabled): + return .Tail(radius * scale, enabled) + } + } } public func ==(lhs: ImageCorner, rhs: ImageCorner) -> Bool { @@ -56,6 +65,15 @@ public func ==(lhs: ImageCorner, rhs: ImageCorner) -> Bool { } } +func isRoundEqualCorners(_ corners: ImageCorners) -> Bool { + if case .Corner = corners.topLeft, case .Corner = corners.topRight, case .Corner = corners.bottomLeft, case .Corner = corners.bottomRight { + if corners.topLeft.radius == corners.topRight.radius && corners.topRight.radius == corners.bottomLeft.radius && corners.bottomLeft.radius == corners.bottomRight.radius { + return true + } + } + return false +} + public struct ImageCorners: Equatable { public let topLeft: ImageCorner public let topRight: ImageCorner @@ -106,6 +124,10 @@ public struct ImageCorners: Equatable { public func withRemovedTails() -> ImageCorners { return ImageCorners(topLeft: self.topLeft.withoutTail, topRight: self.topRight.withoutTail, bottomLeft: self.bottomLeft.withoutTail, bottomRight: self.bottomRight.withoutTail) } + + public func scaledBy(_ scale: CGFloat) -> ImageCorners { + return ImageCorners(topLeft: self.topLeft.scaledBy(scale), topRight: self.topRight.scaledBy(scale), bottomLeft: self.bottomLeft.scaledBy(scale), bottomRight: self.bottomRight.scaledBy(scale)) + } } public func ==(lhs: ImageCorners, rhs: ImageCorners) -> Bool { 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 8766df4246..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 @@ -151,6 +153,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private let statusNode: RadialStatusNode private var isCentral = false + private var initiallyActivated = false private var validLayout: (ContainerViewLayout, CGFloat)? private var didPause = false private var isPaused = true @@ -308,8 +311,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.videoNode = videoNode videoNode.isUserInteractionEnabled = disablePlayerControls videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335) - videoNode.canAttachContent = true - 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 { @@ -513,6 +520,16 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let videoNode = self.videoNode { if isCentral { + if let fetchStatus = self.fetchStatus, let item = self.item, let content = item.content as? NativeVideoContent, let contentInfo = item.contentInfo, case let .message(message) = contentInfo, !self.initiallyActivated { + self.initiallyActivated = true + var isLocal = false + if case .Local = fetchStatus { + isLocal = true + } + if isLocal || isMediaStreamable(message: message, media: content.fileReference.media) { + videoNode.play() + } + } //videoNode.canAttachContent = true } else if videoNode.ownsContentNode { videoNode.pause() @@ -522,15 +539,17 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } override func activateAsInitial() { - if self.isCentral { + if let videoNode = self.videoNode, self.isCentral { + self.initiallyActivated = true + var isAnimated = false if let item = self.item, let content = item.content as? NativeVideoContent { isAnimated = content.fileReference.media.isAnimated } if isAnimated { - self.videoNode?.play() + videoNode.play() } else { - self.videoNode?.playOnceWithSound(playAndRecord: false, seekToStart: .automatic, actionAtEnd: .stop) + videoNode.playOnceWithSound(playAndRecord: false, seekToStart: .automatic, actionAtEnd: .stop) } } } @@ -604,8 +623,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { transformedFrame.origin = CGPoint() 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: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) + Queue.mainQueue().after(0.001) { + videoNode.canAttachContent = true + } + if let pictureInPictureNode = self.pictureInPictureNode { let transformedPlaceholderFrame = node.0.view.convert(node.0.view.bounds, to: pictureInPictureNode.view) let transform = CATransform3DScale(pictureInPictureNode.layer.transform, transformedPlaceholderFrame.size.width / pictureInPictureNode.layer.bounds.size.width, transformedPlaceholderFrame.size.height / pictureInPictureNode.layer.bounds.size.height, 1.0) @@ -627,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()! @@ -652,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() } } @@ -675,16 +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() - }) - - videoNode.allowsGroupOpacity = true - videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in - videoNode?.allowsGroupOpacity = false - }) - 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() @@ -692,11 +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) - transformedFrame.origin = CGPoint() + let initialScale = videoNode.layer.bounds.size.width / node.0.view.bounds.width - 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 + let fromTransform: CATransform3D + let toTransform: CATransform3D + + if let interactiveMediaNode = node.0 as? ChatMessageInteractiveMediaNode, interactiveMediaNode.automaticPlayback ?? false { + 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 { + 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() + }) + } + } + + 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) + } + + 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() + }) + + 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 74cd4b73da..2ae76c10ed 100644 --- a/TelegramUI/UniversalVideoNode.swift +++ b/TelegramUI/UniversalVideoNode.swift @@ -81,7 +81,7 @@ final class UniversalVideoNode: ASDisplayNode { private let manager: UniversalVideoContentManager private let content: UniversalVideoContent private let priority: UniversalVideoPriority - private let decoration: UniversalVideoDecoration + let decoration: UniversalVideoDecoration private let autoplay: Bool private let snapshotContentWhenGone: Bool @@ -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 }