diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 25770b83a9..7b396c5bc7 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10863,3 +10863,7 @@ Sorry for the inconvenience."; "Premium.MaxSavedPinsText" = "Sorry, you can't pin more than **%1$@** chats to the top. Unpin some that are currently pinned or subscribe to **Telegram Premium** to double the limit to **%2$@** chats."; "Premium.MaxSavedPinsNoPremiumText" = "Sorry, you can't pin more than **%@** chats to the top. Unpin some that are currently pinned."; "Premium.MaxSavedPinsFinalText" = "Sorry, you can't pin more than **%@** chats to the top. Unpin some that are currently pinned."; + +"Chat.PlayOnceMesasgeClose" = "Close"; +"Chat.PlayOnceMesasgeCloseAndDelete" = "Close and Delete"; +"Chat.PlayOnceMesasge.DisableScreenCapture" = "Sorry, you can't play this message while screen recording is active."; diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 0e4d17c88d..ccd54f740e 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -216,11 +216,12 @@ public func galleryItemForEntry( } else if let file = media as? TelegramMediaFile { if file.isVideo { let content: UniversalVideoContent + let captureProtected = message.isCopyProtected() || message.containsSecretMedia || message.minAutoremoveOrClearTimeout == viewOnceTimeout if file.isAnimated { - content = NativeVideoContent(id: .message(message.stableId, file.fileId), userLocation: .peer(message.id.peerId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), loopVideo: true, enableSound: false, tempFilePath: tempFilePath, captureProtected: message.isCopyProtected() || message.containsSecretMedia, storeAfterDownload: generateStoreAfterDownload?(message, file)) + content = NativeVideoContent(id: .message(message.stableId, file.fileId), userLocation: .peer(message.id.peerId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), loopVideo: true, enableSound: false, tempFilePath: tempFilePath, captureProtected: captureProtected, storeAfterDownload: generateStoreAfterDownload?(message, file)) } else { if true || (file.mimeType == "video/mpeg4" || file.mimeType == "video/mov" || file.mimeType == "video/mp4") { - content = NativeVideoContent(id: .message(message.stableId, file.fileId), userLocation: .peer(message.id.peerId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath, captureProtected: message.isCopyProtected() || message.containsSecretMedia, storeAfterDownload: generateStoreAfterDownload?(message, file)) + content = NativeVideoContent(id: .message(message.stableId, file.fileId), userLocation: .peer(message.id.peerId), fileReference: .message(message: MessageReference(message), media: file), imageReference: mediaImage.flatMap({ ImageMediaReference.message(message: MessageReference(message), media: $0) }), streamVideo: .conservative, loopVideo: loopVideos, tempFilePath: tempFilePath, captureProtected: captureProtected, storeAfterDownload: generateStoreAfterDownload?(message, file)) } else { content = PlatformVideoContent(id: .message(message.id, message.stableId, file.fileId), userLocation: .peer(message.id.peerId), content: .file(.message(message: MessageReference(message), media: file)), streamVideo: streamVideos, loopVideo: loopVideos) } diff --git a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift index 775de8c031..911712c505 100644 --- a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift +++ b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift @@ -525,7 +525,7 @@ public final class SecretMediaPreviewController: ViewController { } } - guard let item = galleryItemForEntry(context: self.context, presentationData: self.presentationData, entry: MessageHistoryEntry(message: message, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)), streamVideos: false, hideControls: true, isSecret: true, playbackRate: { nil }, tempFilePath: tempFilePath, playbackCompleted: { [weak self] in + guard let item = galleryItemForEntry(context: self.context, presentationData: self.presentationData, entry: MessageHistoryEntry(message: message, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)), streamVideos: false, hideControls: true, isSecret: true, playbackRate: { nil }, peerIsCopyProtected: true, tempFilePath: tempFilePath, playbackCompleted: { [weak self] in if let self { if self.currentNodeMessageIsViewOnce || (duration < 30.0 && !self.currentMessageIsDismissed) { if let node = self.controllerNode.pager.centralItemNode() as? UniversalVideoGalleryItemNode { diff --git a/submodules/ScreenCaptureDetection/Sources/ScreenCaptureDetection.swift b/submodules/ScreenCaptureDetection/Sources/ScreenCaptureDetection.swift index 3da80a4947..f0d89f73f6 100644 --- a/submodules/ScreenCaptureDetection/Sources/ScreenCaptureDetection.swift +++ b/submodules/ScreenCaptureDetection/Sources/ScreenCaptureDetection.swift @@ -81,6 +81,8 @@ public final class ScreenCaptureDetectionManager { private var screenRecordingDisposable: Disposable? private var screenRecordingCheckTimer: SwiftSignalKit.Timer? + public var isRecordingActive = false + public init(check: @escaping () -> Bool) { self.observer = NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: .main, using: { [weak self] _ in guard let _ = self else { @@ -94,6 +96,7 @@ public final class ScreenCaptureDetectionManager { guard let strongSelf = self else { return } + strongSelf.isRecordingActive = value if value { if strongSelf.screenRecordingCheckTimer == nil { strongSelf.screenRecordingCheckTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { diff --git a/submodules/TelegramUI/Components/AudioWaveformComponent/Sources/AudioWaveformComponent.swift b/submodules/TelegramUI/Components/AudioWaveformComponent/Sources/AudioWaveformComponent.swift index 03ea1c9261..2eb94f5e3f 100644 --- a/submodules/TelegramUI/Components/AudioWaveformComponent/Sources/AudioWaveformComponent.swift +++ b/submodules/TelegramUI/Components/AudioWaveformComponent/Sources/AudioWaveformComponent.swift @@ -212,6 +212,7 @@ public final class AudioWaveformComponent: Component { private var sparksView: SparksView? private var progress: CGFloat = 0.0 + private var lastHeight: CGFloat = 0.0 private var revealProgress: CGFloat = 1.0 private var animator: DisplayLinkAnimator? @@ -409,7 +410,7 @@ public final class AudioWaveformComponent: Component { self.addSubview(sparksView) self.sparksView = sparksView } - sparksView.frame = CGRect(origin: .zero, size: size).insetBy(dx: -5.0, dy: -5.0) + sparksView.frame = CGRect(origin: .zero, size: size).insetBy(dx: -10.0, dy: -15.0) } else if let sparksView = self.sparksView { self.sparksView = nil sparksView.removeFromSuperview() @@ -433,7 +434,7 @@ public final class AudioWaveformComponent: Component { if needsAnimation { self.playbackStatusAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in if let self, let component = self.component, let sparksView = self.sparksView { - sparksView.update(position: CGPoint(x: sparksView.bounds.width * self.progress, y: sparksView.bounds.height / 2.0), color: component.foregroundColor) + sparksView.update(position: CGPoint(x: 10.0 + (sparksView.bounds.width - 20.0) * self.progress, y: sparksView.bounds.height / 2.0 + 8.0), sampleHeight: self.lastHeight, color: component.foregroundColor) } self?.setNeedsDisplay() }) @@ -574,6 +575,7 @@ public final class AudioWaveformComponent: Component { let commonRevealFraction = listViewAnimationCurveSystem(self.revealProgress) + var lastHeight: CGFloat = 0.0 for i in 0 ..< numSamples { let offset = CGFloat(i) * (sampleWidth + distance) let peakSample = adjustedSamples[i] @@ -596,6 +598,7 @@ public final class AudioWaveformComponent: Component { let colorMixFraction: CGFloat if startFraction < playbackProgress { colorMixFraction = max(0.0, min(1.0, (playbackProgress - startFraction) / (nextStartFraction - startFraction))) + lastHeight = sampleHeight } else { colorMixFraction = 0.0 } @@ -637,6 +640,8 @@ public final class AudioWaveformComponent: Component { context.fill(adjustedRect) } } + + self.lastHeight = lastHeight } } } @@ -683,12 +688,18 @@ private class SparksView: UIView { fatalError("init(coder:) has not been implemented") } - func update(position: CGPoint, color: UIColor) { + private var presentationSampleHeight: CGFloat = 0.0 + private var sampleHeight: CGFloat = 0.0 + + func update(position: CGPoint, sampleHeight: CGFloat, color: UIColor) { self.color = color + + self.sampleHeight = sampleHeight + self.presentationSampleHeight = self.presentationSampleHeight * 0.9 + self.sampleHeight * 0.1 let v = CGPoint(x: 1.0, y: 0.0) - let c = CGPoint(x: position.x - 3.0, y: position.y - 5.5 + 13.0 * CGFloat(arc4random_uniform(100)) / 100.0 + 1.0) - + let c = CGPoint(x: position.x - 4.0, y: position.y + 1.0 - self.presentationSampleHeight * CGFloat(arc4random_uniform(100)) / 100.0) + let timestamp = CACurrentMediaTime() let dt: CGFloat = 1.0 / 60.0 @@ -714,9 +725,9 @@ private class SparksView: UIView { self.particles.remove(at: i) } - let newParticleCount = 2 + let newParticleCount = 3 for _ in 0 ..< newParticleCount { - let degrees: CGFloat = CGFloat(arc4random_uniform(100)) - 50.0 + let degrees: CGFloat = CGFloat(arc4random_uniform(100)) - 65.0 let angle: CGFloat = degrees * CGFloat.pi / 180.0 let direction = CGPoint(x: v.x * cos(angle) - v.y * sin(angle), y: v.x * sin(angle) + v.y * cos(angle)) @@ -739,7 +750,7 @@ private class SparksView: UIView { context.setFillColor(self.color.cgColor) for particle in self.particles { - let size: CGFloat = 1.0 + let size: CGFloat = 1.4 context.setAlpha(particle.alpha * 1.0) context.fillEllipse(in: CGRect(origin: CGPoint(x: particle.position.x - size / 2.0, y: particle.position.y - size / 2.0), size: CGSize(width: size, height: size))) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index 6b2f1bff0b..a851caebff 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -1656,6 +1656,9 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { if isViewOnceMessage && playbackStatus == .playing { state = .secretTimeout(position: playbackState.position, duration: playbackState.duration, generationTimestamp: playbackState.generationTimestamp, appearance: .init(inset: 1.0 + UIScreenPixel, lineWidth: 2.0 - UIScreenPixel)) + if incoming { + self.consumableContentNode.isHidden = true + } } else { switch playbackStatus { case .playing: @@ -1783,8 +1786,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { self.addSubnode(streamingStatusNode) if isViewOnceMessage { - streamingStatusNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) - streamingStatusNode.layer.animateAlpha(from: 0.1, to: 1.0, duration: 0.2) + streamingStatusNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue) + streamingStatusNode.layer.animateAlpha(from: 0.1, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue) } } else if let streamingStatusNode = self.streamingStatusNode { streamingStatusNode.backgroundNodeColor = backgroundNodeColor @@ -1815,9 +1818,9 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { if streamingState == .none { self.streamingStatusNode = nil if isViewOnceMessage { - streamingStatusNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false) + streamingStatusNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false) } - streamingStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak streamingStatusNode] _ in + streamingStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak streamingStatusNode] _ in if streamingState == .none { streamingStatusNode?.removeFromSupernode() } diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 1db5fab7f0..12bfa12d89 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -417,7 +417,9 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch if message.id.peerId.namespace == Namespaces.Peer.SecretChat { canReplyInAnotherChat = false } - + if message.minAutoremoveOrClearTimeout == viewOnceTimeout { + canReplyInAnotherChat = false + } } if canReplyInAnotherChat { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 8ce6551f02..384b326944 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -11490,7 +11490,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if !strongSelf.traceVisibility() { return false } - + if strongSelf.currentContextController != nil { + return false + } if !isTopmostChatController(strongSelf) { return false } @@ -11588,6 +11590,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } }) + } else if peerId.namespace == Namespaces.Peer.CloudUser { + self.screenCaptureManager = ScreenCaptureDetectionManager(check: { [weak self] in + guard let self else { + return false + } + + let _ = (self.context.sharedContext.mediaManager.globalMediaPlayerState + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] playlistStateAndType in + if let self, let (_, playbackState, _) = playlistStateAndType, case let .state(state) = playbackState { + if let source = state.item.playbackData?.source, case let .telegramFile(_, _, isViewOnce) = source, isViewOnce { + self.context.sharedContext.mediaManager.setPlaylist(nil, type: .voice, control: .playback(.pause)) + } + } + }) + return true + }) } } @@ -18952,6 +18971,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func openViewOnceMediaMessage(_ message: Message) { + if self.screenCaptureManager?.isRecordingActive == true { + let controller = textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_PlayOnceMesasge_DisableScreenCapture, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { + })]) + self.present(controller, in: .window(.root)) + return + } + let isIncoming = message.effectivelyIncoming(self.context.account.peerId) var presentImpl: ((ViewController) -> Void)? @@ -18972,7 +18998,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } )), items: .single(ContextController.Items(content: .list([]))), - closeActionTitle: isIncoming ? "Delete and Close" : "Close", + closeActionTitle: isIncoming ? self.presentationData.strings.Chat_PlayOnceMesasgeCloseAndDelete : self.presentationData.strings.Chat_PlayOnceMesasgeClose, closeAction: { [weak self] in if let self { self.context.sharedContext.mediaManager.setPlaylist(nil, type: .voice, control: .playback(.pause)) diff --git a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift index a1103af4b4..61fa9d1913 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift @@ -121,6 +121,8 @@ final class ChatViewOnceMessageContextExtractedContentSource: ContextExtractedCo private var messageNodeCopy: ChatMessageItemView? private weak var tooltipController: TooltipScreen? + private let idleTimerExtensionDisposable = MetaDisposable() + var shouldBeDismissed: Signal { return self.context.sharedContext.mediaManager.globalMediaPlayerState |> filter { playlistStateAndType in @@ -156,11 +158,17 @@ final class ChatViewOnceMessageContextExtractedContentSource: ContextExtractedCo self.present = present } + deinit { + self.idleTimerExtensionDisposable.dispose() + } + func takeView() -> ContextControllerTakeViewInfo? { guard let chatNode = self.chatNode, let backgroundNode = self.backgroundNode, let validLayout = chatNode.validLayout?.0 else { return nil } + self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension()) + var result: ContextControllerTakeViewInfo? var sourceNode: ContextExtractedContentContainingNode? var sourceRect: CGRect = .zero @@ -309,6 +317,8 @@ final class ChatViewOnceMessageContextExtractedContentSource: ContextExtractedCo return nil } + self.idleTimerExtensionDisposable.set(nil) + if let tooltipController = self.tooltipController { tooltipController.dismiss() } diff --git a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift index ffb98a1d50..f269156706 100644 --- a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -515,26 +515,21 @@ private final class PlayPauseIconNode: ManagedAnimationNode { final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode { - private let backgroundNode: NavigationBackgroundNode - private let borderNode: ASImageNode + private let backgroundNode: ASImageNode private let iconNode: ASImageNode private var theme: PresentationTheme? override init(pointerStyle: PointerStyle? = nil) { - self.backgroundNode = NavigationBackgroundNode(color: .clear) + self.backgroundNode = ASImageNode() self.backgroundNode.isUserInteractionEnabled = false - self.borderNode = ASImageNode() - self.borderNode.isUserInteractionEnabled = false - self.iconNode = ASImageNode() self.iconNode.isUserInteractionEnabled = false super.init(pointerStyle: pointerStyle) self.addSubnode(self.backgroundNode) - self.addSubnode(self.borderNode) self.addSubnode(self.iconNode) self.highligthedChanged = { [weak self] highlighted in @@ -592,19 +587,13 @@ final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode { if self.theme !== theme { self.theme = theme - self.backgroundNode.updateColor(color: theme.chat.inputPanel.panelBackgroundColor, transition: .immediate) - - self.borderNode.image = generateCircleImage(diameter: innerSize.width, lineWidth: 0.5, color: theme.chat.historyNavigation.strokeColor, backgroundColor: nil) + self.backgroundNode.image = generateFilledCircleImage(diameter: innerSize.width, color: theme.rootController.navigationBar.opaqueBackgroundColor, strokeColor: theme.chat.inputPanel.panelSeparatorColor, strokeWidth: 0.5, backgroundColor: nil) self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: self.innerIsSelected ? "Media Gallery/ViewOnceEnabled" : "Media Gallery/ViewOnce"), color: theme.chat.inputPanel.panelControlAccentColor) } - - let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - innerSize.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - innerSize.height / 2.0)), size: innerSize) - self.backgroundNode.update(size: innerSize, cornerRadius: innerSize.width / 2.0, transition: .immediate, beginWithCurrentState: false) - self.backgroundNode.frame = backgroundFrame - - if let borderImage = self.borderNode.image { - let borderFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - borderImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - borderImage.size.height / 2.0)), size: borderImage.size) - self.borderNode.frame = borderFrame + + if let backgroundImage = self.backgroundNode.image { + let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - backgroundImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - backgroundImage.size.height / 2.0)), size: backgroundImage.size) + self.backgroundNode.frame = backgroundFrame } if let iconImage = self.iconNode.image { diff --git a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift index 89386c20b7..2dc30772ef 100644 --- a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift @@ -173,19 +173,27 @@ private func aroundMessagesFromMessages(_ messages: [Message], centralIndex: Mes } private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: MessageIndex) -> [Message] { - guard let index = view.entries.firstIndex(where: { $0.index.id == centralIndex.id }) else { + let filteredEntries = view.entries.filter { entry in + if entry.message.minAutoremoveOrClearTimeout == viewOnceTimeout { + return false + } else { + return true + } + } + + guard let index = filteredEntries.firstIndex(where: { $0.index.id == centralIndex.id }) else { return [] } var result: [Message] = [] if index != 0 { for i in (0 ..< index).reversed() { - result.append(view.entries[i].message) + result.append(filteredEntries[i].message) break } } - if index != view.entries.count - 1 { - for i in index + 1 ..< view.entries.count { - result.append(view.entries[i].message) + if index != filteredEntries.count - 1 { + for i in index + 1 ..< filteredEntries.count { + result.append(filteredEntries[i].message) break } } @@ -234,7 +242,15 @@ private func navigatedMessageFromMessages(_ messages: [Message], anchorIndex: Me private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition, reversed: Bool) -> (message: Message, around: [Message], exact: Bool)? { var index = 0 - for entry in view.entries { + let filteredEntries = view.entries.filter { entry in + if entry.message.minAutoremoveOrClearTimeout == viewOnceTimeout { + return false + } else { + return true + } + } + + for entry in filteredEntries { if entry.index.id == anchorIndex.id { let currentGroupKey = entry.message.groupingKey @@ -243,63 +259,63 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M return (entry.message, aroundMessagesFromView(view: view, centralIndex: entry.index), true) case .later: if !reversed, let currentGroupKey { - if index - 1 > 0, view.entries[index - 1].message.groupingKey == currentGroupKey { - let message = view.entries[index - 1].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[index - 1].index), true) + if index - 1 > 0, filteredEntries[index - 1].message.groupingKey == currentGroupKey { + let message = filteredEntries[index - 1].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[index - 1].index), true) } else { - for i in index ..< view.entries.count { - if view.entries[i].message.groupingKey != currentGroupKey { - let message = view.entries[i].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[i].index), true) + for i in index ..< filteredEntries.count { + if filteredEntries[i].message.groupingKey != currentGroupKey { + let message = filteredEntries[i].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[i].index), true) } } } - } else if index + 1 < view.entries.count { - let message = view.entries[index + 1].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[index + 1].index), true) + } else if index + 1 < filteredEntries.count { + let message = filteredEntries[index + 1].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[index + 1].index), true) } else { return nil } case .earlier: if !reversed, let currentGroupKey { - if index + 1 < view.entries.count, view.entries[index + 1].message.groupingKey == currentGroupKey { - let message = view.entries[index + 1].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[index + 1].index), true) + if index + 1 < filteredEntries.count, filteredEntries[index + 1].message.groupingKey == currentGroupKey { + let message = filteredEntries[index + 1].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[index + 1].index), true) } else { var nextGroupingKey: Int64? for i in (0 ..< index).reversed() { if let nextGroupingKey { - if view.entries[i].message.groupingKey != nextGroupingKey { - let message = view.entries[i + 1].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[i + 1].index), true) + if filteredEntries[i].message.groupingKey != nextGroupingKey { + let message = filteredEntries[i + 1].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[i + 1].index), true) } else if i == 0 { - let message = view.entries[i].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[i].index), true) + let message = filteredEntries[i].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[i].index), true) } - } else if view.entries[i].message.groupingKey != currentGroupKey { - if let groupingKey = view.entries[i].message.groupingKey { + } else if filteredEntries[i].message.groupingKey != currentGroupKey { + if let groupingKey = filteredEntries[i].message.groupingKey { nextGroupingKey = groupingKey } else { - let message = view.entries[i].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[i].index), true) + let message = filteredEntries[i].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[i].index), true) } } } } } else if index != 0 { - let message = view.entries[index - 1].message + let message = filteredEntries[index - 1].message if !reversed, let nextGroupingKey = message.groupingKey { for i in (0 ..< index).reversed() { - if view.entries[i].message.groupingKey != nextGroupingKey { - let message = view.entries[i + 1].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[i + 1].index), true) + if filteredEntries[i].message.groupingKey != nextGroupingKey { + let message = filteredEntries[i + 1].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[i + 1].index), true) } else if i == 0 { - let message = view.entries[i].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[i].index), true) + let message = filteredEntries[i].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[i].index), true) } } } - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[index - 1].index), true) + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[index - 1].index), true) } else { return nil } @@ -307,14 +323,14 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M } index += 1 } - if !view.entries.isEmpty { + if !filteredEntries.isEmpty { switch position { case .later, .exact: - let message = view.entries[view.entries.count - 1].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[view.entries.count - 1].index), false) + let message = filteredEntries[filteredEntries.count - 1].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[filteredEntries.count - 1].index), false) case .earlier: - let message = view.entries[0].message - return (message, aroundMessagesFromView(view: view, centralIndex: view.entries[0].index), false) + let message = filteredEntries[0].message + return (message, aroundMessagesFromView(view: view, centralIndex: filteredEntries[0].index), false) } } else { return nil