From 72fbe5d7a71e789f0c75ca3b6b80d4fa32ac9293 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 13 Mar 2026 14:54:44 +0100 Subject: [PATCH] Various improvements --- .../Sources/PinchSourceContainerNode.swift | 4 +- .../Display/Source/ListViewProtocol.swift | 1 + .../GalleryUI/Sources/GalleryFooterNode.swift | 2 +- .../Items/UniversalVideoGalleryItem.swift | 9 +- .../Sources/MediaPlayerScrubbingNode.swift | 24 ++- .../Postbox/Sources/TimeBasedCleanup.swift | 85 +------- .../VideoChatParticipantVideoComponent.swift | 4 + .../VideoChatParticipantsComponent.swift | 2 +- .../Sources/VideoChatScreen.swift | 11 +- .../VoiceChatShareScreenContextItem.swift | 3 +- .../Sources/State/PaidMessages.swift | 10 +- .../Resources/CollectCacheUsageStats.swift | 25 +++ .../Utils/AutomaticCacheEviction.swift | 184 +++++++++--------- .../Sources/ChatTextInputPanelNode.swift | 13 +- .../Sources/NavigationBarImpl.swift | 5 +- .../PeerInfoGroupsInCommonPaneNode.swift | 87 ++++++++- .../Sources/Panes/PeerInfoMembersPane.swift | 73 ++++++- .../Sources/PeerInfoPaneContainerNode.swift | 6 +- .../PeerInfoScreenPerformButtonAction.swift | 21 ++ .../Sources/PeerInfoSelectionPanelNode.swift | 4 +- .../Components/StorageUsageScreen/BUILD | 1 + .../StorageUsagePanelContainerComponent.swift | 2 +- .../Sources/StorageUsageScreen.swift | 45 +++-- .../Sources/ChatControllerNode.swift | 10 +- .../Sources/ChatMessageTransitionNode.swift | 30 ++- 25 files changed, 430 insertions(+), 231 deletions(-) diff --git a/submodules/ContextUI/Sources/PinchSourceContainerNode.swift b/submodules/ContextUI/Sources/PinchSourceContainerNode.swift index bc2cc63ac6..e04b59d7fd 100644 --- a/submodules/ContextUI/Sources/PinchSourceContainerNode.swift +++ b/submodules/ContextUI/Sources/PinchSourceContainerNode.swift @@ -146,7 +146,7 @@ public final class PinchSourceContainerNode: ASDisplayNode, ASGestureRecognizerD public var maxPinchScale: CGFloat = 10.0 - private var isActive: Bool = false + public private(set) var isActive: Bool = false public var activate: ((PinchSourceContainerNode) -> Void)? public var scaleUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)? @@ -223,4 +223,4 @@ public final class PinchSourceContainerNode: ASDisplayNode, ASGestureRecognizerD } self.contentNode.frame = naturalContentFrame } -} \ No newline at end of file +} diff --git a/submodules/Display/Source/ListViewProtocol.swift b/submodules/Display/Source/ListViewProtocol.swift index d66509ef95..51a838d7e4 100644 --- a/submodules/Display/Source/ListViewProtocol.swift +++ b/submodules/Display/Source/ListViewProtocol.swift @@ -30,6 +30,7 @@ public protocol ListView: ASDisplayNode { // MARK: - Read-only State Properties var insets: UIEdgeInsets { get } + var visibleSize: CGSize { get } var isTracking: Bool { get } var trackingOffset: CGFloat { get } var beganTrackingAtTopOrigin: Bool { get } diff --git a/submodules/GalleryUI/Sources/GalleryFooterNode.swift b/submodules/GalleryUI/Sources/GalleryFooterNode.swift index a2571990d0..af4656ee40 100644 --- a/submodules/GalleryUI/Sources/GalleryFooterNode.swift +++ b/submodules/GalleryUI/Sources/GalleryFooterNode.swift @@ -78,7 +78,7 @@ public final class GalleryFooterNode: ASDisplayNode { self.currentThumbnailPanelHeight = thumbnailPanelHeight self.currentFooterContentNode = footerContentNode if let footerContentNode = footerContentNode { - footerContentNode.setVisibilityAlpha(self.visibilityAlpha, animated: transition.isAnimated) + footerContentNode.setVisibilityAlpha(self.visibilityAlpha, animated: false) footerContentNode.controllerInteraction = self.controllerInteraction footerContentNode.requestLayout = { [weak self] transition in if let strongSelf = self, let (currentLayout, navigationBarHeight, currentThumbnailPanelHeight, isHidden) = strongSelf.currentLayout { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 7de25df73e..8c4bc2ac28 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -928,6 +928,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { private var scrubbingFrameDisposable: Disposable? private var isPlaying = false + private var hasStartedOnce = false private let isPlayingPromise = ValuePromise(false, ignoreRepeated: true) private let isInteractingPromise = ValuePromise(false, ignoreRepeated: true) private var areControlsVisible: Bool = true @@ -1679,10 +1680,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { var isPlaying = false var isPaused = true var seekable = hintSeekable - var hasStarted = false + var hasStarted = strongSelf.hasStartedOnce var displayProgress = true if let value = value { - hasStarted = value.timestamp > 0 + if value.timestamp > 0 { + hasStarted = true + } if let zoomableContent = strongSelf.zoomableContent, !value.dimensions.width.isZero && !value.dimensions.height.isZero { let videoSize = CGSize(width: value.dimensions.width * 2.0, height: value.dimensions.height * 2.0) @@ -1750,6 +1753,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } + strongSelf.hasStartedOnce = hasStarted + if !disablePlayerControls && strongSelf.isCentral == true && isPlaying { strongSelf.isPlayingPromise.set(true) strongSelf.isPlaying = true diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index d5ee06bab1..98029012c8 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -268,7 +268,11 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode { self.foregroundNode.isLayerBacked = true self.foregroundNode.displayWithoutProcessing = true self.foregroundNode.displaysAsynchronously = false - self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: color) + if case .round = lineCap { + self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: color) + } else { + self.foregroundNode.backgroundColor = color + } super.init() @@ -278,6 +282,9 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode { func updateStatus(_ ranges: RangeSet, _ size: Int64) { self.ranges = (ranges, size) + /*#if DEBUG + self.ranges = (RangeSet(0 ..< size / 2), size) + #endif*/ if !self.bounds.width.isZero { self.updateLayout(size: self.bounds.size, transition: .animated(duration: 0.15, curve: .easeInOut)) } @@ -424,7 +431,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { backgroundNode.displaysAsynchronously = false backgroundNode.displayWithoutProcessing = true - let bufferingNode = MediaPlayerScrubbingBufferingNode(color: bufferingColor, lineCap: lineCap, lineHeight: lineHeight) + let bufferingNode = MediaPlayerScrubbingBufferingNode(color: bufferingColor, lineCap: scrubberHandle == .none ? .square : lineCap, lineHeight: lineHeight) let foregroundContentNode = ASImageNode() foregroundContentNode.isLayerBacked = true @@ -434,7 +441,11 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { switch lineCap { case .round: backgroundNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: backgroundColor) - foregroundContentNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: foregroundColor) + if case .none = scrubberHandle { + foregroundContentNode.backgroundColor = foregroundColor + } else { + foregroundContentNode.image = generateStretchableFilledCircleImage(diameter: lineHeight, color: foregroundColor) + } case .square: backgroundNode.backgroundColor = backgroundColor foregroundContentNode.backgroundColor = foregroundColor @@ -443,13 +454,16 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { let foregroundNode = MediaPlayerScrubbingForegroundNode() foregroundNode.isLayerBacked = true foregroundNode.clipsToBounds = true + if case .round = lineCap { + foregroundNode.layer.cornerRadius = lineHeight * 0.5 + } var handleNodeImpl: ASImageNode? var highlightedHandleNodeImpl: ASImageNode? var handleNodeContainerImpl: MediaPlayerScrubbingNodeButton? switch scrubberHandle { - case .none: + case .none: let handleNode = ASImageNode() handleNode.isLayerBacked = true handleNodeImpl = handleNode @@ -750,7 +764,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { switch self.contentNodes { case let .standard(node): let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate - node.foregroundContentNode.backgroundColor = collapsed ? .white : nil + node.foregroundContentNode.backgroundColor = collapsed ? .white : (node.handle == .none ? .white : nil) if let handleNode = node.handleNodeContainer { transition.updateAlpha(node: node.foregroundContentNode, alpha: collapsed ? 0.45 : 1.0) diff --git a/submodules/Postbox/Sources/TimeBasedCleanup.swift b/submodules/Postbox/Sources/TimeBasedCleanup.swift index 978ec8e094..12b4e864ba 100644 --- a/submodules/Postbox/Sources/TimeBasedCleanup.swift +++ b/submodules/Postbox/Sources/TimeBasedCleanup.swift @@ -198,87 +198,6 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu return result } -/*private func mapFiles(paths: [String], inodes: inout [InodeInfo], removeSize: UInt64, mainStoragePath: String, storageBox: StorageBox) { - var removedSize: UInt64 = 0 - inodes.sort(by: { lhs, rhs in - return lhs.timestamp < rhs.timestamp - }) - - var inodesToDelete = Set<__darwin_ino64_t>() - - for inode in inodes { - inodesToDelete.insert(inode.inode) - removedSize += UInt64(inode.size) - if removedSize >= removeSize { - break - } - } - - if inodesToDelete.isEmpty { - return - } - - let pathBuffer = malloc(2048).assumingMemoryBound(to: Int8.self) - defer { - free(pathBuffer) - } - - var unlinkedResourceIds: [Data] = [] - - for path in paths { - let isMainPath = path == mainStoragePath - if let dp = opendir(path) { - while true { - guard let dirp = readdir(dp) else { - break - } - - if strncmp(&dirp.pointee.d_name.0, ".", 1024) == 0 { - continue - } - if strncmp(&dirp.pointee.d_name.0, "..", 1024) == 0 { - continue - } - strncpy(pathBuffer, path, 1024) - strncat(pathBuffer, "/", 1024) - strncat(pathBuffer, &dirp.pointee.d_name.0, 1024) - - //puts(pathBuffer) - //puts("\n") - - var value = stat() - if stat(pathBuffer, &value) == 0 { - if (((value.st_mode) & S_IFMT) == S_IFDIR) { - if let subPath = String(data: Data(bytes: pathBuffer, count: strnlen(pathBuffer, 1024)), encoding: .utf8) { - mapFiles(paths: <#T##[String]#>, inodes: &<#T##[InodeInfo]#>, removeSize: remov, mainStoragePath: mainStoragePath, storageBox: storageBox) - } - } else { - if inodesToDelete.contains(value.st_ino) { - if isMainPath { - let nameLength = strnlen(&dirp.pointee.d_name.0, 1024) - let nameData = Data(bytesNoCopy: &dirp.pointee.d_name.0, count: Int(nameLength), deallocator: .none) - withExtendedLifetime(nameData, { - if let fileName = String(data: nameData, encoding: .utf8) { - if let idData = MediaBox.idForFileName(name: fileName).data(using: .utf8) { - unlinkedResourceIds.append(idData) - } - } - }) - } - unlink(pathBuffer) - } - } - } - } - closedir(dp) - } - } - - if !unlinkedResourceIds.isEmpty { - storageBox.remove(ids: unlinkedResourceIds) - } -}*/ - private func statForDirectory(path: String) -> Int64 { if #available(macOS 10.13, *) { var s = darwin_dirstat() @@ -482,10 +401,10 @@ private final class TimeBasedCleanupImpl { } //let fileName = filePath.lastPathComponent - if remainingSize >= bytesLimit { + if remainingSize <= Int64(bytesLimit) { return false } - + return true } } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift index a46563c51a..4c6ef24fcc 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift @@ -213,6 +213,10 @@ final class VideoChatParticipantVideoComponent: Component { private var referenceLocation: ReferenceLocation? private var loadingEffectView: VideoChatVideoLoadingEffectView? + public var isPinchToZoomActive: Bool { + return self.pinchContainerNode.isActive + } + override init(frame: CGRect) { self.backgroundGradientView = UIImageView() self.pinchContainerNode = PinchSourceContainerNode() diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift index 11529cf236..5aa0a630c3 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift @@ -693,7 +693,7 @@ final class VideoChatParticipantsComponent: Component { private var appliedGridIsEmpty: Bool = true - private var isPinchToZoomActive: Bool = false + private(set) var isPinchToZoomActive: Bool = false private var stopRequestingNonCentralVideo: Bool = false private var stopRequestingNonCentralVideoTimer: Foundation.Timer? diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 5449297515..def60adbb4 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -337,6 +337,13 @@ final class VideoChatScreenComponent: Component { var maxVideoQuality: Int = Int.max + private var isPinchToZoomActive: Bool { + if let participantsView = self.participants.view as? VideoChatParticipantsComponent.View { + return participantsView.isPinchToZoomActive + } + return false + } + override init(frame: CGRect) { self.containerView = UIView() self.containerView.clipsToBounds = true @@ -1609,7 +1616,7 @@ final class VideoChatScreenComponent: Component { } if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, let members { - if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in + if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, !self.isPinchToZoomActive, let participant = members.participants.first(where: { participant in if let callState = self.callState, participant.id == .peer(callState.myPeerId) { return false } @@ -1966,7 +1973,7 @@ final class VideoChatScreenComponent: Component { } if let expandedParticipantsVideoState = self.expandedParticipantsVideoState { - if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in + if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, !self.isPinchToZoomActive, let participant = members.participants.first(where: { participant in if let callState = self.callState, participant.id == .peer(callState.myPeerId) { return false } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatShareScreenContextItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatShareScreenContextItem.swift index 70360e3234..d84df8a4c2 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatShareScreenContextItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatShareScreenContextItem.swift @@ -121,11 +121,10 @@ private final class VoiceChatShareScreenContextItemNode: ASDisplayNode, ContextM let standardIconWidth: CGFloat = 32.0 var rightTextInset: CGFloat = sideInset if !iconSize.width.isZero { - rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset + rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset } let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude)) - let verticalSpacing: CGFloat = 2.0 let combinedTextHeight = textSize.height + verticalSpacing diff --git a/submodules/TelegramCore/Sources/State/PaidMessages.swift b/submodules/TelegramCore/Sources/State/PaidMessages.swift index fc41251860..d576b283a7 100644 --- a/submodules/TelegramCore/Sources/State/PaidMessages.swift +++ b/submodules/TelegramCore/Sources/State/PaidMessages.swift @@ -96,23 +96,29 @@ func _internal_reinstateNoPaidMessagesException(account: Account, scopePeerId: P return (transaction.getPeer(scopePeerId).flatMap(apiInputPeer), transaction.getPeer(peerId).flatMap(apiInputUser)) } |> mapToSignal { scopeInputPeer, inputUser -> Signal in + var scopeInputPeer = scopeInputPeer if scopePeerId != account.peerId { if scopeInputPeer == nil { return .never() } } else { - return .never() + scopeInputPeer = nil } guard let inputUser else { return .never() } var flags: Int32 = 0 flags |= (1 << 2) - flags |= (1 << 1) + if scopeInputPeer != nil { + flags |= (1 << 1) + } return account.network.request(Api.functions.account.toggleNoPaidMessagesException(flags: flags, parentPeer: scopeInputPeer, userId: inputUser)) |> `catch` { _ -> Signal in return .single(.boolFalse) } |> mapToSignal { _ in + if scopePeerId == account.peerId { + account.viewTracker.forceUpdateCachedPeerData(peerId: peerId) + } return account.postbox.transaction { transaction -> Void in if scopePeerId != account.peerId { guard var data = transaction.getMessageHistoryThreadInfo(peerId: scopePeerId, threadId: peerId.toInt64())?.data.get(MessageHistoryThreadData.self) else { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift index c7c38cb20b..e51b9a840e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift @@ -325,6 +325,31 @@ func _internal_renderStorageUsageStatsMessages(account: Account, stats: StorageU } func _internal_clearStorage(account: Account, peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], includeMessages: [Message], excludeMessages: [Message]) -> Signal { + #if DEBUG + if "".isEmpty { + return Signal { subscriber in + var value: Float = 0.0 + subscriber.putNext(value) + let timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: true, completion: { + if value != 1.0 { + value += 0.1 + if value >= 1.0 { + value = 1.0 + subscriber.putNext(value) + subscriber.putCompletion() + } else { + subscriber.putNext(value) + } + } + }, queue: .mainQueue()) + timer.start() + return ActionDisposable { + timer.invalidate() + } + } + } + #endif + let mediaBox = account.postbox.mediaBox return Signal { subscriber in var includeResourceIds = Set() diff --git a/submodules/TelegramCore/Sources/Utils/AutomaticCacheEviction.swift b/submodules/TelegramCore/Sources/Utils/AutomaticCacheEviction.swift index e4773b42e8..fe0cae68e7 100644 --- a/submodules/TelegramCore/Sources/Utils/AutomaticCacheEviction.swift +++ b/submodules/TelegramCore/Sources/Utils/AutomaticCacheEviction.swift @@ -134,115 +134,119 @@ final class AutomaticCacheEvictionContext { } } - if timeout == Int32.max { - return .complete() - } + let storyTimeout: Int32 = settings.categoryStorageTimeout[.stories] ?? (2 * 24 * 60 * 60) - let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - timeout - //let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - - let allSignal = mediaBox.storageBox.all(peerId: peerId, excludeType: MediaResourceUserContentType.story.rawValue) - |> mapToSignal { peerResourceIds -> Signal in - return Signal { subscriber in - var isCancelled = false - - processingQueue.justDispatch { - var removeIds: [MediaResourceId] = [] - var removeRawIds: [Data] = [] - var localCounter = 0 - for resourceId in peerResourceIds { - localCounter += 1 - if localCounter % 100 == 0 { - if isCancelled { - subscriber.putCompletion() - return - } - } - - removeRawIds.append(resourceId) - let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!) - let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id) - if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp { - removeIds.append(id) - } - } + let allSignal: Signal + if timeout != Int32.max { + let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - timeout + + allSignal = mediaBox.storageBox.all(peerId: peerId, excludeType: MediaResourceUserContentType.story.rawValue) + |> mapToSignal { peerResourceIds -> Signal in + return Signal { subscriber in + var isCancelled = false - if !removeIds.isEmpty { - Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources") - - let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in - var actualRawIds: [Data] = [] - for id in actualIds { - if let data = id.stringRepresentation.data(using: .utf8) { - actualRawIds.append(data) + processingQueue.justDispatch { + var removeIds: [MediaResourceId] = [] + var localCounter = 0 + for resourceId in peerResourceIds { + localCounter += 1 + if localCounter % 100 == 0 { + if isCancelled { + subscriber.putCompletion() + return } } - mediaBox.storageBox.remove(ids: actualRawIds) - - subscriber.putCompletion() - }) - } else { - subscriber.putCompletion() - } - } - - return ActionDisposable { - isCancelled = true - } - } - } - - let storySignal = mediaBox.storageBox.all(peerId: peerId, onlyType: MediaResourceUserContentType.story.rawValue) - |> mapToSignal { peerResourceIds -> Signal in - return Signal { subscriber in - var isCancelled = false - - processingQueue.justDispatch { - var removeIds: [MediaResourceId] = [] - var removeRawIds: [Data] = [] - var localCounter = 0 - for resourceId in peerResourceIds { - localCounter += 1 - if localCounter % 100 == 0 { - if isCancelled { - subscriber.putCompletion() - return + let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!) + let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id) + if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp { + removeIds.append(id) } } - removeRawIds.append(resourceId) - let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!) - let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id) - if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp { - removeIds.append(id) + if !removeIds.isEmpty { + Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources") + + let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in + var actualRawIds: [Data] = [] + for id in actualIds { + if let data = id.stringRepresentation.data(using: .utf8) { + actualRawIds.append(data) + } + } + + mediaBox.storageBox.remove(ids: actualRawIds) + + subscriber.putCompletion() + }) + } else { + subscriber.putCompletion() } } - if !removeIds.isEmpty { - Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources") - - let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in - var actualRawIds: [Data] = [] - for id in actualIds { - if let data = id.stringRepresentation.data(using: .utf8) { - actualRawIds.append(data) + return ActionDisposable { + isCancelled = true + } + } + } + } else { + allSignal = .complete() + } + + let storySignal: Signal + if storyTimeout != Int32.max { + let minPeerTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - storyTimeout + storySignal = mediaBox.storageBox.all(peerId: peerId, onlyType: MediaResourceUserContentType.story.rawValue) + |> mapToSignal { peerResourceIds -> Signal in + return Signal { subscriber in + var isCancelled = false + + processingQueue.justDispatch { + var removeIds: [MediaResourceId] = [] + var localCounter = 0 + for resourceId in peerResourceIds { + localCounter += 1 + if localCounter % 100 == 0 { + if isCancelled { + subscriber.putCompletion() + return } } - mediaBox.storageBox.remove(ids: actualRawIds) + let id = MediaResourceId(String(data: resourceId, encoding: .utf8)!) + let resourceTimestamp = mediaBox.resourceUsageWithInfo(id: id) + if resourceTimestamp != 0 && resourceTimestamp < minPeerTimestamp { + removeIds.append(id) + } + } + + if !removeIds.isEmpty { + Logger.shared.log("AutomaticCacheEviction", "peer \(peerId): cleaning \(removeIds.count) resources") + let _ = mediaBox.removeCachedResourcesWithResult(removeIds).start(next: { actualIds in + var actualRawIds: [Data] = [] + for id in actualIds { + if let data = id.stringRepresentation.data(using: .utf8) { + actualRawIds.append(data) + } + } + + mediaBox.storageBox.remove(ids: actualRawIds) + + subscriber.putCompletion() + }) + } else { subscriber.putCompletion() - }) - } else { - subscriber.putCompletion() + } + } + + return ActionDisposable { + isCancelled = true } } - - return ActionDisposable { - isCancelled = true - } } + } else { + storySignal = .complete() } return allSignal |> then(storySignal) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index c4f433f965..4c75039caa 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -2404,7 +2404,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if additionalSideInsets.right > 0.0 { textFieldInsets.right += additionalSideInsets.right / 3.0 } - if inputHasText || self.extendedSearchLayout || hasMediaDraft || hasForward || hasSlowmodeButton { + if inputHasText || self.extendedSearchLayout || hasMediaDraft || hasForward || hasSlowmodeButton || isEditingMedia { } else { if let customRightAction = self.customRightAction, case .empty = customRightAction { textFieldInsets.right = 8.0 @@ -3033,7 +3033,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if self.extendedSearchLayout { nextButtonTopRight.x -= 46.0 } else if hasSlowmodeButton { - } else if inputHasText || hasMediaDraft || hasForward { + } else if inputHasText || hasMediaDraft || hasForward || isEditingMedia { nextButtonTopRight.x -= sendActionButtonsSize.width } for (item, button) in self.accessoryItemButtons.reversed() { @@ -3182,7 +3182,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } var mediaActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX + 6.0, y: textInputContainerBackgroundFrame.maxY - mediaActionButtonsSize.height), size: mediaActionButtonsSize) - if inputHasText || self.extendedSearchLayout || hasMediaDraft || interfaceState.interfaceState.forwardMessageIds != nil || hasSlowmodeButton { + if inputHasText || self.extendedSearchLayout || hasMediaDraft || interfaceState.interfaceState.forwardMessageIds != nil || hasSlowmodeButton || isEditingMedia { mediaActionButtonsFrame.origin.x = width + 8.0 } transition.updateFrame(node: self.mediaActionButtons, frame: mediaActionButtonsFrame) @@ -3230,7 +3230,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg var sendActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX - sendActionButtonsSize.width, y: textInputContainerBackgroundFrame.maxY - sendActionButtonsSize.height), size: sendActionButtonsSize) let sendActionsScale: CGFloat - if inputHasText || hasMediaDraft || hasForward { + if inputHasText || hasMediaDraft || hasForward || isEditingMedia { sendActionsScale = 1.0 } else { sendActionsScale = 0.001 @@ -4369,6 +4369,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if presentationInterfaceState.interfaceState.mediaDraftState != nil { keepSendButtonEnabled = true } + if let editMessageState = presentationInterfaceState.editMessageState { + if case let .media(value) = editMessageState.content, !value.isEmpty { + keepSendButtonEnabled = true + } + } hasForward = presentationInterfaceState.interfaceState.forwardMessageIds != nil hideMicButtonBackground = presentationInterfaceState.inputTextPanelState.mediaRecordingState != nil diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift index ab60255310..9d00e1054b 100644 --- a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift +++ b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift @@ -1010,7 +1010,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { if (leftTitleInset == leftInset) != (rightTitleInset == rightInset) { if rightTitleInset == rightInset { - rightTitleInset = leftTitleInset + rightTitleInset = max(rightInset, 16.0) } else if leftTitleInset == leftInset { leftTitleInset = rightTitleInset } @@ -1041,6 +1041,9 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { let titleSize = titleView.updateLayout(availableSize: CGSize(width: size.width - leftTitleInset - rightTitleInset, height: nominalHeight), transition: titleViewTransition) var titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) * 0.5), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + if titleFrame.origin.x + titleFrame.width > size.width - rightTitleInset { + titleFrame.origin.x = size.width - rightTitleInset - titleFrame.width + } if titleFrame.origin.x < leftTitleInset { titleFrame.origin.x = leftTitleInset + floorToScreenPixels((size.width - leftTitleInset - rightTitleInset - titleFrame.width) * 0.5) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift index 1f69cbc65a..b1600c7312 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift @@ -69,14 +69,18 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { private let groupsInCommonContext: GroupsInCommonContext weak var parentController: ViewController? - + + private let listBackgroundView: UIImageView + private let listMaskView: UIImageView private let listNode: ListView private var state: GroupsInCommonState? private var currentEntries: [GroupsInCommonListEntry] = [] private var enqueuedTransactions: [GroupsInCommonListTransaction] = [] private var currentParams: (size: CGSize, isScrollingLockedAtTop: Bool, presentationData: PresentationData)? - + + private var ignoreListBackgroundUpdates: Bool = false + private let ready = Promise() private var didSetReady: Bool = false var isReady: Signal { @@ -114,11 +118,25 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { self.listNode.accessibilityPageScrolledString = { row, count in return presentationData.strings.VoiceOver_ScrollStatus(row, count).string } - + + self.listBackgroundView = UIImageView() + self.listBackgroundView.image = generateStretchableFilledCircleImage(diameter: 26.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate) + self.listMaskView = UIImageView() + self.listMaskView.image = generateImage(CGSize(width: 16.0 + 26.0 * 2.0 + 16.0, height: 2.0 * 2.0 + 26.0 * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 2.0), size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0))) + })?.stretchableImage(withLeftCapWidth: 16 + 26, topCapHeight: 2 + 26).withRenderingMode(.alwaysTemplate) + super.init() - + self.listNode.preloadPages = true + self.view.addSubview(self.listBackgroundView) self.addSubnode(self.listNode) + self.view.addSubview(self.listMaskView) self.disposable = (groupsInCommonContext.state |> deliverOnMainQueue).startStrict(next: { [weak self] state in @@ -139,6 +157,23 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { strongSelf.groupsInCommonContext.loadMore() } } + + self.listNode.visibleContentOffsetChanged = { [weak self] _, transition in + guard let self else { + return + } + if !self.ignoreListBackgroundUpdates { + self.updateListBackground(transition: transition) + } + } + self.listNode.displayedItemRangeChanged = { [weak self] _, _ in + guard let self else { + return + } + if !self.ignoreListBackgroundUpdates { + self.updateListBackground(transition: .immediate) + } + } } deinit { @@ -161,9 +196,10 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { let isFirstLayout = self.currentParams == nil self.currentParams = (size, isScrollingLockedAtTop, presentationData) + self.ignoreListBackgroundUpdates = true transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - + var scrollToItem: ListViewScrollToItem? if isScrollingLockedAtTop { switch self.listNode.visibleContentOffset() { @@ -174,10 +210,16 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { } } - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset + 16.0, bottom: bottomInset, right: sideInset + 16.0), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.listNode.scrollEnabled = !isScrollingLockedAtTop - + + self.ignoreListBackgroundUpdates = false + self.updateListBackground(transition: transition) + + self.listBackgroundView.tintColor = presentationData.theme.list.itemBlocksBackgroundColor + self.listMaskView.tintColor = presentationData.theme.list.blocksBackgroundColor + if isFirstLayout, let state = self.state { self.updatePeers(state: state, presentationData: presentationData) } @@ -221,6 +263,35 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { }) } + private func updateListBackground(transition: ContainedViewLayoutTransition) { + guard self.listNode.visibleSize.width != 0.0 else { + return + } + + var distanceToTop: CGFloat = -100.0 + var distanceToBottom: CGFloat = -100.0 + switch self.listNode.visibleContentOffset() { + case let .known(topOffset): + distanceToTop = -topOffset + self.listNode.insets.top + default: + break + } + switch self.listNode.visibleBottomContentOffset() { + case let .known(bottomOffset): + distanceToBottom = -bottomOffset + self.listNode.insets.bottom + default: + break + } + + distanceToTop = max(-100.0, distanceToTop) + distanceToBottom = max(-100.0, distanceToBottom) + + let listBackgroundFrame = CGRect(origin: CGPoint(x: 16.0, y: distanceToTop), size: CGSize(width: max(1.0, self.listNode.visibleSize.width - 16.0 * 2.0), height: max(1.0, self.listNode.visibleSize.height - distanceToBottom - distanceToTop))) + let listMaskFrame = CGRect(origin: CGPoint(x: 0.0, y: listBackgroundFrame.minY - 2.0), size: CGSize(width: listBackgroundFrame.width + 16.0 * 2.0, height: listBackgroundFrame.height + 2.0 * 2.0)) + transition.updateFrame(view: self.listBackgroundView, frame: listBackgroundFrame) + transition.updateFrame(view: self.listMaskView, frame: listMaskFrame) + } + func findLoadedMessage(id: MessageId) -> Message? { return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift index 284992c656..7ba0773cb5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift @@ -286,6 +286,8 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { weak var parentController: ViewController? + private let listBackgroundView: UIImageView + private let listMaskView: UIImageView private let listNode: ListView private var currentEntries: [PeerMembersListEntry] = [] private var enclosingPeer: Peer? @@ -296,6 +298,8 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { private var currentParams: (size: CGSize, isScrollingLockedAtTop: Bool)? private let presentationDataPromise = Promise() + private var ignoreListBackgroundUpdates: Bool = false + private let ready = Promise() private var didSetReady: Bool = false var isReady: Signal { @@ -325,10 +329,24 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { return presentationData.strings.VoiceOver_ScrollStatus(row, count).string } + self.listBackgroundView = UIImageView() + self.listBackgroundView.image = generateStretchableFilledCircleImage(diameter: 26.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate) + self.listMaskView = UIImageView() + self.listMaskView.image = generateImage(CGSize(width: 16.0 + 26.0 * 2.0 + 16.0, height: 26.0 * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0))) + })?.stretchableImage(withLeftCapWidth: 16 + 26, topCapHeight: 26).withRenderingMode(.alwaysTemplate) + super.init() self.listNode.preloadPages = true + self.view.addSubview(self.listBackgroundView) self.addSubnode(self.listNode) + self.view.addSubview(self.listMaskView) self.disposable = (combineLatest(queue: .mainQueue(), membersContext.state, @@ -353,6 +371,23 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { strongSelf.membersContext.loadMore() } } + + self.listNode.visibleContentOffsetChanged = { [weak self] _, transition in + guard let self else { + return + } + if !self.ignoreListBackgroundUpdates { + self.updateListBackground(transition: transition) + } + } + self.listNode.displayedItemRangeChanged = { [weak self] _, _ in + guard let self else { + return + } + if !self.ignoreListBackgroundUpdates { + self.updateListBackground(transition: .immediate) + } + } } deinit { @@ -376,6 +411,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { self.currentParams = (size, isScrollingLockedAtTop) self.presentationDataPromise.set(.single(presentationData)) + self.ignoreListBackgroundUpdates = true transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) @@ -388,10 +424,16 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: duration), directionHint: .Up) } } - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: scrollToItem, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset + 16.0, bottom: bottomInset, right: sideInset + 16.0), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.scrollEnabled = !isScrollingLockedAtTop + self.ignoreListBackgroundUpdates = false + self.updateListBackground(transition: transition) + + self.listBackgroundView.tintColor = presentationData.theme.list.itemBlocksBackgroundColor + self.listMaskView.tintColor = presentationData.theme.list.blocksBackgroundColor + if isFirstLayout, let enclosingPeer = self.enclosingPeer, let state = self.currentState { self.updateState(enclosingPeer: enclosingPeer, state: state, presentationData: presentationData) } @@ -471,6 +513,35 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { }) } + private func updateListBackground(transition: ContainedViewLayoutTransition) { + guard self.listNode.visibleSize.width != 0.0 else { + return + } + + var distanceToTop: CGFloat = -100.0 + var distanceToBottom: CGFloat = -100.0 + switch self.listNode.visibleContentOffset() { + case let .known(topOffset): + distanceToTop = -topOffset + self.listNode.insets.top + default: + break + } + switch self.listNode.visibleBottomContentOffset() { + case let .known(bottomOffset): + distanceToBottom = -bottomOffset + self.listNode.insets.bottom + default: + break + } + + distanceToTop = max(-100.0, distanceToTop) + distanceToBottom = max(-100.0, distanceToBottom) + + let listBackgroundFrame = CGRect(origin: CGPoint(x: 16.0, y: distanceToTop), size: CGSize(width: max(1.0, self.listNode.visibleSize.width - 16.0 * 2.0), height: max(1.0, self.listNode.visibleSize.height - distanceToBottom - distanceToTop))) + let listMaskFrame = CGRect(origin: CGPoint(x: 0.0, y: listBackgroundFrame.minY), size: CGSize(width: listBackgroundFrame.width + 16.0 * 2.0, height: listBackgroundFrame.height)) + transition.updateFrame(view: self.listBackgroundView, frame: listBackgroundFrame) + transition.updateFrame(view: self.listMaskView, frame: listMaskFrame) + } + func findLoadedMessage(id: MessageId) -> Message? { return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index fbfeabc8f3..c88a28f342 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -962,7 +962,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat if self.currentPaneKey == .gifts { backgroundColor = presentationData.theme.list.blocksBackgroundColor } else { - backgroundColor = presentationData.theme.list.blocksBackgroundColor.mixedWith(presentationData.theme.list.plainBackgroundColor, alpha: expansionFraction) + if self.currentPaneKey == .stories || self.currentPaneKey == .storyArchive { + backgroundColor = presentationData.theme.list.blocksBackgroundColor.mixedWith(presentationData.theme.list.plainBackgroundColor, alpha: expansionFraction) + } else { + backgroundColor = presentationData.theme.list.blocksBackgroundColor + } } self.backgroundColor = backgroundColor diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift index 2ea49ad1ed..cf9db2dc9c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift @@ -682,6 +682,27 @@ extension PeerInfoScreenNode { } let itemsCount = items.count + + /*if let cachedData = data.cachedData as? CachedUserData { + var hasPaidFee = false + if let paidMessageStars = cachedData.peerStatusSettings?.paidMessageStars, paidMessageStars != .zero { + hasPaidFee = true + } + if !hasPaidFee { + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "Return Fee", icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Paid"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + guard let self, let peer = self.data?.peer else { + return + } + + let _ = self.context.engine.peers.reinstateNoPaidMessagesException(scopePeerId: self.context.account.peerId, peerId: peer.id).startStandalone() + }))) + } + }*/ if canSetupAutoremoveTimeout { let strings = strongSelf.presentationData.strings diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift index 330bba5457..48aa418161 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift @@ -187,8 +187,8 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { super.init() - self.addSubnode(self.backgroundNode) - self.addSubnode(self.separatorNode) + //self.addSubnode(self.backgroundNode) + //self.addSubnode(self.separatorNode) self.addSubnode(self.selectionPanel) } diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD index 4b984b59f0..d935fec442 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD +++ b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD @@ -46,6 +46,7 @@ swift_library( "//submodules/SegmentedControlNode", "//submodules/TelegramUIPreferences", "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsagePanelContainerComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsagePanelContainerComponent.swift index 6c50be3b61..57576d65b8 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsagePanelContainerComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsagePanelContainerComponent.swift @@ -561,7 +561,7 @@ final class StorageUsagePanelContainerComponent: Component { self.panelsBackgroundLayer.backgroundColor = component.theme.list.itemBlocksBackgroundColor.cgColor self.topPanelSeparatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor self.topPanelBackgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor - self.topPanelMergedBackgroundView.backgroundColor = component.theme.rootController.navigationBar.blurredBackgroundColor + self.topPanelMergedBackgroundView.backgroundColor = component.theme.list.blocksBackgroundColor } let topPanelCoverHeight: CGFloat = 10.0 diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index 87729ec7df..827f5feb2d 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -25,6 +25,7 @@ import GalleryData import AnimatedTextComponent import BottomButtonPanelComponent import GlassBackgroundComponent +import EdgeEffect #if DEBUG import os.signpost @@ -124,20 +125,17 @@ final class StorageUsageScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext - let overNavigationContainer: UIView let makeStorageUsageExceptionsScreen: (CacheStorageSettings.PeerStorageCategory) -> ViewController? let peer: EnginePeer? let ready: Promise init( context: AccountContext, - overNavigationContainer: UIView, makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?, peer: EnginePeer?, ready: Promise ) { self.context = context - self.overNavigationContainer = overNavigationContainer self.makeStorageUsageExceptionsScreen = makeStorageUsageExceptionsScreen self.peer = peer self.ready = ready @@ -755,6 +753,8 @@ final class StorageUsageScreenComponent: Component { private let navigationEditButton = ComponentView() private let navigationDoneButton = ComponentView() + private let edgeEffectView: EdgeEffectView + private let headerView = ComponentView() private let headerOffsetContainer: UIView private let headerDescriptionView = ComponentView() @@ -768,7 +768,8 @@ final class StorageUsageScreenComponent: Component { private var doneStatusNode: RadialStatusNode? private let scrollContainerView: UIView - + private let topContentOverlayView: UIView + private let pieChartView = ComponentView() private let chartTotalLabel = ComponentView() private let categoriesView = ComponentView() @@ -811,7 +812,11 @@ final class StorageUsageScreenComponent: Component { self.headerOffsetContainer = SparseContainerView() self.scrollContainerView = UIView() - + + self.topContentOverlayView = UIView() + self.topContentOverlayView.isUserInteractionEnabled = false + self.topContentOverlayView.alpha = 0.0 + self.scrollView = ScrollViewImpl() self.keepDurationSectionContainerView = UIView() @@ -823,6 +828,8 @@ final class StorageUsageScreenComponent: Component { self.navigationRightButtonsBackground = GlassBackgroundView() + self.edgeEffectView = EdgeEffectView() + super.init(frame: frame) self.scrollView.delaysContentTouches = true @@ -848,6 +855,8 @@ final class StorageUsageScreenComponent: Component { self.scrollView.layer.addSublayer(self.headerProgressBackgroundLayer) self.scrollView.layer.addSublayer(self.headerProgressForegroundLayer) + + self.addSubview(self.edgeEffectView) } required init?(coder: NSCoder) { @@ -961,6 +970,7 @@ final class StorageUsageScreenComponent: Component { if let panelContainerView = self.panelContainer.view as? StorageUsagePanelContainerComponent.View { panelContainerView.updateNavigationMergeFactor(value: 1.0 - expansionDistanceFactor, transition: transition) } + self.topContentOverlayView.alpha = 1.0 - expansionDistanceFactor var offsetFraction: CGFloat = abs(headerOffset - minOffset) / 60.0 offsetFraction = min(1.0, max(0.0, offsetFraction)) @@ -1105,10 +1115,10 @@ final class StorageUsageScreenComponent: Component { } if self.headerOffsetContainer.superview == nil { - component.overNavigationContainer.addSubview(self.headerOffsetContainer) + self.addSubview(self.headerOffsetContainer) } if self.navigationRightButtonsBackground.superview == nil { - component.overNavigationContainer.addSubview(self.navigationRightButtonsBackground) + self.addSubview(self.navigationRightButtonsBackground) } var wasLockedAtPanels = false @@ -1222,6 +1232,11 @@ final class StorageUsageScreenComponent: Component { self.backgroundColor = environment.theme.list.blocksBackgroundColor + let edgeEffectHeight: CGFloat = environment.navigationHeight + 24.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: min(64.0, edgeEffectHeight), transition: transition) + var contentHeight: CGFloat = 0.0 let topInset: CGFloat = 19.0 @@ -2219,10 +2234,16 @@ final class StorageUsageScreenComponent: Component { self.scrollContainerView.addSubview(panelContainerView) } transition.setFrame(view: panelContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: panelContainerSize)) + if self.topContentOverlayView.superview == nil { + self.scrollContainerView.insertSubview(self.topContentOverlayView, belowSubview: panelContainerView) + } + self.topContentOverlayView.backgroundColor = environment.theme.list.blocksBackgroundColor + transition.setFrame(view: self.topContentOverlayView, frame: CGRect(origin: CGPoint(x: 0.0, y: panelContainerView.frame.minY - availableSize.height), size: availableSize)) } contentHeight += panelContainerSize.height } else { self.panelContainer.view?.removeFromSuperview() + self.topContentOverlayView.removeFromSuperview() } self.ignoreScrolling = true @@ -3315,8 +3336,6 @@ final class StorageUsageScreenComponent: Component { public final class StorageUsageScreen: ViewControllerComponentContainer { private let context: AccountContext - private let overNavigationContainer: UIView - private let readyValue = Promise() override public var ready: Promise { return self.readyValue @@ -3327,19 +3346,13 @@ public final class StorageUsageScreen: ViewControllerComponentContainer { public init(context: AccountContext, makeStorageUsageExceptionsScreen: @escaping (CacheStorageSettings.PeerStorageCategory) -> ViewController?, peer: EnginePeer? = nil, focusOnItemTag: StorageUsageEntryTag? = nil) { self.context = context - self.overNavigationContainer = SparseContainerView() - let componentReady = Promise() - super.init(context: context, component: StorageUsageScreenComponent(context: context, overNavigationContainer: self.overNavigationContainer, makeStorageUsageExceptionsScreen: makeStorageUsageExceptionsScreen, peer: peer, ready: componentReady), navigationBarAppearance: .default) + super.init(context: context, component: StorageUsageScreenComponent(context: context, makeStorageUsageExceptionsScreen: makeStorageUsageExceptionsScreen, peer: peer, ready: componentReady), navigationBarAppearance: .transparent) if peer != nil { self.navigationPresentation = .modal } - if let navigationBar = self.navigationBar { - navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) - } - self.readyValue.set(componentReady.get() |> timeout(0.3, queue: .mainQueue(), alternate: .single(true))) } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 09e0372e91..e9b7c5074f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -838,6 +838,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.wrappingNode.contentNode.addSubnode(self.contentContainerNode) self.contentContainerNode.contentNode.addSubnode(self.backgroundNode) self.contentContainerNode.contentNode.addSubnode(self.historyNodeContainer) + + self.contentContainerNode.contentNode.addSubnode(self.messageTransitionNode) + self.contentContainerNode.contentNode.addSubnode(self.floatingTopicsPanelContainer) if let navigationBar = self.navigationBar { @@ -863,8 +866,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode) self.inputPanelContainerNode.addSubnode(self.inputPanelOverlayNode) self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode) + + self.wrappingNode.contentNode.addSubnode(self.messageTransitionNode.overlayContainerNode) - self.wrappingNode.contentNode.addSubnode(self.messageTransitionNode) self.contentContainerNode.contentNode.addSubnode(self.navigateButtons) self.wrappingNode.contentNode.addSubnode(self.presentationContextMarker) self.contentContainerNode.contentNode.addSubnode(self.contentDimNode) @@ -2330,7 +2334,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { bottomBackgroundEdgeEffectNode = value self.bottomBackgroundEdgeEffectNode = value value.isUserInteractionEnabled = false - self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view) + self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.messageTransitionNode.view) } } } @@ -2531,7 +2535,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { topBackgroundEdgeEffectNode = value self.topBackgroundEdgeEffectNode = value value.isUserInteractionEnabled = false - self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view) + self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.messageTransitionNode.view) } } } diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 6f493bf636..82217835b6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -337,21 +337,25 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran private let source: ChatMessageTransitionNodeImpl.Source private let getContentAreaInScreenSpace: () -> CGRect + private let portalSourceView: PortalSourceView private let scrollingContainer: ASDisplayNode private let containerNode: ASDisplayNode private let clippingNode: ASDisplayNode + private var portalTargetView: PortalView? weak var overlayController: OverlayTransitionContainerController? var animationEnded: (() -> Void)? var updateAfterCompletion: Bool = false - init(itemNode: ChatMessageItemNodeProtocol, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNodeImpl.Source, getContentAreaInScreenSpace: @escaping () -> CGRect) { + init(itemNode: ChatMessageItemNodeProtocol, contextSourceNode: ContextExtractedContentContainingNode, source: ChatMessageTransitionNodeImpl.Source, overlayContainerNode: ASDisplayNode, getContentAreaInScreenSpace: @escaping () -> CGRect) { + self.portalSourceView = PortalSourceView() + self.itemNode = itemNode self.getContentAreaInScreenSpace = getContentAreaInScreenSpace self.clippingNode = ASDisplayNode() - self.clippingNode.clipsToBounds = true + self.clippingNode.clipsToBounds = false self.scrollingContainer = ASDisplayNode() self.containerNode = ASDisplayNode() @@ -359,10 +363,18 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran self.source = source super.init() + + self.view.addSubview(self.portalSourceView) - self.addSubnode(self.clippingNode) + self.portalSourceView.addSubview(self.clippingNode.view) self.clippingNode.addSubnode(self.scrollingContainer) self.scrollingContainer.addSubnode(self.containerNode) + + if let portalTargetView = PortalView(matchPosition: true) { + self.portalTargetView = portalTargetView + self.portalSourceView.addPortal(view: portalTargetView) + overlayContainerNode.view.addSubview(portalTargetView.view) + } } deinit { @@ -374,6 +386,13 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } func beginAnimation() { + if let portalTargetView = self.portalTargetView { + portalTargetView.view.alpha = 0.0 + portalTargetView.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) + + self.portalSourceView.layer.animateAlpha(from: 0.01, to: 1.0, duration: 0.12) + } + let verticalDuration: Double = ChatMessageTransitionNodeImpl.animationDuration let horizontalDuration: Double = verticalDuration let delay: Double = 0.0 @@ -970,6 +989,8 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran private var decorationItemNodes: [DecorationItemNodeImpl] = [] private var messageReactionContexts: [MessageReactionContext] = [] private var customOffsetHandlers: [CustomOffsetHandlerImpl] = [] + + public let overlayContainerNode: ASDisplayNode var hasScheduledTransitions: Bool { return !self.currentPendingItems.isEmpty @@ -983,6 +1004,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran self.listNode = listNode self.getContentAreaInScreenSpace = getContentAreaInScreenSpace self.onTransitionEvent = onTransitionEvent + self.overlayContainerNode = ASDisplayNode() super.init() @@ -1066,7 +1088,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } if let contextSourceNode = contextSourceNode { - let animatingItemNode = AnimatingItemNode(itemNode: itemNode, contextSourceNode: contextSourceNode, source: source, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace) + let animatingItemNode = AnimatingItemNode(itemNode: itemNode, contextSourceNode: contextSourceNode, source: source, overlayContainerNode: self.overlayContainerNode, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace) animatingItemNode.updateLayout(size: self.bounds.size) self.animatingItemNodes.append(animatingItemNode)