diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index de3b414bc7..3ee7cc641d 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -912,6 +912,24 @@ public extension Peer { } } +public struct ChatControllerCustomNavigationPanelNodeLayoutResult { + public var backgroundHeight: CGFloat + public var insetHeight: CGFloat + public var hitTestSlop: CGFloat + + public init(backgroundHeight: CGFloat, insetHeight: CGFloat, hitTestSlop: CGFloat) { + self.backgroundHeight = backgroundHeight + self.insetHeight = insetHeight + self.hitTestSlop = hitTestSlop + } +} + +public protocol ChatControllerCustomNavigationPanelNode: ASDisplayNode { + typealias LayoutResult = ChatControllerCustomNavigationPanelNodeLayoutResult + + func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, chatController: ChatController) -> LayoutResult +} + public protocol ChatController: ViewController { var chatLocation: ChatLocation { get } var canReadHistory: ValuePromise { get } @@ -919,9 +937,16 @@ public protocol ChatController: ViewController { var purposefulAction: (() -> Void)? { get set } + var stateUpdated: ((ContainedViewLayoutTransition) -> Void)? { get set } + var selectedMessageIds: Set? { get } var presentationInterfaceStateSignal: Signal { get } + var customNavigationBarContentNode: NavigationBarContentNode? { get } + var customNavigationPanelNode: ChatControllerCustomNavigationPanelNode? { get } + + var visibleContextController: ViewController? { get } + func updatePresentationMode(_ mode: ChatControllerPresentationMode) func beginMessageSearch(_ query: String) func displayPromoAnnouncement(text: String) diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 609f9c2608..9d0c54d93c 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -85,7 +85,7 @@ final class CameraDeviceContext { } private func maxDimensions(additional: Bool, preferWide: Bool) -> CMVideoDimensions { - if self.isRoundVideo && !Camera.isDualCameraSupported { + if self.isRoundVideo && self.exclusive { return CMVideoDimensions(width: 640, height: 480) } else { if additional || preferWide { diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index ab889bf248..6f5c21942a 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -95,7 +95,9 @@ final class CameraOutput: NSObject { private var roundVideoFilter: CameraRoundVideoFilter? private let semaphore = DispatchSemaphore(value: 1) - private let queue = DispatchQueue(label: "") + private let videoQueue = DispatchQueue(label: "", qos: .userInitiated) + private let audioQueue = DispatchQueue(label: "") + private let metadataQueue = DispatchQueue(label: "") private var photoCaptureRequests: [Int64: PhotoCaptureContext] = [:] @@ -114,7 +116,7 @@ final class CameraOutput: NSObject { self.isVideoMessage = use32BGRA super.init() - + if #available(iOS 13.0, *) { self.photoOutput.maxPhotoQualityPrioritization = .balanced } @@ -135,13 +137,13 @@ final class CameraOutput: NSObject { } else { session.session.addOutput(self.videoOutput) } - self.videoOutput.setSampleBufferDelegate(self, queue: self.queue) + self.videoOutput.setSampleBufferDelegate(self, queue: self.videoQueue) } else { Logger.shared.log("Camera", "Can't add video output") } if audio, session.session.canAddOutput(self.audioOutput) { session.session.addOutput(self.audioOutput) - self.audioOutput.setSampleBufferDelegate(self, queue: self.queue) + self.audioOutput.setSampleBufferDelegate(self, queue: self.audioQueue) } if photo, session.session.canAddOutput(self.photoOutput) { if session.hasMultiCam { @@ -305,6 +307,8 @@ final class CameraOutput: NSObject { return .complete() } + Logger.shared.log("CameraOutput", "startRecording") + self.currentMode = mode self.lastSampleTimestamp = nil self.captureOrientation = orientation @@ -449,18 +453,19 @@ final class CameraOutput: NSObject { transitionFactor = 1.0 - max(0.0, (currentTimestamp - self.lastSwitchTimestamp) / duration) } } - if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: fromAdditionalOutput, transitionFactor: transitionFactor) { - let presentationTime = CMSampleBufferGetPresentationTimeStamp(processedSampleBuffer) - if let lastSampleTimestamp = self.lastSampleTimestamp, lastSampleTimestamp > presentationTime { - - } else { - if (transitionFactor == 1.0 && fromAdditionalOutput) || (transitionFactor == 0.0 && !fromAdditionalOutput) || (transitionFactor > 0.0 && transitionFactor < 1.0) { + + if (transitionFactor == 1.0 && fromAdditionalOutput) + || (transitionFactor == 0.0 && !fromAdditionalOutput) + || (transitionFactor > 0.0 && transitionFactor < 1.0) { + if let processedSampleBuffer = self.processRoundVideoSampleBuffer(sampleBuffer, additional: fromAdditionalOutput, transitionFactor: transitionFactor) { + let presentationTime = CMSampleBufferGetPresentationTimeStamp(processedSampleBuffer) + if let lastSampleTimestamp = self.lastSampleTimestamp, lastSampleTimestamp > presentationTime { + + } else { videoRecorder.appendSampleBuffer(processedSampleBuffer) self.lastSampleTimestamp = presentationTime } } - } else { - videoRecorder.appendSampleBuffer(sampleBuffer) } } else { var additional = self.currentPosition == .front @@ -518,7 +523,7 @@ final class CameraOutput: NSObject { return nil } self.semaphore.wait() - + let mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription) let extensions = CMFormatDescriptionGetExtensions(formatDescription) as! [String: Any] @@ -528,6 +533,7 @@ final class CameraOutput: NSObject { var newFormatDescription: CMFormatDescription? var status = CMVideoFormatDescriptionCreate(allocator: nil, codecType: mediaSubType, width: videoMessageDimensions.width, height: videoMessageDimensions.height, extensions: updatedExtensions as CFDictionary, formatDescriptionOut: &newFormatDescription) guard status == noErr, let newFormatDescription else { + self.semaphore.signal() return nil } @@ -539,8 +545,9 @@ final class CameraOutput: NSObject { self.roundVideoFilter = filter } if !filter.isPrepared { - filter.prepare(with: newFormatDescription, outputRetainedBufferCountHint: 3) + filter.prepare(with: newFormatDescription, outputRetainedBufferCountHint: 4) } + guard let newPixelBuffer = filter.render(pixelBuffer: videoPixelBuffer, additional: additional, captureOrientation: self.captureOrientation, transitionFactor: transitionFactor) else { self.semaphore.signal() return nil @@ -592,7 +599,7 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA guard CMSampleBufferDataIsReady(sampleBuffer) else { return } - + if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { self.processSampleBuffer?(sampleBuffer, videoPixelBuffer, connection) } else { @@ -607,7 +614,9 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA } func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - + if #available(iOS 13.0, *) { + Logger.shared.log("VideoRecorder", "Dropped sample buffer \(sampleBuffer.attachments)") + } } } diff --git a/submodules/Camera/Sources/VideoRecorder.swift b/submodules/Camera/Sources/VideoRecorder.swift index 1d5f9352cc..e785db1834 100644 --- a/submodules/Camera/Sources/VideoRecorder.swift +++ b/submodules/Camera/Sources/VideoRecorder.swift @@ -40,11 +40,12 @@ private final class VideoRecorderImpl { private var pendingAudioSampleBuffers: [CMSampleBuffer] = [] - private var _duration: CMTime = .zero + private var _duration = Atomic(value: .zero) public var duration: CMTime { - self.queue.sync { _duration } + return self._duration.with { $0 } } + private var startedSession = false private var lastVideoSampleTime: CMTime = .invalid private var recordingStartSampleTime: CMTime = .invalid private var recordingStopSampleTime: CMTime = .invalid @@ -59,7 +60,11 @@ private final class VideoRecorderImpl { private let error = Atomic(value: nil) - private var stopped = false + private var _stopped = Atomic(value: false) + private var stopped: Bool { + return self._stopped.with { $0 } + } + private var hasAllVideoBuffers = false private var hasAllAudioBuffers = false @@ -113,20 +118,21 @@ private final class VideoRecorderImpl { } } + + private var previousPresentationTime: Double? + private var previousAppendTime: Double? + public func appendVideoSampleBuffer(_ sampleBuffer: CMSampleBuffer) { - if let _ = self.hasError() { - return - } - - guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Video else { - return - } - - let presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) self.queue.async { - guard !self.stopped && self.error.with({ $0 }) == nil else { + guard self.hasError() == nil && !self.stopped else { return } + + guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Video else { + return + } + let presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + var failed = false if self.videoInput == nil { Logger.shared.log("VideoRecorder", "Try adding video input") @@ -159,36 +165,56 @@ private final class VideoRecorderImpl { return } if self.videoInput != nil && (self.audioInput != nil || !self.configuration.hasAudio) { + print("startWriting") + let start = CACurrentMediaTime() if !self.assetWriter.startWriting() { if let error = self.assetWriter.error { self.transitionToFailedStatus(error: .avError(error)) - return } } - - self.assetWriter.startSession(atSourceTime: presentationTime) - self.recordingStartSampleTime = presentationTime - self.lastVideoSampleTime = presentationTime + print("started In \(CACurrentMediaTime() - start)") + return } + } else if self.assetWriter.status == .writing && !self.startedSession { + print("Started session at \(presentationTime)") + self.assetWriter.startSession(atSourceTime: presentationTime) + self.recordingStartSampleTime = presentationTime + self.lastVideoSampleTime = presentationTime + self.startedSession = true } if self.recordingStartSampleTime == .invalid || sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { return } - if self.assetWriter.status == .writing { + if self.assetWriter.status == .writing && self.startedSession { if self.recordingStopSampleTime != .invalid && sampleBuffer.presentationTimestamp > self.recordingStopSampleTime { self.hasAllVideoBuffers = true self.maybeFinish() return } - - if let videoInput = self.videoInput, videoInput.isReadyForMoreMediaData { + + if let videoInput = self.videoInput { + while (!videoInput.isReadyForMoreMediaData) + { + let maxDate = Date(timeIntervalSinceNow: 0.05) + RunLoop.current.run(until: maxDate) + } + } + + if let videoInput = self.videoInput { + let time = CACurrentMediaTime() + if let previousPresentationTime = self.previousPresentationTime, let previousAppendTime = self.previousAppendTime { + print("appending \(presentationTime.seconds) (\(presentationTime.seconds - previousPresentationTime) ) on \(time) (\(time - previousAppendTime)") + } + self.previousPresentationTime = presentationTime.seconds + self.previousAppendTime = time + if videoInput.append(sampleBuffer) { self.lastVideoSampleTime = presentationTime let startTime = self.recordingStartSampleTime let duration = presentationTime - startTime - self._duration = duration + let _ = self._duration.modify { _ in return duration } } if !self.savedTransitionImage, let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { @@ -220,16 +246,12 @@ private final class VideoRecorderImpl { } public func appendAudioSampleBuffer(_ sampleBuffer: CMSampleBuffer) { - if let _ = self.hasError() { - return - } - - guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Audio else { - return - } - self.queue.async { - guard !self.stopped && self.error.with({ $0 }) == nil else { + guard self.hasError() == nil && !self.stopped else { + return + } + + guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer), CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Audio else { return } @@ -274,7 +296,7 @@ private final class VideoRecorderImpl { return } - if self.recordingStartSampleTime != .invalid { //self.assetWriter.status == .writing { + if self.recordingStartSampleTime != .invalid { if sampleBuffer.presentationTimestamp < self.recordingStartSampleTime { return } @@ -304,7 +326,7 @@ private final class VideoRecorderImpl { } return } - self.stopped = true + let _ = self._stopped.modify { _ in return true } self.pendingAudioSampleBuffers = [] if self.assetWriter.status == .writing { self.assetWriter.cancelWriting() @@ -318,7 +340,7 @@ private final class VideoRecorderImpl { } public var isRecording: Bool { - self.queue.sync { !(self.hasAllVideoBuffers && self.hasAllAudioBuffers) } + return !self.stopped } public func stopRecording() { @@ -334,60 +356,58 @@ private final class VideoRecorderImpl { } } - public func maybeFinish() { - self.queue.async { - guard self.hasAllVideoBuffers && self.hasAllVideoBuffers else { - return - } - self.stopped = true - self.finish() + private func maybeFinish() { + dispatchPrecondition(condition: .onQueue(self.queue)) + guard self.hasAllVideoBuffers && self.hasAllVideoBuffers && !self.stopped else { + return } + let _ = self._stopped.modify { _ in return true } + self.finish() } - public func finish() { - self.queue.async { - let completion = self.completion - if self.recordingStopSampleTime == .invalid { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + private func finish() { + dispatchPrecondition(condition: .onQueue(self.queue)) + let completion = self.completion + if self.recordingStopSampleTime == .invalid { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if let _ = self.error.with({ $0 }) { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + return + } + + if let _ = self.error.with({ $0 }) { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if !self.tryAppendingPendingAudioBuffers() { - DispatchQueue.main.async { - completion(false, nil, nil) - } - return + return + } + + if !self.tryAppendingPendingAudioBuffers() { + DispatchQueue.main.async { + completion(false, nil, nil) } - - if self.assetWriter.status == .writing { - self.assetWriter.finishWriting { - if let _ = self.assetWriter.error { - DispatchQueue.main.async { - completion(false, nil, nil) - } - } else { - DispatchQueue.main.async { - completion(true, self.transitionImage, self.positionChangeTimestamps) - } + return + } + + if self.assetWriter.status == .writing { + self.assetWriter.finishWriting { + if let _ = self.assetWriter.error { + DispatchQueue.main.async { + completion(false, nil, nil) + } + } else { + DispatchQueue.main.async { + completion(true, self.transitionImage, self.positionChangeTimestamps) } } - } else if let _ = self.assetWriter.error { - DispatchQueue.main.async { - completion(false, nil, nil) - } - } else { - DispatchQueue.main.async { - completion(false, nil, nil) - } + } + } else if let _ = self.assetWriter.error { + DispatchQueue.main.async { + completion(false, nil, nil) + } + } else { + DispatchQueue.main.async { + completion(false, nil, nil) } } } @@ -423,7 +443,13 @@ private final class VideoRecorderImpl { } private func internalAppendAudioSampleBuffer(_ sampleBuffer: CMSampleBuffer) -> Bool { - if let audioInput = self.audioInput, audioInput.isReadyForMoreMediaData { + if self.startedSession, let audioInput = self.audioInput { + while (!audioInput.isReadyForMoreMediaData) + { + let maxDate = Date(timeIntervalSinceNow: 0.05) + RunLoop.current.run(until: maxDate) + } + if !audioInput.append(sampleBuffer) { if let _ = self.assetWriter.error { return false diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index c43de99600..e589429076 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -881,7 +881,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) }, - requiresPremiumForMessaging: requiresPremiumForMessaging + requiresPremiumForMessaging: requiresPremiumForMessaging, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): @@ -3743,7 +3744,8 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) case .media: return nil diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index 53f89b3dea..865bdef36e 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -208,7 +208,8 @@ public final class ChatListShimmerNode: ASDisplayNode { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index e2eb846261..7b6b145eab 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -97,6 +97,7 @@ public enum ChatListItemContent { public var autoremoveTimeout: Int32? public var storyState: StoryState? public var requiresPremiumForMessaging: Bool + public var displayAsTopicList: Bool public init( messages: [EngineMessage], @@ -117,7 +118,8 @@ public enum ChatListItemContent { topForumTopicItems: [EngineChatList.ForumTopicData], autoremoveTimeout: Int32?, storyState: StoryState?, - requiresPremiumForMessaging: Bool + requiresPremiumForMessaging: Bool, + displayAsTopicList: Bool ) { self.messages = messages self.peer = peer @@ -138,6 +140,7 @@ public enum ChatListItemContent { self.autoremoveTimeout = autoremoveTimeout self.storyState = storyState self.requiresPremiumForMessaging = requiresPremiumForMessaging + self.displayAsTopicList = displayAsTopicList } } @@ -1417,11 +1420,17 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else if peer.isDeleted { overrideImage = .deletedIcon } - var isForum = false + var isForumAvatar = false if case let .channel(channel) = peer, channel.flags.contains(.isForum) { - isForum = true + isForumAvatar = true } - self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: isForum ? .roundedRect : .round, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0)) + if case let .peer(data) = item.content { + if data.displayAsTopicList { + isForumAvatar = true + } + } + + self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: isForumAvatar ? .roundedRect : .round, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0)) if peer.isPremium && peer.id != item.context.account.peerId { let context = item.context diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 01fc0c5016..2c11365dd2 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -427,7 +427,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL hasUnseenCloseFriends: storyState.hasUnseenCloseFriends ) }, - requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging + requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, + displayAsTopicList: peerEntry.displayAsTopicList )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -796,7 +797,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL hasUnseenCloseFriends: storyState.hasUnseenCloseFriends ) }, - requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging + requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, + displayAsTopicList: peerEntry.displayAsTopicList )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -1278,6 +1280,8 @@ public final class ChatListNode: ListView { public let isMainTab = ValuePromise(false, ignoreRepeated: true) private let suggestedChatListNotice = Promise(nil) + public var synchronousDrawingWhenNotAnimated: Bool = false + public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool, autoSetReady: Bool, isMainTab: Bool?) { self.context = context self.location = location @@ -1977,39 +1981,6 @@ public final class ChatListNode: ListView { } let currentPeerId: EnginePeer.Id = context.account.peerId - - /*let contactList: Signal - if case let .chatList(appendContacts) = mode, appendContacts { - contactList = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Contacts.List(includePresences: true)) - |> map(Optional.init) - } else { - contactList = .single(nil) - } - let _ = contactList*/ - - - /*let emptyInitialView = ChatListNodeView( - originalList: EngineChatList( - items: [], - groupItems: [], - additionalItems: [], - hasEarlier: false, - hasLater: false, - isLoading: false - ), - filteredEntries: [ChatListNodeEntry.HeaderEntry], - isLoading: false, - filter: nil - ) - let _ = previousView.swap(emptyInitialView) - - let _ = (preparedChatListNodeViewTransition(from: nil, to: emptyInitialView, reason: .initial, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: nil, searchMode: false) - |> map { mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: nil, mode: mode, isPeerEnabled: nil, transition: $0) }).start(next: { [weak self] value in - guard let self else { - return - } - let _ = self.enqueueTransition(value).start() - })*/ let contacts: Signal<[ChatListContactPeer], NoError> if case .chatList(groupId: .root) = location, chatListFilter == nil, case .chatList = mode { @@ -3414,7 +3385,7 @@ public final class ChatListNode: ListView { var options = transition.options //options.insert(.Synchronous) - if self.view.window != nil { + if self.view.window != nil || self.synchronousDrawingWhenNotAnimated { if !options.contains(.AnimateInsertion) { options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousResourceLoading) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 5bd0ba108b..d8fc0cac03 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -115,6 +115,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { var revealed: Bool var storyState: ChatListNodeState.StoryState? var requiresPremiumForMessaging: Bool + var displayAsTopicList: Bool init( index: EngineChatList.Item.Index, @@ -140,7 +141,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { topForumTopicItems: [EngineChatList.ForumTopicData], revealed: Bool, storyState: ChatListNodeState.StoryState?, - requiresPremiumForMessaging: Bool + requiresPremiumForMessaging: Bool, + displayAsTopicList: Bool ) { self.index = index self.presentationData = presentationData @@ -166,6 +168,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { self.revealed = revealed self.storyState = storyState self.requiresPremiumForMessaging = requiresPremiumForMessaging + self.displayAsTopicList = displayAsTopicList } static func ==(lhs: PeerEntryData, rhs: PeerEntryData) -> Bool { @@ -281,6 +284,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { if lhs.requiresPremiumForMessaging != rhs.requiresPremiumForMessaging { return false } + if lhs.displayAsTopicList != rhs.displayAsTopicList { + return false + } return true } } @@ -708,7 +714,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, hasUnseenCloseFriends: stats.hasUnseenCloseFriends ) }, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: entry.displayAsTopicList )) if let threadInfo, threadInfo.isHidden { @@ -759,7 +766,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, topForumTopicItems: [], revealed: false, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false ))) if foundPinningIndex != 0 { foundPinningIndex -= 1 @@ -791,7 +799,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, topForumTopicItems: [], revealed: false, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false ))) } else { if !filteredAdditionalItemEntries.isEmpty { @@ -843,7 +852,8 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, topForumTopicItems: item.item.topForumTopicItems, revealed: state.hiddenItemShouldBeTemporaryRevealed || state.editing, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false ))) if pinningIndex != 0 { pinningIndex -= 1 diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 1e7e66a198..dd0101c727 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -114,6 +114,8 @@ public func chatListFilterPredicate(filter: ChatListFilterData, accountPeerId: E } func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account) -> Signal { + let accountPeerId = account.peerId + switch chatListLocation { case let .chatList(groupId): let filterPredicate: ChatListFilterPredicate? @@ -129,7 +131,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat signal = account.viewTracker.tailChatListView(groupId: groupId._asGroup(), filterPredicate: filterPredicate, count: count) return signal |> map { view, updateType -> ChatListNodeViewUpdate in - return ChatListNodeViewUpdate(list: EngineChatList(view), type: updateType, scrollPosition: nil) + return ChatListNodeViewUpdate(list: EngineChatList(view, accountPeerId: accountPeerId), type: updateType, scrollPosition: nil) } case let .navigation(index, _): guard case let .chatList(index) = index else { @@ -145,7 +147,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat } else { genericType = updateType } - return ChatListNodeViewUpdate(list: EngineChatList(view), type: genericType, scrollPosition: nil) + return ChatListNodeViewUpdate(list: EngineChatList(view, accountPeerId: accountPeerId), type: genericType, scrollPosition: nil) } case let .scroll(index, sourceIndex, scrollPosition, animated, _): guard case let .chatList(index) = index else { @@ -165,7 +167,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat } else { genericType = updateType } - return ChatListNodeViewUpdate(list: EngineChatList(view), type: genericType, scrollPosition: scrollPosition) + return ChatListNodeViewUpdate(list: EngineChatList(view, accountPeerId: accountPeerId), type: genericType, scrollPosition: scrollPosition) } } case let .forum(peerId): @@ -292,7 +294,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat hasFailed: false, isContact: false, autoremoveTimeout: nil, - storyStats: nil + storyStats: nil, + displayAsTopicList: false )) } @@ -356,7 +359,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat hasFailed: false, isContact: false, autoremoveTimeout: nil, - storyStats: nil + storyStats: nil, + displayAsTopicList: false )) } diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index af47a20091..ef5b52afd9 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -724,6 +724,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo controller.premiumReactionsSelected?() } } + + reactionContextNode.updateLayout(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: 0.0, right: layout.safeInsets.right), anchorRect: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: CGSize(width: 1.0, height: 1.0)), isCoveredByInput: false, isAnimatingOut: false, transition: .immediate) } contentTopInset += reactionContextNode.contentHeight + 18.0 } else if let reactionContextNode = self.reactionContextNode { @@ -751,7 +753,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo case let .location(location): if let transitionInfo = location.transitionInfo() { contentRect = CGRect(origin: transitionInfo.location, size: CGSize(width: 1.0, height: 1.0)) - contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minX), size: CGSize(width: layout.size.width, height: contentRect.height)) + contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minY), size: CGSize(width: layout.size.width, height: contentRect.height)) } else { return } @@ -759,7 +761,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let transitionInfo = reference.transitionInfo() { contentRect = convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view).insetBy(dx: -2.0, dy: 0.0) contentRect.size.width += 5.0 - contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minX), size: CGSize(width: layout.size.width, height: contentRect.height)) + contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minY), size: CGSize(width: layout.size.width, height: contentRect.height)) } else { return } diff --git a/submodules/ContextUI/Sources/ContextSourceContainer.swift b/submodules/ContextUI/Sources/ContextSourceContainer.swift index d6c7ec6e7e..d027596dfa 100644 --- a/submodules/ContextUI/Sources/ContextSourceContainer.swift +++ b/submodules/ContextUI/Sources/ContextSourceContainer.swift @@ -377,7 +377,10 @@ final class ContextSourceContainer: ASDisplayNode { super.init() + #if DEBUG + #else self.addSubnode(self.backgroundNode) + #endif for i in 0 ..< configuration.sources.count { let source = configuration.sources[i] diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index a5b8eefe16..86fd7ffcc1 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -994,6 +994,37 @@ public extension ContainedViewLayoutTransition { } } + func updateTintColor(view: UIView, color: UIColor, completion: ((Bool) -> Void)? = nil) { + if let current = view.layer.layerTintColor, UIColor(cgColor: current) == color { + completion?(true) + return + } + + switch self { + case .immediate: + view.tintColor = color + view.layer.layerTintColor = color.cgColor + completion?(true) + case let .animated(duration, curve): + let previousColor: CGColor = view.layer.layerTintColor ?? UIColor.clear.cgColor + view.tintColor = color + view.layer.layerTintColor = color.cgColor + + view.layer.animate( + from: previousColor, + to: color.cgColor, + keyPath: "contentsMultiplyColor", + timingFunction: curve.timingFunction, + duration: duration, + delay: 0.0, + mediaTimingFunction: curve.mediaTimingFunction, + removeOnCompletion: true, + additive: false, + completion: completion + ) + } + } + func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)? = nil) { if layer.contentsRect == contentsRect { if let completion = completion { diff --git a/submodules/Display/Source/HapticFeedback.swift b/submodules/Display/Source/HapticFeedback.swift index a0a714adaf..63b07ac513 100644 --- a/submodules/Display/Source/HapticFeedback.swift +++ b/submodules/Display/Source/HapticFeedback.swift @@ -34,32 +34,11 @@ private final class HapticFeedbackImpl { }() private lazy var selectionGenerator: UISelectionFeedbackGenerator? = { - let generator = UISelectionFeedbackGenerator() - generator.prepare() - var string = generator.debugDescription - string.removeLast() - let number = string.suffix(1) - if number == "1" { - return generator - } else { - if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { - return generator - } - return nil - } + return UISelectionFeedbackGenerator() }() private lazy var notificationGenerator: UINotificationFeedbackGenerator? = { - let generator = UINotificationFeedbackGenerator() - generator.prepare() - var string = generator.debugDescription - string.removeLast() - let number = string.suffix(1) - if number == "1" { - return generator - } else { - return nil - } + return UINotificationFeedbackGenerator() }() func prepareTap() { diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 1ddb6767cf..a6d3392043 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -493,6 +493,8 @@ open class NavigationBar: ASDisplayNode { public var makeCustomTransitionNode: ((NavigationBar, Bool) -> CustomNavigationTransitionNode?)? public var allowsCustomTransition: (() -> Bool)? + public var customSetContentNode: ((NavigationBarContentNode?, Bool) -> Void)? + private var collapsed: Bool { get { return self.frame.size.height.isLess(than: 44.0) @@ -1649,6 +1651,11 @@ open class NavigationBar: ASDisplayNode { } public func setContentNode(_ contentNode: NavigationBarContentNode?, animated: Bool) { + if let customSetContentNode = self.customSetContentNode { + customSetContentNode(contentNode, animated) + return + } + if self.contentNode !== contentNode { if let previous = self.contentNode { if animated { diff --git a/submodules/Display/Source/NavigationButtonNode.swift b/submodules/Display/Source/NavigationButtonNode.swift index fe281a6211..ea05895c76 100644 --- a/submodules/Display/Source/NavigationButtonNode.swift +++ b/submodules/Display/Source/NavigationButtonNode.swift @@ -500,7 +500,7 @@ public final class NavigationButtonNode: ContextControllerSourceNode { var totalHeight: CGFloat = 0.0 for i in 0 ..< self.nodes.count { if i != 0 { - nodeOrigin.x += 10.0 + nodeOrigin.x += 15.0 } let node = self.nodes[i] diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 3a37963f16..aa2b834f6e 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -566,6 +566,9 @@ final class MutableChatListView { private var currentHiddenPeerIds = Set() + private let displaySavedMessagesAsTopicListPreferencesKey: ValueBoxKey + private(set) var displaySavedMessagesAsTopicList: PreferencesEntry? + init(postbox: PostboxImpl, currentTransaction: Transaction, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents) { self.groupId = groupId self.filterPredicate = filterPredicate @@ -574,6 +577,8 @@ final class MutableChatListView { self.currentHiddenPeerIds = postbox.hiddenChatIds + self.displaySavedMessagesAsTopicListPreferencesKey = postbox.seedConfiguration.displaySavedMessagesAsTopicListPreferencesKey + var spaces: [ChatListViewSpace] = [ .group(groupId: self.groupId, pinned: .notPinned, predicate: filterPredicate) ] @@ -612,6 +617,8 @@ final class MutableChatListView { } else { self.groupEntries = [] } + + self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey) } private func reloadGroups(postbox: PostboxImpl) { @@ -689,6 +696,8 @@ final class MutableChatListView { } } } + + self.displaySavedMessagesAsTopicList = postbox.preferencesTable.get(key: self.displaySavedMessagesAsTopicListPreferencesKey) } func refreshDueToExternalTransaction(postbox: PostboxImpl, currentTransaction: Transaction) -> Bool { @@ -699,12 +708,16 @@ final class MutableChatListView { updated = true let currentGroupEntries = self.groupEntries + let currentDisplaySavedMessagesAsTopicList = self.displaySavedMessagesAsTopicList self.reloadGroups(postbox: postbox) if self.groupEntries != currentGroupEntries { updated = true } + if self.displaySavedMessagesAsTopicList != currentDisplaySavedMessagesAsTopicList { + updated = true + } return updated } @@ -734,6 +747,20 @@ final class MutableChatListView { } } + if !transaction.currentPreferencesOperations.isEmpty { + for operation in transaction.currentPreferencesOperations { + switch operation { + case let .update(key, value): + if key == self.displaySavedMessagesAsTopicListPreferencesKey { + if self.displaySavedMessagesAsTopicList != value { + self.displaySavedMessagesAsTopicList = value + hasChanges = true + } + } + } + } + } + if case .root = self.groupId, self.filterPredicate == nil { var invalidatedGroups = false for (groupId, groupOperations) in operations { @@ -938,6 +965,7 @@ public final class ChatListView { public let groupEntries: [ChatListGroupReferenceEntry] public let earlierIndex: ChatListIndex? public let laterIndex: ChatListIndex? + public let displaySavedMessagesAsTopicList: PreferencesEntry? init(_ mutableView: MutableChatListView) { self.groupId = mutableView.groupId @@ -1006,5 +1034,6 @@ public final class ChatListView { } self.additionalItemEntries = additionalItemEntries + self.displaySavedMessagesAsTopicList = mutableView.displaySavedMessagesAsTopicList } } diff --git a/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift b/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift index 2ec1ad130c..ae2ca6183f 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift @@ -173,7 +173,9 @@ class MessageHistoryTagsSummaryTable: Table { } func getCustomTags(tag: MessageTags, peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace) -> [MemoryBuffer] { - let peerKey = self.keyInternal(key: MessageHistoryTagsSummaryKey(tag: tag, peerId: peerId, threadId: threadId, namespace: namespace, customTag: nil), allowShared: false) + let key = MessageHistoryTagsSummaryKey(tag: tag, peerId: peerId, threadId: threadId, namespace: namespace, customTag: nil) + + let peerKey = self.keyInternal(key: key, allowShared: false) let prefixLength = 4 + 8 + 4 + 8 var result: [MemoryBuffer] = [] self.valueBox.range(self.table, start: peerKey.predecessor, end: peerKey.successor, keys: { key in @@ -187,6 +189,17 @@ class MessageHistoryTagsSummaryTable: Table { } return true }, limit: 0) + + for updatedKey in self.updatedKeys { + if updatedKey.peerId == peerId && updatedKey.tag == tag && updatedKey.threadId == threadId && updatedKey.namespace == namespace { + if let customTag = updatedKey.customTag { + if !result.contains(customTag) { + result.append(customTag) + } + } + } + } + return result } diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index 4e7bd89a52..c5cb3a70c2 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -110,7 +110,7 @@ private extension MessageHistoryInput { assert(Set(items.map({ $0.stableId })).count == items.count) if items.count > limit { - let overLimit = limit - items.count + let overLimit = items.count - limit switch direction { case .lowToHigh: items.removeFirst(overLimit) diff --git a/submodules/Postbox/Sources/SeedConfiguration.swift b/submodules/Postbox/Sources/SeedConfiguration.swift index f389647aa2..66e0d82094 100644 --- a/submodules/Postbox/Sources/SeedConfiguration.swift +++ b/submodules/Postbox/Sources/SeedConfiguration.swift @@ -80,6 +80,7 @@ public final class SeedConfiguration { public let isPeerUpgradeMessage: (Message) -> Bool public let automaticThreadIndexInfo: (PeerId, Int64) -> StoredMessageHistoryThreadInfo? public let customTagsFromAttributes: ([MessageAttribute]) -> [MemoryBuffer] + public let displaySavedMessagesAsTopicListPreferencesKey: ValueBoxKey public init( globalMessageIdsPeerIdNamespaces: Set, @@ -109,7 +110,8 @@ public final class SeedConfiguration { decodeDisplayPeerAsRegularChat: @escaping (CachedPeerData) -> Bool, isPeerUpgradeMessage: @escaping (Message) -> Bool, automaticThreadIndexInfo: @escaping (PeerId, Int64) -> StoredMessageHistoryThreadInfo?, - customTagsFromAttributes: @escaping ([MessageAttribute]) -> [MemoryBuffer] + customTagsFromAttributes: @escaping ([MessageAttribute]) -> [MemoryBuffer], + displaySavedMessagesAsTopicListPreferencesKey: ValueBoxKey ) { self.globalMessageIdsPeerIdNamespaces = globalMessageIdsPeerIdNamespaces self.initializeChatListWithHole = initializeChatListWithHole @@ -135,5 +137,6 @@ public final class SeedConfiguration { self.isPeerUpgradeMessage = isPeerUpgradeMessage self.automaticThreadIndexInfo = automaticThreadIndexInfo self.customTagsFromAttributes = customTagsFromAttributes + self.displaySavedMessagesAsTopicListPreferencesKey = displaySavedMessagesAsTopicListPreferencesKey } } diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index f9e693c813..3d329a577c 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -295,7 +295,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 29a2264c0d..4e664b9040 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -442,7 +442,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 7c33a21936..90c8435a2f 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -1458,7 +1458,8 @@ private func threadList(accountPeerId: EnginePeer.Id, postbox: Postbox, peerId: hasFailed: false, isContact: false, autoremoveTimeout: nil, - storyStats: nil + storyStats: nil, + displayAsTopicList: false )) } diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index 7b1431d82f..0c9564781f 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -960,6 +960,17 @@ public final class ManagedAudioSession: NSObject { try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none) } + if case let .record(speaker, _) = type, !speaker, let input = AVAudioSession.sharedInstance().availableInputs?.first { + if let dataSources = input.dataSources { + for source in dataSources { + if source.dataSourceName.contains("Front") { + try? input.setPreferredDataSource(source) + break + } + } + } + } + if resetToBuiltin { var updatedType = type if case .record(false, let withOthers) = updatedType, self.isHeadsetPluggedInValue { diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index c0040a2c68..79351361b7 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -1136,7 +1136,8 @@ public func _internal_searchForumTopics(account: Account, peerId: EnginePeer.Id, hasFailed: false, isContact: false, autoremoveTimeout: nil, - storyStats: nil + storyStats: nil, + displayAsTopicList: false )) } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 58a92dd8ca..c61b94706a 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -279,6 +279,7 @@ private enum PreferencesKeyValues: Int32 { case storiesConfiguration = 32 case audioTranscriptionTrialState = 33 case didCacheSavedMessageTagsPrefix = 34 + case displaySavedChatsAsTopics = 35 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -456,6 +457,12 @@ public struct PreferencesKeys { key.setInt64(4, value: threadId ?? 0) return key } + + public static func displaySavedChatsAsTopics() -> ValueBoxKey { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.displaySavedChatsAsTopics.rawValue) + return key + } } private enum SharedDataKeyValues: Int32 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index 2932141570..94bc0cd63d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -235,7 +235,8 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { } return result - } + }, + displaySavedMessagesAsTopicListPreferencesKey: PreferencesKeys.displaySavedChatsAsTopics() ) }() diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index d24b4d1840..9b3049aac8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -18,6 +18,14 @@ public enum EnginePeerCachedInfoItem { } } +public struct EngineDisplaySavedChatsAsTopics: Codable, Equatable { + public var value: Bool + + public init(value: Bool) { + self.value = value + } +} + extension EnginePeerCachedInfoItem: Equatable where T: Equatable { public static func ==(lhs: EnginePeerCachedInfoItem, rhs: EnginePeerCachedInfoItem) -> Bool { switch lhs { @@ -1245,5 +1253,28 @@ public extension TelegramEngine.EngineData.Item { } } + public struct DisplaySavedChatsAsTopics: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = Bool + + public init() { + } + + var key: PostboxViewKey { + return .preferences(keys: Set([PreferencesKeys.displaySavedChatsAsTopics()])) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? PreferencesView else { + preconditionFailure() + } + + if let value = view.values[PreferencesKeys.displaySavedChatsAsTopics()]?.get(EngineDisplaySavedChatsAsTopics.self) { + return value.value + } else { + return false + } + } + } + } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index 6a765d40c7..a0544481ee 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -129,6 +129,7 @@ public final class EngineChatList: Equatable { public let isContact: Bool public let autoremoveTimeout: Int32? public let storyStats: StoryStats? + public let displayAsTopicList: Bool public init( id: Id, @@ -147,7 +148,8 @@ public final class EngineChatList: Equatable { hasFailed: Bool, isContact: Bool, autoremoveTimeout: Int32?, - storyStats: StoryStats? + storyStats: StoryStats?, + displayAsTopicList: Bool ) { self.id = id self.index = index @@ -166,6 +168,7 @@ public final class EngineChatList: Equatable { self.isContact = isContact self.autoremoveTimeout = autoremoveTimeout self.storyStats = storyStats + self.displayAsTopicList = displayAsTopicList } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -220,6 +223,9 @@ public final class EngineChatList: Equatable { if lhs.storyStats != rhs.storyStats { return false } + if lhs.displayAsTopicList != rhs.displayAsTopicList { + return false + } return true } } @@ -419,7 +425,7 @@ public extension EngineChatList.RelativePosition { } extension EngineChatList.Item { - convenience init?(_ entry: ChatListEntry) { + convenience init?(_ entry: ChatListEntry, displayAsTopicList: Bool) { switch entry { case let .MessageEntry(entryData): let index = entryData.index @@ -504,7 +510,8 @@ extension EngineChatList.Item { hasFailed: hasFailed, isContact: isContact, autoremoveTimeout: autoremoveTimeout, - storyStats: entryData.storyStats + storyStats: entryData.storyStats, + displayAsTopicList: displayAsTopicList ) case .HoleEntry: return nil @@ -544,7 +551,7 @@ extension EngineChatList.AdditionalItem.PromoInfo { extension EngineChatList.AdditionalItem { convenience init?(_ entry: ChatListAdditionalItemEntry) { - guard let item = EngineChatList.Item(entry.entry) else { + guard let item = EngineChatList.Item(entry.entry, displayAsTopicList: false) else { return nil } guard let promoInfo = (entry.info as? PromoChatListItem).flatMap(EngineChatList.AdditionalItem.PromoInfo.init) else { @@ -555,14 +562,19 @@ extension EngineChatList.AdditionalItem { } public extension EngineChatList { - convenience init(_ view: ChatListView) { + convenience init(_ view: ChatListView, accountPeerId: PeerId) { var isLoading = false + + var displaySavedMessagesAsTopicList = false + if let value = view.displaySavedMessagesAsTopicList?.get(EngineDisplaySavedChatsAsTopics.self) { + displaySavedMessagesAsTopicList = value.value + } var items: [EngineChatList.Item] = [] loop: for entry in view.entries { switch entry { case .MessageEntry: - if let item = EngineChatList.Item(entry) { + if let item = EngineChatList.Item(entry, displayAsTopicList: entry.index.messageIndex.id.peerId == accountPeerId ? displaySavedMessagesAsTopicList : false) { items.append(item) } case .HoleEntry: diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 70c5e6c3b9..ec3fc8a859 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -328,9 +328,10 @@ public extension TelegramEngine { } public func chatList(group: EngineChatList.Group, count: Int) -> Signal { + let accountPeerId = self.account.peerId return self.account.postbox.tailChatListView(groupId: group._asGroup(), count: count, summaryComponents: ChatListEntrySummaryComponents()) |> map { view -> EngineChatList in - return EngineChatList(view.0) + return EngineChatList(view.0, accountPeerId: accountPeerId) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index beed309046..e11a85ab14 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -1346,6 +1346,14 @@ public extension TelegramEngine { |> distinctUntilChanged } } + + public func updateSavedMessagesViewAsTopics(value: Bool) { + let _ = (self.account.postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: PreferencesKeys.displaySavedChatsAsTopics(), { _ in + return PreferencesEntry(EngineDisplaySavedChatsAsTopics(value: value)) + }) + }).start() + } } } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index d88a54c12e..2fb1c51a1a 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -20,7 +20,9 @@ public enum PresentationResourceKey: Int32 { case navigationShareIcon case navigationSearchIcon case navigationCompactSearchIcon + case navigationCompactSearchWhiteIcon case navigationCompactTagsSearchIcon + case navigationCompactTagsSearchWhiteIcon case navigationCalendarIcon case navigationMoreIcon case navigationMoreCircledIcon diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift index ab1f46fa75..342eb23fc4 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift @@ -74,11 +74,23 @@ public struct PresentationResourcesRootController { }) } + public static func navigationCompactSearchWhiteIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationCompactSearchWhiteIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchIcon"), color: .white) + }) + } + public static func navigationCompactTagsSearchIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationCompactTagsSearchIcon.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigationSearchTagsIcon"), color: theme.rootController.navigationBar.accentTextColor) }) } + + public static func navigationCompactTagsSearchWhiteIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationCompactTagsSearchWhiteIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigationSearchTagsIcon"), color: .white) + }) + } public static func navigationCalendarIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationCalendarIcon.rawValue, { theme in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift index ae5c65eeda..4298a2546d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode/Sources/ChatMessageAttachedContentButtonNode.swift @@ -83,13 +83,13 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton }) } - public typealias AsyncLayout = (_ width: CGFloat, _ iconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ inProgress: Bool, _ drawBackground: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageAttachedContentButtonNode)) + public typealias AsyncLayout = (_ width: CGFloat, _ sideInset: CGFloat?, _ iconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ inProgress: Bool, _ drawBackground: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageAttachedContentButtonNode)) public static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> AsyncLayout { let previousRegularIconImage = current?.regularIconImage let maybeMakeTextLayout = (current?.textNode).flatMap(TextNode.asyncLayout) - return { width, iconImage, cornerIcon, title, titleColor, inProgress, drawBackground in + return { width, sideInset, iconImage, cornerIcon, title, titleColor, inProgress, drawBackground in let targetNode: ChatMessageAttachedContentButtonNode if let current = current { targetNode = current @@ -114,7 +114,7 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton iconWidth = iconImage.size.width + 5.0 } - let labelInset: CGFloat = 8.0 + let labelInset: CGFloat = sideInset ?? 8.0 let (textSize, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0 - iconWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets())) @@ -146,7 +146,7 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: refinedWidth, height: size.height)) - var textFrame = CGRect(origin: CGPoint(x: floor((refinedWidth - textSize.size.width) / 2.0), y: floor((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size) + var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((refinedWidth - textSize.size.width) / 2.0), y: floorToScreenPixels((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size) if drawBackground { textFrame.origin.y += 1.0 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 5c232753b6..7e507c47c6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -587,6 +587,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { let (buttonWidth, continueLayout) = makeActionButtonLayout( maxContentsWidth, + nil, buttonIconImage, cornerIcon, actionTitle, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift index 1e64524861..a5cb3a4368 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift @@ -307,7 +307,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { avatarPlaceholderColor = item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor } - let (messageButtonWidth, messageContinueLayout) = makeMessageButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Conversation_ContactMessage.uppercased(), mainColor, false, false) + let (messageButtonWidth, messageContinueLayout) = makeMessageButtonLayout(constrainedSize.width, 10.0, nil, false, item.presentationData.strings.Conversation_ContactMessage.uppercased(), mainColor, false, false) let addTitle: String if !canMessage && !canAdd { @@ -319,7 +319,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { addTitle = item.presentationData.strings.Conversation_ContactAddContactLong } } - let (addButtonWidth, addContinueLayout) = makeAddButtonLayout(constrainedSize.width, nil, false, addTitle.uppercased(), mainColor, false, false) + let (addButtonWidth, addContinueLayout) = makeAddButtonLayout(constrainedSize.width, 10.0, nil, false, addTitle.uppercased(), mainColor, false, false) let maxButtonWidth = max(messageButtonWidth, addButtonWidth) var maxContentWidth: CGFloat = avatarSize.width + 7.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index 55cbc75a21..5db10bc728 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -537,7 +537,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, titleColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor } - let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Chat_Giveaway_Message_LearnMore.uppercased(), titleColor, false, true) + let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, nil, false, item.presentationData.strings.Chat_Giveaway_Message_LearnMore.uppercased(), titleColor, false, true) let animationName: String let months = giveaway?.months ?? 0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index fe2584314f..a38eb33ace 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -1066,18 +1066,15 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { strongSelf.audioTranscriptionState = updatedAudioTranscriptionState } - /*switch updatedAudioTranscriptionState { + switch updatedAudioTranscriptionState { case .expanded: info?.setInvertOffsetDirection() default: - break - } - } else if strongSelf.isWaitingForCollapse { - strongSelf.isWaitingForCollapse = false - info?.setInvertOffsetDirection() - }*/ - - info?.setInvertOffsetDirection() + if strongSelf.isWaitingForCollapse { + strongSelf.isWaitingForCollapse = false + info?.setInvertOffsetDirection() + } + } if let consumableContentIcon = consumableContentIcon { if strongSelf.consumableContentNode.supernode == nil { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift index f0afff8d6e..5f71bfcce7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift @@ -427,15 +427,15 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { if let tagButton { buttons = [ self.deleteButton, - self.forwardButton, tagButton, + self.forwardButton, self.shareButton ] } else { buttons = [ self.deleteButton, - self.forwardButton, - self.shareButton + self.shareButton, + self.forwardButton ] } } else if !self.deleteButton.isHidden { @@ -459,17 +459,17 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { if let tagButton { buttons = [ self.deleteButton, - self.forwardButton, self.reportButton, tagButton, - self.shareButton + self.shareButton, + self.forwardButton ] } else { buttons = [ self.deleteButton, - self.forwardButton, self.reportButton, - self.shareButton + self.shareButton, + self.forwardButton ] } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift index b154236175..eb8e110712 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift @@ -48,7 +48,7 @@ public final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleCon } else { titleColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor } - let (buttonWidth, continueActionButtonLayout) = makeButtonLayout(constrainedSize.width, nil, false, presentationData.strings.Conversation_UpdateTelegram, titleColor, false, true) + let (buttonWidth, continueActionButtonLayout) = makeButtonLayout(constrainedSize.width, nil, nil, false, presentationData.strings.Conversation_UpdateTelegram, titleColor, false, true) let initialWidth = buttonWidth + insets.left + insets.right diff --git a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift index 89afc9f9d3..e559c37311 100644 --- a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift +++ b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift @@ -52,10 +52,10 @@ public final class MoreHeaderButton: HighlightableButtonNode { strongSelf.contextAction?(strongSelf.containerNode, gesture) } - self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0)) + self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 30.0, height: 44.0)) self.referenceNode.frame = self.containerNode.bounds - self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color) + //self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color) if let image = self.iconNode.image { self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) } @@ -72,11 +72,13 @@ public final class MoreHeaderButton: HighlightableButtonNode { private var content: Content? public func setContent(_ content: Content, animated: Bool = false) { if case .more = content { - let animationSize = CGSize(width: 22.0, height: 22.0) + let animationSize = CGSize(width: 30.0, height: 30.0) let _ = self.animationView.update( transition: .immediate, component: AnyComponent(LottieComponent( - content: LottieComponent.AppBundleContent(name: "anim_profilemore"), + content: LottieComponent.AppBundleContent( + name: "anim_moredots" + ), color: self.color )), environment: {}, @@ -119,13 +121,13 @@ public final class MoreHeaderButton: HighlightableButtonNode { if let animationComponentView = self.animationView.view { animationComponentView.isHidden = true } - case let .more(image): - if let image = image { + case .more: + /*if let image = image { self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) } self.iconNode.image = image - self.iconNode.isHidden = false + self.iconNode.isHidden = false*/ if let animationComponentView = self.animationView.view { animationComponentView.isHidden = false } @@ -143,13 +145,13 @@ public final class MoreHeaderButton: HighlightableButtonNode { if let animationComponentView = self.animationView.view { animationComponentView.isHidden = true } - case let .more(image): - if let image = image { + case .more: + /*if let image = image { self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) } self.iconNode.image = image - self.iconNode.isHidden = false + self.iconNode.isHidden = false*/ if let animationComponentView = self.animationView.view { animationComponentView.isHidden = false } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift index 32d33aeb28..9678cd3744 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift @@ -79,6 +79,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI autoSetReady: false, isMainTab: nil ) + self.chatListNode.synchronousDrawingWhenNotAnimated = true super.init() @@ -355,7 +356,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI public func updateSelectedMessages(animated: Bool) { } - public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) transition.updateFrame(node: self.chatListNode, frame: CGRect(origin: CGPoint(), size: size)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD index 6ff7241b51..067d915018 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/ComponentFlow", "//submodules/AppBundle", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode", + "//submodules/ContextUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift index 89d0a2a7f0..ba7225d538 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift @@ -10,6 +10,84 @@ import ComponentFlow import TelegramUIPreferences import AppBundle import PeerInfoPaneNode +import ContextUI + +private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNodeNavigationContentNode { + private struct Params: Equatable { + var width: CGFloat + var defaultHeight: CGFloat + var insets: UIEdgeInsets + + init(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets) { + self.width = width + self.defaultHeight = defaultHeight + self.insets = insets + } + } + + weak var chatController: ChatController? + let contentNode: NavigationBarContentNode + + var panelNode: ChatControllerCustomNavigationPanelNode? + private var appliedPanelNode: ChatControllerCustomNavigationPanelNode? + + private var params: Params? + + init(chatController: ChatController, contentNode: NavigationBarContentNode) { + self.chatController = chatController + self.contentNode = contentNode + + super.init() + + self.addSubnode(self.contentNode) + } + + func update(transition: ContainedViewLayoutTransition) { + if let params = self.params { + let _ = self.update(width: params.width, defaultHeight: params.defaultHeight, insets: params.insets, transition: transition) + } + } + + func update(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) -> CGFloat { + self.params = Params(width: width, defaultHeight: defaultHeight, insets: insets) + + let size = CGSize(width: width, height: defaultHeight) + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: size)) + self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) + + var contentHeight: CGFloat = size.height + 10.0 + + if self.appliedPanelNode !== self.panelNode { + if let previous = self.appliedPanelNode { + transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in + previous?.removeFromSupernode() + }) + } + + self.appliedPanelNode = self.panelNode + if let panelNode = self.panelNode, let chatController = self.chatController { + self.addSubnode(panelNode) + let panelLayout = panelNode.updateLayout(width: width, leftInset: insets.left, rightInset: insets.right, transition: .immediate, chatController: chatController) + let panelHeight = panelLayout.backgroundHeight + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) + panelNode.frame = panelFrame + panelNode.alpha = 0.0 + transition.updateAlpha(node: panelNode, alpha: 1.0) + + contentHeight += panelHeight - 1.0 + } + } else if let panelNode = self.panelNode, let chatController = self.chatController { + let panelLayout = panelNode.updateLayout(width: width, leftInset: insets.left, rightInset: insets.right, transition: transition, chatController: chatController) + let panelHeight = panelLayout.backgroundHeight + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) + transition.updateFrame(node: panelNode, frame: panelFrame) + + contentHeight += panelHeight - 1.0 + } + + return contentHeight + } +} public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private let context: AccountContext @@ -18,6 +96,8 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro private let chatController: ChatController + private let coveringView: UIView + public weak var parentController: ViewController? { didSet { if self.parentController !== oldValue { @@ -51,6 +131,12 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro public var tabBarOffset: CGFloat { return 0.0 } + + private var searchNavigationContentNode: SearchNavigationContentNode? + public var navigationContentNode: PeerInfoPanelNodeNavigationContentNode? { + return self.searchNavigationContentNode + } + public var externalDataUpdated: ((ContainedViewLayoutTransition) -> Void)? private var presentationData: PresentationData private var presentationDataDisposable: Disposable? @@ -61,10 +147,14 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.navigationController = navigationController self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.coveringView = UIView() + self.chatController = context.sharedContext.makeChatController(context: context, chatLocation: .replyThread(message: ChatReplyThreadMessage(peerId: context.account.peerId, threadId: peerId.toInt64(), channelMessageId: nil, isChannelPost: false, isForumPost: false, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)), subject: nil, botStart: nil, mode: .standard(.embedded(invertDirection: true))) super.init() + self.clipsToBounds = true + self.presentationDataDisposable = (self.context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in guard let self else { @@ -77,6 +167,31 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.addSubnode(self.chatController.displayNode) self.chatController.displayNode.clipsToBounds = true + + self.view.addSubview(self.coveringView) + + self.chatController.stateUpdated = { [weak self] transition in + guard let self else { + return + } + if let contentNode = self.chatController.customNavigationBarContentNode { + if self.searchNavigationContentNode?.contentNode !== contentNode { + self.searchNavigationContentNode = SearchNavigationContentNode(chatController: self.chatController, contentNode: contentNode) + self.searchNavigationContentNode?.panelNode = self.chatController.customNavigationPanelNode + self.externalDataUpdated?(transition) + } else if self.searchNavigationContentNode?.panelNode !== self.chatController.customNavigationPanelNode { + self.searchNavigationContentNode?.panelNode = self.chatController.customNavigationPanelNode + self.externalDataUpdated?(transition.isAnimated ? transition : .animated(duration: 0.4, curve: .spring)) + } else { + self.searchNavigationContentNode?.update(transition: transition) + } + } else { + if self.searchNavigationContentNode !== nil { + self.searchNavigationContentNode = nil + self.externalDataUpdated?(transition) + } + } + } } deinit { @@ -124,6 +239,9 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro super.didLoad() } + public func activateSearch() { + self.chatController.activateSearch(domain: .everything, query: "") + } override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return true @@ -141,14 +259,27 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro public func updateSelectedMessages(animated: Bool) { } - public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) - let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: size.height - topInset)) + + let fullHeight = navigationHeight + size.height + + let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: -navigationHeight), size: CGSize(width: size.width, height: fullHeight)) + + if !self.chatController.displayNode.bounds.isEmpty { + if let contextController = self.chatController.visibleContextController as? ContextController { + let deltaY = chatFrame.minY - self.chatController.displayNode.frame.minY + contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -deltaY * 0.0), transition: transition) + } + } + + self.coveringView.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor + transition.updateFrame(view: self.coveringView, frame: CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: size.width, height: topInset + 1.0))) let combinedBottomInset = bottomInset transition.updateFrame(node: self.chatController.displayNode, frame: chatFrame) self.chatController.updateIsScrollingLockedAtTop(isScrollingLockedAtTop: isScrollingLockedAtTop) - self.chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) + self.chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: topInset + navigationHeight, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: navigationHeight + topInset + 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift index 2cd86e88ac..1208db53df 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift @@ -39,6 +39,10 @@ public struct PeerInfoStatusData: Equatable { } } +public protocol PeerInfoPanelNodeNavigationContentNode: ASDisplayNode { + func update(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) -> CGFloat +} + public protocol PeerInfoPaneNode: ASDisplayNode { var isReady: Signal { get } @@ -48,7 +52,10 @@ public protocol PeerInfoPaneNode: ASDisplayNode { var tabBarOffsetUpdated: ((ContainedViewLayoutTransition) -> Void)? { get set } var tabBarOffset: CGFloat { get } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) + var navigationContentNode: PeerInfoPanelNodeNavigationContentNode? { get } + var externalDataUpdated: ((ContainedViewLayoutTransition) -> Void)? { get set } + + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) func scrollToTop() -> Bool func transferVelocity(_ velocity: CGFloat) func cancelPreviewGestures() @@ -59,3 +66,15 @@ public protocol PeerInfoPaneNode: ASDisplayNode { func updateSelectedMessages(animated: Bool) func ensureMessageIsVisible(id: MessageId) } + +public extension PeerInfoPaneNode { + var navigationContentNode: PeerInfoPanelNodeNavigationContentNode? { + return nil + } + var externalDataUpdated: ((ContainedViewLayoutTransition) -> Void)? { + get { + return nil + } set(value) { + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index f78e733852..1123166542 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -137,6 +137,7 @@ swift_library( "//submodules/MediaPickerUI", "//submodules/AttachmentUI", "//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent", + "//submodules/Components/MultilineTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift index 1c8448b312..054cfeba82 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift @@ -786,7 +786,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe return self._itemInteraction! } - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -959,8 +959,8 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe let wasFirstHistoryView = self.isFirstHistoryView self.isFirstHistoryView = false - if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { - self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate) + if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { + self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate) if !self.didSetReady { self.didSetReady = true self.ready.set(.single(true)) @@ -1066,9 +1066,9 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let previousParams = self.currentParams - self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: size.height - topInset))) @@ -1110,7 +1110,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe private var previousDidScrollTimestamp: Double = 0.0 func scrollViewDidScroll(_ scrollView: UIScrollView) { - if let (size, _, sideInset, bottomInset, _, visibleHeight, _, _, presentationData) = self.currentParams { + if let (size, _, sideInset, bottomInset, _, visibleHeight, _, _, _, presentationData) = self.currentParams { self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: false) if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0, let currentView = self.currentView, currentView.earlierId != nil { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift index 1ffa847f01..c09a19046e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift @@ -155,7 +155,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.currentParams == nil self.currentParams = (size, isScrollingLockedAtTop, presentationData) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift index 4be4c987bb..15be3b375f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift @@ -31,7 +31,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { private let listNode: ChatHistoryListNode - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -146,8 +146,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { strongSelf.playlistLocation = nil } - if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = strongSelf.currentParams { + strongSelf.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: .animated(duration: 0.4, curve: .spring)) } } }) @@ -200,8 +200,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) var topPanelHeight: CGFloat = 0.0 if let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift index 712656f0e0..cc18a4165d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift @@ -238,7 +238,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.currentParams == nil self.currentParams = (size, isScrollingLockedAtTop) self.presentationDataPromise.set(.single(presentationData)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift index 089c3bea7f..991453a817 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedChannelsPane.swift @@ -195,7 +195,7 @@ final class PeerInfoRecommendedChannelsPaneNode: ASDisplayNode, PeerInfoPaneNode } } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.currentParams == nil self.currentParams = (size, sideInset, bottomInset, isScrollingLockedAtTop, presentationData) self.presentationDataPromise.set(.single(presentationData)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 03bbb12e0d..6646f7a337 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -207,6 +207,7 @@ final class PeerInfoScreenData { let appConfiguration: AppConfiguration? let isPowerSavingEnabled: Bool? let accountIsPremium: Bool + let hasSavedMessageTags: Bool let _isContact: Bool var forceIsContact: Bool = false @@ -242,7 +243,8 @@ final class PeerInfoScreenData { threadData: MessageHistoryThreadData?, appConfiguration: AppConfiguration?, isPowerSavingEnabled: Bool?, - accountIsPremium: Bool + accountIsPremium: Bool, + hasSavedMessageTags: Bool ) { self.peer = peer self.chatPeer = chatPeer @@ -267,6 +269,7 @@ final class PeerInfoScreenData { self.appConfiguration = appConfiguration self.isPowerSavingEnabled = isPowerSavingEnabled self.accountIsPremium = accountIsPremium + self.hasSavedMessageTags = hasSavedMessageTags } } @@ -662,7 +665,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, threadData: nil, appConfiguration: appConfiguration, isPowerSavingEnabled: isPowerSavingEnabled, - accountIsPremium: peer?.isPremium ?? false + accountIsPremium: peer?.isPremium ?? false, + hasSavedMessageTags: false ) } } @@ -697,7 +701,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen threadData: nil, appConfiguration: nil, isPowerSavingEnabled: nil, - accountIsPremium: false + accountIsPremium: false, + hasSavedMessageTags: false )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -840,7 +845,16 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen |> distinctUntilChanged if peerId == context.account.peerId { - hasSavedMessagesChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved() + hasSavedMessagesChats = combineLatest( + context.engine.messages.savedMessagesHasPeersOtherThanSaved(), + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.DisplaySavedChatsAsTopics() + ) + ) + |> map { hasChats, displayAsTopics -> Bool in + return hasChats || displayAsTopics + } + |> distinctUntilChanged } else { hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead() |> map { headPeerId -> Bool in @@ -853,6 +867,39 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessagesChats = .single(false) } + let hasSavedMessageTags: Signal + if let peerId = chatLocation.peerId { + if case .peer = chatLocation { + if peerId != context.account.peerId { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64()) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } else { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: nil) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } + } else { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64()) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } + } else { + hasSavedMessageTags = .single(false) + } + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), @@ -863,9 +910,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, - hasSavedMessages + hasSavedMessages, + hasSavedMessageTags ) - |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags -> PeerInfoScreenData in var availablePanes = availablePanes if let hasStories { @@ -924,7 +972,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen threadData: nil, appConfiguration: nil, isPowerSavingEnabled: nil, - accountIsPremium: accountIsPremium + accountIsPremium: accountIsPremium, + hasSavedMessageTags: hasSavedMessageTags ) } case .channel: @@ -985,6 +1034,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessagesChats = .single(false) } + let hasSavedMessageTags: Signal + if let peerId = chatLocation.peerId { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64()) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } else { + hasSavedMessageTags = .single(false) + } + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), @@ -998,9 +1060,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen accountIsPremium, context.engine.peers.recommendedChannels(peerId: peerId), hasSavedMessages, - hasSavedMessagesChats + hasSavedMessagesChats, + hasSavedMessageTags ) - |> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> PeerInfoScreenData in var availablePanes = availablePanes if let hasStories { if hasStories { @@ -1072,7 +1135,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen threadData: nil, appConfiguration: nil, isPowerSavingEnabled: nil, - accountIsPremium: accountIsPremium + accountIsPremium: accountIsPremium, + hasSavedMessageTags: hasSavedMessageTags ) } case let .group(groupId): @@ -1219,6 +1283,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen hasSavedMessagesChats = .single(false) } + let hasSavedMessageTags: Signal + if let peerId = chatLocation.peerId { + hasSavedMessageTags = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64()) + ) + |> map { tags -> Bool in + return !tags.isEmpty + } + |> distinctUntilChanged + } else { + hasSavedMessageTags = .single(false) + } + return combineLatest(queue: .mainQueue(), context.account.viewTracker.peerView(groupId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), @@ -1233,9 +1310,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]), accountIsPremium, hasSavedMessages, - hasSavedMessagesChats + hasSavedMessagesChats, + hasSavedMessageTags ) - |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats -> Signal in + |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> Signal in var discussionPeer: Peer? if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer @@ -1320,7 +1398,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen threadData: threadData, appConfiguration: appConfiguration, isPowerSavingEnabled: nil, - accountIsPremium: accountIsPremium + accountIsPremium: accountIsPremium, + hasSavedMessageTags: hasSavedMessageTags )) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift index 566be83523..1d2a7e3d41 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift @@ -178,7 +178,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { self.backgroundNode.updateColor(color: backgroundColor, transition: transition) transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) - transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) + transition.updateTintColor(view: self.iconNode.view, color: self.contentsColor) transition.updateStrokeColor(layer: self.backIconLayer, strokeColor: self.contentsColor) switch self.key { @@ -247,9 +247,17 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { case .search: text = "" accessibilityText = presentationData.strings.Common_Search - icon = nil// PresentationResourcesRootController.navigationCompactSearchIcon(presentationData.theme) + icon = nil isAnimation = true animationState = .search + case .standaloneSearch: + text = "" + accessibilityText = presentationData.strings.Common_Search + icon = PresentationResourcesRootController.navigationCompactSearchWhiteIcon(presentationData.theme) + case .searchWithTags: + text = "" + accessibilityText = presentationData.strings.Common_Search + icon = PresentationResourcesRootController.navigationCompactTagsSearchWhiteIcon(presentationData.theme) case .editPhoto: text = presentationData.strings.Settings_EditPhoto accessibilityText = text @@ -283,7 +291,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: .white) transition.updateTintColor(layer: self.textNode.layer, color: self.contentsColor) self.iconNode.image = icon - transition.updateTintColor(layer: self.iconNode.layer, color: self.contentsColor) + transition.updateTintColor(view: self.iconNode.view, color: self.contentsColor) if isAnimation { self.iconNode.isHidden = true diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift index 7d6e435dfa..cc71f68e12 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift @@ -13,6 +13,8 @@ enum PeerInfoHeaderNavigationButtonKey { case select case selectionDone case search + case searchWithTags + case standaloneSearch case editPhoto case editVideo case more @@ -51,11 +53,11 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { } for (_, button) in self.rightButtonNodes { button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition) - transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: canBeExpanded ? 8.0 : 0.0, y: 0.0)) + transition.updateSublayerTransformOffset(layer: button.layer, offset: CGPoint(x: canBeExpanded ? 16.0 : 0.0, y: 0.0)) } } - func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, shouldAnimateIn: Bool, transition: ContainedViewLayoutTransition) { let sideInset: CGFloat = 24.0 let maximumExpandOffset: CGFloat = 14.0 @@ -200,7 +202,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { if case .postStory = spec.key { buttonFrame.origin.x -= 12.0 } - nextButtonOrigin -= buttonSize.width + 4.0 + nextButtonOrigin -= buttonSize.width + 15.0 if spec.isForExpandedView { nextExpandedButtonOrigin = nextButtonOrigin } else { @@ -210,15 +212,17 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { if wasAdded { buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: self.canBeExpanded, transition: .immediate) - if key == .moreToSearch { - buttonNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) + if shouldAnimateIn { + if key == .moreToSearch || key == .searchWithTags || key == .standaloneSearch { + buttonNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) + } } buttonNode.frame = buttonFrame buttonNode.alpha = 0.0 transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) - transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? 8.0 : 0.0, y: 0.0)) + transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? 16.0 : 0.0, y: 0.0)) } else { transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) @@ -236,7 +240,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { } for key in removeKeys { if let buttonNode = self.rightButtonNodes.removeValue(forKey: key) { - if key == .moreToSearch { + if key == .moreToSearch || key == .searchWithTags || key == .standaloneSearch { buttonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in buttonNode?.removeFromSupernode() }) @@ -263,7 +267,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { if case .postStory = spec.key { buttonFrame.origin.x -= 12.0 } - nextButtonOrigin -= buttonSize.width + 4.0 + nextButtonOrigin -= buttonSize.width + 15.0 if spec.isForExpandedView { nextExpandedButtonOrigin = nextButtonOrigin } else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index 983445d9ce..8a7ee03cc9 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -37,6 +37,7 @@ import ChatAvatarNavigationNode import MultiScaleTextNode import PeerInfoCoverComponent import PeerInfoPaneNode +import MultilineTextComponent final class PeerInfoHeaderNavigationTransition { let sourceNavigationBar: NavigationBar @@ -108,6 +109,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let titleNodeContainer: ASDisplayNode let titleNodeRawContainer: ASDisplayNode let titleNode: MultiScaleTextNode + var standardTitle: ComponentView? let titleCredibilityIconView: ComponentHostView var credibilityIconSize: CGSize? @@ -169,6 +171,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { var emojiStatusPackDisposable = MetaDisposable() var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>() + var customNavigationContentNode: PeerInfoPanelNodeNavigationContentNode? + private var appliedCustomNavigationContentNode: PeerInfoPanelNodeNavigationContentNode? + private var validLayout: (width: CGFloat, deviceMetrics: DeviceMetrics)? init(context: AccountContext, controller: PeerInfoScreenImpl, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, forumTopicThreadId: Int64?, chatLocation: ChatLocation) { @@ -451,6 +456,24 @@ final class PeerInfoHeaderNode: ASDisplayNode { private var currentPanelStatusData: PeerInfoStatusData? func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat { + if self.appliedCustomNavigationContentNode !== self.customNavigationContentNode { + if let previous = self.appliedCustomNavigationContentNode { + transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in + previous?.removeFromSupernode() + }) + } + + self.appliedCustomNavigationContentNode = self.customNavigationContentNode + if let customNavigationContentNode = self.customNavigationContentNode { + self.addSubnode(customNavigationContentNode) + customNavigationContentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: navigationHeight)) + customNavigationContentNode.alpha = 0.0 + transition.updateAlpha(node: customNavigationContentNode, alpha: 1.0) + } + } else if let customNavigationContentNode = self.customNavigationContentNode { + transition.updateFrame(node: customNavigationContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: navigationHeight))) + } + var threadData = threadData if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId { threadData = nil @@ -516,7 +539,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { isForum = true } - self.regularContentNode.alpha = state.isEditing ? 0.0 : 1.0 + transition.updateAlpha(node: self.regularContentNode, alpha: (state.isEditing || self.customNavigationContentNode != nil) ? 0.0 : 1.0) + transition.updateAlpha(node: self.navigationButtonContainer, alpha: self.customNavigationContentNode != nil ? 0.0 : 1.0) + self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0 let editingContentHeight = self.editingContentNode.update(width: width, safeInset: containerInset, statusBarHeight: statusBarHeight, navigationHeight: navigationHeight, isModalOverlay: isModalOverlay, peer: state.isEditing ? peer : nil, threadData: threadData, chatLocation: self.chatLocation, cachedData: cachedData, isContact: isContact, isSettings: isSettings, presentationData: presentationData, transition: transition) @@ -968,12 +993,15 @@ final class PeerInfoHeaderNode: ASDisplayNode { let titleShadowColor: UIColor? = nil + var displayStandardTitle = false + if let peer = peer { var title: String if peer.id == self.context.account.peerId && !self.isSettings { if case .replyThread = self.chatLocation { title = presentationData.strings.Conversation_MyNotes } else { + displayStandardTitle = true title = presentationData.strings.Conversation_SavedMessages } } else if peer.id.isAnonymousSavedMessages { @@ -1362,6 +1390,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { if self.navigationTransition == nil && !self.isSettings && effectiveSeparatorAlpha == 1.0 && secondarySeparatorAlpha < 1.0 { effectiveSeparatorAlpha = secondarySeparatorAlpha } + if self.customNavigationContentNode != nil { + effectiveSeparatorAlpha = 0.0 + } transition.updateAlpha(node: self.separatorNode, alpha: effectiveSeparatorAlpha) self.titleNode.update(stateFractions: [ @@ -1763,6 +1794,41 @@ final class PeerInfoHeaderNode: ASDisplayNode { } } + if displayStandardTitle { + self.titleNode.isHidden = true + + let standardTitle: ComponentView + if let current = self.standardTitle { + standardTitle = current + } else { + standardTitle = ComponentView() + self.standardTitle = standardTitle + } + + let titleSize = standardTitle.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: titleStringText, font: Font.semibold(17.0), textColor: navigationContentsPrimaryColor)) + )), + environment: {}, + containerSize: CGSize(width: width, height: navigationHeight) + ) + if let standardTitleView = standardTitle.view { + if standardTitleView.superview == nil { + self.regularContentNode.view.addSubview(standardTitleView) + } + let standardTitleFrame = titleSize.centered(in: self.titleNodeContainer.frame).offsetBy(dx: 2.0, dy: 0.0) + standardTitleView.frame = standardTitleFrame + } + } else { + if let standardTitle = self.standardTitle { + self.standardTitle = nil + standardTitle.view?.removeFromSuperview() + + self.titleNode.isHidden = false + } + } + let buttonsTransitionDistance: CGFloat = -min(0.0, apparentBackgroundHeight - backgroundHeight) let buttonsTransitionDistanceNorm: CGFloat = 40.0 @@ -1984,6 +2050,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } transition.updateFrame(node: self.regularContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: resolvedHeight))) + transition.updateFrameAdditive(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentBackgroundHeight - backgroundHeight), size: CGSize(width: width, height: 1000.0))) navigationTransition.updateAlpha(node: self.buttonsContainerNode, alpha: backgroundBannerAlpha) @@ -2078,6 +2145,13 @@ final class PeerInfoHeaderNode: ASDisplayNode { return nil } + if let customNavigationContentNode = self.customNavigationContentNode { + if let result = customNavigationContentNode.view.hitTest(self.view.convert(point, to: customNavigationContentNode.view), with: event) { + return result + } + return self.view + } + let setByFrame = self.avatarListNode.listContainerNode.setByYouNode.view.convert(self.avatarListNode.listContainerNode.setByYouNode.bounds, to: self.view).insetBy(dx: -44.0, dy: 0.0) if self.avatarListNode.listContainerNode.setByYouNode.alpha > 0.0, setByFrame.contains(point) { return self.avatarListNode.listContainerNode.setByYouNode.view diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index b24aa644b6..ce1c371908 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -18,21 +18,21 @@ final class PeerInfoPaneWrapper { let key: PeerInfoPaneKey let node: PeerInfoPaneNode var isAnimatingOut: Bool = false - private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, DeviceMetrics, CGFloat, Bool, CGFloat, PresentationData)? + private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, DeviceMetrics, CGFloat, Bool, CGFloat, CGFloat, PresentationData)? init(key: PeerInfoPaneKey, node: PeerInfoPaneNode) { self.key = key self.node = node } - func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - if let (currentSize, currentTopInset, currentSideInset, currentBottomInset, _, currentVisibleHeight, currentIsScrollingLockedAtTop, currentExpandProgress, currentPresentationData) = self.appliedParams { - if currentSize == size && currentTopInset == topInset, currentSideInset == sideInset && currentBottomInset == bottomInset && currentVisibleHeight == visibleHeight && currentIsScrollingLockedAtTop == isScrollingLockedAtTop && currentExpandProgress == expandProgress && currentPresentationData === presentationData { + func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + if let (currentSize, currentTopInset, currentSideInset, currentBottomInset, _, currentVisibleHeight, currentIsScrollingLockedAtTop, currentExpandProgress, currentNavigationHeight, currentPresentationData) = self.appliedParams { + if currentSize == size && currentTopInset == topInset, currentSideInset == sideInset && currentBottomInset == bottomInset && currentVisibleHeight == visibleHeight && currentIsScrollingLockedAtTop == isScrollingLockedAtTop && currentExpandProgress == expandProgress && currentNavigationHeight == navigationHeight && currentPresentationData === presentationData { return } } - self.appliedParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) - self.node.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: synchronous, transition: transition) + self.appliedParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) + self.node.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: synchronous, transition: transition) } } @@ -366,7 +366,8 @@ private final class PeerInfoPendingPane { parentController: ViewController?, openMediaCalendar: @escaping () -> Void, paneDidScroll: @escaping () -> Void, - ensureRectVisible: @escaping (UIView, CGRect) -> Void + ensureRectVisible: @escaping (UIView, CGRect) -> Void, + externalDataUpdated: @escaping (ContainedViewLayoutTransition) -> Void ) { let captureProtected = data.peer?.isCopyProtectionEnabled ?? false let paneNode: PeerInfoPaneNode @@ -425,6 +426,7 @@ private final class PeerInfoPendingPane { case .savedMessages: paneNode = PeerInfoChatPaneNode(context: context, peerId: peerId, navigationController: chatControllerInteraction.navigationController) } + paneNode.externalDataUpdated = externalDataUpdated paneNode.parentController = parentController self.pane = PeerInfoPaneWrapper(key: key, node: paneNode) self.disposable = (paneNode.isReady @@ -458,7 +460,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat let isReady = Promise() var didSetIsReady = false - private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?)? + private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, areTabsHidden: Bool, navigationHeight: CGFloat)? private(set) var currentPaneKey: PeerInfoPaneKey? var pendingSwitchToPaneKey: PeerInfoPaneKey? @@ -495,6 +497,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat var currentPaneUpdated: ((Bool) -> Void)? var requestExpandTabs: (() -> Bool)? + var requestUpdate: ((ContainedViewLayoutTransition) -> Void)? var openMediaCalendar: (() -> Void)? var paneDidScroll: (() -> Void)? @@ -550,8 +553,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat if strongSelf.currentPanes[key] != nil { strongSelf.currentPaneKey = key - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.currentPaneUpdated?(true) @@ -563,8 +566,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat strongSelf.pendingSwitchToPaneKey = key strongSelf.expandOnSwitch = true - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.4, curve: .spring)) + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring)) } } } @@ -586,6 +589,9 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat } return [.leftCenter, .rightCenter] } + if strongSelf.currentPane?.node.navigationContentNode != nil { + return [] + } if index == 0 { return .left } @@ -629,7 +635,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat cancelContextGestures(view: self.view) case .changed: - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { let translation = recognizer.translation(in: self.view) var transitionFraction = translation.x / size.width if currentIndex <= 0 { @@ -644,11 +650,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat // print(transitionFraction) self.paneTransitionPromise.set(transitionFraction) - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .immediate) + self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: .immediate) self.currentPaneUpdated?(false) } case .cancelled, .ended: - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = self.currentParams, let availablePanes = data?.availablePanes, availablePanes.count > 1, let currentPaneKey = self.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey) { let translation = recognizer.translation(in: self.view) let velocity = recognizer.velocity(in: self.view) var directionIsToRight: Bool? @@ -672,7 +678,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat } } self.transitionFraction = 0.0 - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.35, curve: .spring)) + self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: .animated(duration: 0.35, curve: .spring)) self.currentPaneUpdated?(false) self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil)) @@ -711,7 +717,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat } } - func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, areTabsHidden: Bool, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { let previousAvailablePanes = self.currentAvailablePanes let availablePanes = data?.availablePanes ?? [] self.currentAvailablePanes = data?.availablePanes @@ -755,7 +761,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat currentIndex = nil } - self.currentParams = (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) + self.currentParams = (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) transition.updateAlpha(node: self.coveringBackgroundNode, alpha: expansionFraction) @@ -770,6 +776,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat let isScrollingLockedAtTop = expansionFraction < 1.0 - CGFloat.ulpOfOne let tabsHeight: CGFloat = 48.0 + let effectiveTabsHeight: CGFloat = areTabsHidden ? 0.0 : tabsHeight let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) @@ -825,12 +832,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat guard let strongSelf = self else { return } - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams { + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = strongSelf.currentParams { var transition: ContainedViewLayoutTransition = .immediate if strongSelf.pendingSwitchToPaneKey == key && strongSelf.currentPaneKey != nil { transition = .animated(duration: 0.4, curve: .spring) } - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: transition) + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: transition) } } if leftScope { @@ -849,18 +856,24 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat return } self.ensurePaneRectVisible?(self.view, sourceView.convert(rect, to: self.view)) + }, + externalDataUpdated: { [weak self] transition in + guard let self else { + return + } + self.requestUpdate?(transition) } ) self.pendingPanes[key] = pane pane.pane.node.frame = paneFrame - pane.pane.update(size: paneFrame.size, topInset: tabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, presentationData: presentationData, synchronous: true, transition: .immediate) + pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: .immediate) let paneNode = pane.pane.node pane.pane.node.tabBarOffsetUpdated = { [weak self, weak paneNode] transition in guard let strongSelf = self, let paneNode = paneNode, let currentPane = strongSelf.currentPane, paneNode === currentPane.node else { return } - if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: transition) + if let (size, sideInset, bottomInset, deviceMetrics, visibleHeight, expansionFraction, presentationData, data, areTabsHidden, navigationHeight) = strongSelf.currentParams { + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, areTabsHidden: areTabsHidden, navigationHeight: navigationHeight, transition: transition) } } leftScope = true @@ -869,7 +882,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat for (key, pane) in self.pendingPanes { pane.pane.node.frame = paneFrame - pane.pane.update(size: paneFrame.size, topInset: tabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate) + pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: self.currentPaneKey == nil, transition: .immediate) if pane.isReady { self.pendingPanes.removeValue(forKey: key) @@ -930,7 +943,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat return } pane.isAnimatingOut = false - if let (_, _, _, _, _, _, _, data) = strongSelf.currentParams { + if let (_, _, _, _, _, _, _, data, _, _) = strongSelf.currentParams { if let availablePanes = data?.availablePanes, let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey), let paneIndex = availablePanes.firstIndex(of: key), abs(paneIndex - currentIndex) <= 1 { } else { if let pane = strongSelf.currentPanes.removeValue(forKey: key) { @@ -961,7 +974,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat paneCompletion() }) } - pane.update(size: paneFrame.size, topInset: tabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition) + pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition) } } @@ -973,7 +986,14 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat if isScrollingLockedAtTop || self.isMediaOnly { tabsOffset = 0.0 } - var tabsAlpha = 1.0 - tabsOffset / tabsHeight + + var tabsAlpha: CGFloat + if areTabsHidden { + tabsAlpha = 0.0 + tabsOffset = tabsHeight + } else { + tabsAlpha = 1.0 - tabsOffset / tabsHeight + } tabsAlpha *= tabsAlpha transition.updateFrame(node: self.tabsContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -tabsOffset), size: CGSize(width: size.width, height: tabsHeight))) transition.updateAlpha(node: self.tabsContainerNode, alpha: tabsAlpha) @@ -1019,7 +1039,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat for (_, pane) in self.pendingPanes { let paneTransition: ContainedViewLayoutTransition = .immediate paneTransition.updateFrame(node: pane.pane.node, frame: paneFrame) - pane.pane.update(size: paneFrame.size, topInset: tabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, presentationData: presentationData, synchronous: true, transition: paneTransition) + pane.pane.update(size: paneFrame.size, topInset: effectiveTabsHeight, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expansionFraction, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: true, transition: paneTransition) } var removeKeys: [PeerInfoPaneKey] = [] diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index a76afc5ec4..8b1080c1f1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -102,6 +102,7 @@ import PeerInfoPaneNode import MediaPickerUI import AttachmentUI import BoostLevelIconComponent +import PeerInfoChatPaneNode public enum PeerInfoAvatarEditingMode { case generic @@ -3145,6 +3146,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } + self.paneContainerNode.requestUpdate = { [weak self] transition in + guard let self else { + return + } + if let (layout, navigationHeight) = self.validLayout { + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: transition, additive: false) + } + } + self.paneContainerNode.ensurePaneRectVisible = { [weak self] sourceView, rect in guard let self else { return @@ -3748,12 +3758,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } strongSelf.chatInterfaceInteraction.selectionState = strongSelf.state.selectedMessageIds.flatMap { ChatInterfaceSelectionState(selectedIds: $0) } strongSelf.paneContainerNode.updateSelectedMessageIds(strongSelf.state.selectedMessageIds, animated: true) - case .search: - strongSelf.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + case .search, .searchWithTags, .standaloneSearch: strongSelf.activateSearch() case .more: - if let source = source { - strongSelf.displayMediaGalleryContextMenu(source: source, gesture: gesture) + if let currentPaneKey = strongSelf.paneContainerNode.currentPaneKey, case .savedMessagesChats = currentPaneKey { + if let controller = strongSelf.controller, let source { + PeerInfoScreenImpl.openSavedMessagesMoreMenu(context: strongSelf.context, sourceController: controller, isViewingAsTopics: true, sourceView: source.view, gesture: gesture) + } + } else { + if let source = source { + strongSelf.displayMediaGalleryContextMenu(source: source, gesture: gesture) + } } case .qrCode: strongSelf.openQrCode() @@ -9269,6 +9284,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } + if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessages = currentPaneKey, let paneNode = self.paneContainerNode.currentPane?.node as? PeerInfoChatPaneNode { + paneNode.activateSearch() + return + } + + self.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + if self.isSettings { (self.controller?.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.3, curve: .linear)) @@ -9803,9 +9825,31 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } + private func updateNavigationHeight(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) -> CGFloat { + var navigationHeight = defaultHeight + if let customNavigationContentNode = self.headerNode.customNavigationContentNode { + var mappedTransition = transition + if customNavigationContentNode.supernode == nil { + mappedTransition = .immediate + } + let contentHeight = customNavigationContentNode.update(width: width, defaultHeight: defaultHeight, insets: insets, transition: mappedTransition) + navigationHeight = contentHeight + } + return navigationHeight + } + func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition, additive: Bool = false) { self.validLayout = (layout, navigationHeight) + self.headerNode.customNavigationContentNode = self.paneContainerNode.currentPane?.node.navigationContentNode + + let isScrollEnabled = !self.isMediaOnly && self.headerNode.customNavigationContentNode == nil + if self.scrollNode.view.isScrollEnabled != isScrollEnabled { + self.scrollNode.view.isScrollEnabled = isScrollEnabled + } + + let navigationHeight = self.updateNavigationHeight(width: layout.size.width, defaultHeight: navigationHeight, insets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: 0.0, right: layout.safeInsets.right), transition: transition) + if self.headerNode.isAvatarExpanded && layout.size.width > layout.size.height { self.headerNode.updateIsAvatarExpanded(false, transition: transition) self.updateNavigationExpansionPresentation(isExpanded: false, animated: true) @@ -10139,7 +10183,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } self.ignoreScrolling = false - self.updateNavigation(transition: transition, additive: additive, animateHeader: true) + self.updateNavigation(transition: transition, additive: additive, animateHeader: self.controller?.didAppear ?? false) if !self.didSetReady && self.data != nil { self.didSetReady = true @@ -10188,6 +10232,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } if let (layout, navigationHeight) = self.validLayout { + let navigationHeight = self.updateNavigationHeight(width: layout.size.width, defaultHeight: navigationHeight, insets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: 0.0, right: layout.safeInsets.right), transition: transition) + if !additive { let sectionInset: CGFloat if layout.size.width >= 375.0 { @@ -10222,7 +10268,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let navigationBarHeight: CGFloat = !self.isSettings && layout.isModalOverlay ? 56.0 : 44.0 - self.paneContainerNode.update(size: self.paneContainerNode.bounds.size, sideInset: layout.safeInsets.left, bottomInset: bottomInset, deviceMetrics: layout.deviceMetrics, visibleHeight: visibleHeight, expansionFraction: effectiveAreaExpansionFraction, presentationData: self.presentationData, data: self.data, transition: transition) + self.paneContainerNode.update(size: self.paneContainerNode.bounds.size, sideInset: layout.safeInsets.left, bottomInset: bottomInset, deviceMetrics: layout.deviceMetrics, visibleHeight: visibleHeight, expansionFraction: effectiveAreaExpansionFraction, presentationData: self.presentationData, data: self.data, areTabsHidden: self.headerNode.customNavigationContentNode != nil, navigationHeight: navigationHeight, transition: transition) transition.updateFrame(node: self.headerNode.navigationButtonContainer, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.statusBarHeight ?? 0.0), size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight))) @@ -10247,8 +10293,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if self.state.selectedMessageIds == nil { if let currentPaneKey = self.paneContainerNode.currentPaneKey { switch currentPaneKey { - case .files, .music, .links, .members, .savedMessagesChats: + case .files, .music, .links, .members: rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) + case .savedMessagesChats: + if let data = self.data, data.hasSavedMessageTags { + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .searchWithTags, isForExpandedView: true)) + } else { + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .standaloneSearch, isForExpandedView: true)) + } + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true)) + case .savedMessages: + if let data = self.data, data.hasSavedMessageTags { + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .searchWithTags, isForExpandedView: true)) + } else { + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) + } case .media: rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true)) default: @@ -10272,7 +10331,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .back, isForExpandedView: false)) } } - self.headerNode.navigationButtonContainer.update(size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight), presentationData: self.presentationData, leftButtons: leftNavigationButtons, rightButtons: rightNavigationButtons, expandFraction: effectiveAreaExpansionFraction, transition: transition) + self.headerNode.navigationButtonContainer.update(size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight), presentationData: self.presentationData, leftButtons: leftNavigationButtons, rightButtons: rightNavigationButtons, expandFraction: effectiveAreaExpansionFraction, shouldAnimateIn: animateHeader, transition: transition) } } @@ -10576,6 +10635,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc } } + var didAppear: Bool = false + private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil, switchToRecommendedChannels: Bool = false) { @@ -11099,6 +11160,10 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + DispatchQueue.main.async { [weak self] in + self?.didAppear = true + } + var chatNavigationStack: [ChatNavigationStackItem] = [] if !self.isSettings, let summary = self.customNavigationDataSummary as? ChatControllerNavigationDataSummary { chatNavigationStack.removeAll() @@ -11220,6 +11285,61 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc ] } } + + public static func openSavedMessagesMoreMenu(context: AccountContext, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> deliverOnMainQueue).startStandalone(next: { peer in + guard let peer else { + return + } + + let strings = context.sharedContext.currentPresentationData.with { $0 }.strings + + var items: [ContextMenuItem] = [] + + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "View as Chats", icon: { theme in + if !isViewingAsTopics { + return nil + } + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { [weak sourceController] _, a in + a(.default) + + guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else { + return + } + + context.engine.peers.updateSavedMessagesViewAsTopics(value: true) + + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + navigationController.replaceController(sourceController, with: infoController, animated: false) + } + }))) + items.append(.action(ContextMenuActionItem(text: strings.Chat_ContextViewAsMessages, icon: { theme in + if isViewingAsTopics { + return nil + } + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, iconPosition: .left, action: { [weak sourceController] _, a in + a(.default) + + guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else { + return + } + + let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: context.account.peerId), subject: nil, botStart: nil, mode: .standard(.default)) + + navigationController.replaceController(sourceController, with: chatController, animated: false) + + context.engine.peers.updateSavedMessagesViewAsTopics(value: false) + }))) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: sourceController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + sourceController.presentInGlobalOverlay(contextController) + }) + } } private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource { @@ -12386,3 +12506,17 @@ private final class PeerInfoControllerContextReferenceContentSource: ContextRefe return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets) } } + +private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceView: UIView + + init(controller: ViewController, sourceView: UIView) { + self.controller = controller + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift index cb020bae7e..505d0b4b3b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift @@ -486,6 +486,7 @@ final class PeerInfoStoryGridScreenComponent: Component { visibleHeight: availableSize.height, isScrollingLockedAtTop: false, expandProgress: 1.0, + navigationHeight: 0.0, presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }), synchronous: false, transition: transition.containedViewLayoutTransition diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 7491954a5d..b7d525ded0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -950,7 +950,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr public private(set) var isSelectionModeActive: Bool - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -1730,12 +1730,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) { self.items = items - if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { + if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { var gridSnapshot: UIView? if reloadAtTop { gridSnapshot = self.itemGrid.view.snapshotView(afterScreenUpdates: false) } - self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: false, transition: .immediate) + self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: .immediate) self.updateSelectedItems(animated: false) if let gridSnapshot = gridSnapshot { self.view.addSubview(gridSnapshot) @@ -2006,8 +2006,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } } - public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift index b021d2470e..081fea46bb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift @@ -1102,7 +1102,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, return self._itemInteraction! } - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -1606,7 +1606,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, self.presentationDataDisposable = (self.context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in - guard let strongSelf = self, let (size, topInset, sideInset, bottomInset, _, _, _, _, _) = strongSelf.currentParams else { + guard let strongSelf = self, let (size, topInset, sideInset, bottomInset, _, _, _, _, _, _) = strongSelf.currentParams else { return } strongSelf.itemGridBinding.updatePresentationData(presentationData: presentationData) @@ -1743,12 +1743,12 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) { self.items = items - if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { + if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams { var gridSnapshot: UIView? if reloadAtTop { gridSnapshot = self.itemGrid.view.snapshotView(afterScreenUpdates: false) } - self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: false, transition: .immediate) + self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, navigationHeight: navigationHeight, presentationData: presentationData, synchronous: false, transition: .immediate) if let gridSnapshot = gridSnapshot { self.view.addSubview(gridSnapshot) gridSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak gridSnapshot] _ in @@ -2037,7 +2037,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, switch self.contentType { case .files, .music, .voiceAndVideoMessages: self.itemGrid.forEachVisibleItem { item in - guard let itemView = item.view as? ItemView, let (size, topInset, sideInset, bottomInset, _, _, _, _, _) = self.currentParams else { + guard let itemView = item.view as? ItemView, let (size, topInset, sideInset, bottomInset, _, _, _, _, _, _) = self.currentParams else { return } if let item = itemView.item { @@ -2094,8 +2094,8 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, } } - public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 78a4cc1605..89dee442cb 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -937,7 +937,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 248a13e7ba..6bb5c6bbe3 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -96,6 +96,7 @@ swift_library( "//submodules/StickerResources", "//submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent", "//submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen", + "//submodules/TelegramUI/Components/SliderContextItem", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index abdc4637c3..4afd4c85dc 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -31,6 +31,7 @@ public final class StoryContentItem: Equatable { public final class SharedState { public var replyDrafts: [StoryId: NSAttributedString] = [:] + public var baseRate: Double = 1.0 public init() { } @@ -55,6 +56,9 @@ public final class StoryContentItem: Equatable { open func enterAmbientMode(ambient: Bool) { } + open func setBaseRate(_ baseRate: Double) { + } + open var videoPlaybackPosition: Double? { return nil } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 9278d4eb32..76fb1778b6 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -33,12 +33,13 @@ final class StoryItemContentComponent: Component { let availableReactions: StoryAvailableReactions? let entityFiles: [MediaId: TelegramMediaFile] let audioMode: StoryContentItem.AudioMode + let baseRate: Double let isVideoBuffering: Bool let isCurrent: Bool let preferHighQuality: Bool let activateReaction: (UIView, MessageReaction.Reaction) -> Void - init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, isVideoBuffering: Bool, isCurrent: Bool, preferHighQuality: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) { + init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, preferHighQuality: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) { self.context = context self.strings = strings self.peer = peer @@ -46,6 +47,7 @@ final class StoryItemContentComponent: Component { self.entityFiles = entityFiles self.availableReactions = availableReactions self.audioMode = audioMode + self.baseRate = baseRate self.isVideoBuffering = isVideoBuffering self.isCurrent = isCurrent self.preferHighQuality = preferHighQuality @@ -71,6 +73,9 @@ final class StoryItemContentComponent: Component { if lhs.entityFiles.keys != rhs.entityFiles.keys { return false } + if lhs.baseRate != rhs.baseRate { + return false + } if lhs.isVideoBuffering != rhs.isVideoBuffering { return false } @@ -117,7 +122,7 @@ final class StoryItemContentComponent: Component { override var videoPlaybackPosition: Double? { return self.videoPlaybackStatus?.timestamp } - + private let hierarchyTrackingLayer: HierarchyTrackingLayer private var fetchPriorityResourceId: String? @@ -226,6 +231,7 @@ final class StoryItemContentComponent: Component { priority: .gallery ) videoNode.isHidden = true + videoNode.setBaseRate(component.baseRate) self.videoNode = videoNode self.insertSubview(videoNode.view, aboveSubview: self.imageView) @@ -330,6 +336,12 @@ final class StoryItemContentComponent: Component { } } + override func setBaseRate(_ baseRate: Double) { + if let videoNode = self.videoNode { + videoNode.setBaseRate(baseRate) + } + } + private func updateProgressMode(update: Bool) { if let videoNode = self.videoNode { let canPlay = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index d11a8bd495..9f87f054d1 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -42,6 +42,7 @@ import TranslateUI import TelegramUIPreferences import StoryFooterPanelComponent import TelegramNotices +import SliderContextItem public final class StoryAvailableReactions: Equatable { let reactionItems: [ReactionItem] @@ -1565,6 +1566,7 @@ public final class StoryItemSetContainerComponent: Component { availableReactions: component.availableReactions, entityFiles: item.entityFiles, audioMode: component.audioMode, + baseRate: component.storyItemSharedState.baseRate, isVideoBuffering: visibleItem.isBuffering, isCurrent: index == centralIndex, preferHighQuality: component.slice.additionalPeerData.preferHighQualityStories, @@ -6033,6 +6035,73 @@ public final class StoryItemSetContainerComponent: Component { return (tip, tipSignal) } + private func contextMenuSpeedItems(value: ValuePromise) -> Signal<[ContextMenuItem], NoError> { + guard let component = self.component else { + return .single([]) + } + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) + + let baseRate = component.storyItemSharedState.baseRate + let valuePromise = ValuePromise(nil) + + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) + }, iconPosition: .left, action: { c, _ in + c.popItems() + }))) + + items.append(.custom(SliderContextItem(minValue: 0.2, maxValue: 2.5, value: baseRate, valueChanged: { [weak self] newValue, done in + guard let self, let component = self.component else { + return + } + func normalizeValue(_ value: CGFloat) -> CGFloat { + return round(value * 10.0) / 10.0 + } + + let rate = normalizeValue(newValue) + if let visibleItem = self.visibleItems[component.slice.item.storyItem.id], let view = visibleItem.view.view as? StoryItemContentComponent.View { + view.setBaseRate(rate) + } + + component.storyItemSharedState.baseRate = rate + valuePromise.set(rate) + + if done { + value.set(rate) + } + }), true)) + + items.append(.separator) + + for (text, _, rate) in speedList(strings: presentationData.strings) { + let isSelected = abs(baseRate - rate) < 0.01 + items.append(.action(ContextMenuActionItem(text: text, icon: { _ in return nil }, iconSource: ContextMenuActionItemIconSource(size: CGSize(width: 24.0, height: 24.0), signal: valuePromise.get() + |> map { value in + if isSelected && value == nil { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white) + } else { + return nil + } + }), action: { [weak self] _, f in + f(.default) + + guard let self, let component = self.component else { + return + } + + if let visibleItem = self.visibleItems[component.slice.item.storyItem.id], let view = visibleItem.view.view as? StoryItemContentComponent.View { + view.setBaseRate(rate) + } + component.storyItemSharedState.baseRate = rate + }))) + } + + return .single(items) + } + private func performMyMoreAction(sourceView: UIView, gesture: ContextGesture?) { guard let component = self.component, let controller = component.controller() else { return @@ -6040,119 +6109,95 @@ public final class StoryItemSetContainerComponent: Component { self.dismissAllTooltips() + let baseRatePromise = ValuePromise(component.storyItemSharedState.baseRate) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - var items: [ContextMenuItem] = [] - - let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0 - let privacyText: String - switch component.slice.item.storyItem.privacy?.base { - case .closeFriends: - privacyText = component.strings.Story_ContextPrivacy_LabelCloseFriends - case .contacts: - if additionalCount != 0 { - privacyText = component.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalCount)").string - } else { - privacyText = component.strings.Story_ContextPrivacy_LabelContacts - } - case .nobody: - if additionalCount != 0 { - privacyText = component.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalCount)) - } else { - privacyText = component.strings.Story_ContextPrivacy_LabelOnlyMe - } - default: - privacyText = component.strings.Story_ContextPrivacy_LabelEveryone - } - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Privacy, textLayout: .secondLineWithValue(privacyText), icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) + + let contextItems = baseRatePromise.get() + |> mapToSignal { [weak self, weak component] baseRate -> Signal in + guard let self, let component else { + return .complete() + } - guard let self else { - return - } - self.openItemPrivacySettings() - }))) - - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) + var items: [ContextMenuItem] = [] - guard let self else { - return + if case .file = component.slice.item.storyItem.media { + var speedValue: String = presentationData.strings.PlaybackSpeed_Normal + var speedIconText: String = "1x" + var didSetSpeedValue = false + for (text, iconText, speed) in speedList(strings: presentationData.strings) { + if abs(speed - baseRate) < 0.01 { + speedValue = text + speedIconText = iconText + didSetSpeedValue = true + break + } + } + if !didSetSpeedValue && baseRate != 1.0 { + speedValue = String(format: "%.1fx", baseRate) + speedIconText = speedValue + } + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PlaybackSpeed_Title, textLayout: .secondLineWithValue(speedValue), icon: { theme in + return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + guard let self else { + c.dismiss(completion: nil) + return + } + + c.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) + }))) + items.append(.separator) } - self.openStoryEditing() - }))) - - items.append(.separator) - items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromProfile : component.strings.Story_Context_SaveToProfile, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Stories/Context Menu/Unpin" : "Stories/Context Menu/Pin"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self, let component = self.component else { - return + let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0 + let privacyText: String + switch component.slice.item.storyItem.privacy?.base { + case .closeFriends: + privacyText = component.strings.Story_ContextPrivacy_LabelCloseFriends + case .contacts: + if additionalCount != 0 { + privacyText = component.strings.Story_ContextPrivacy_LabelContactsExcept("\(additionalCount)").string + } else { + privacyText = component.strings.Story_ContextPrivacy_LabelContacts + } + case .nobody: + if additionalCount != 0 { + privacyText = component.strings.Story_ContextPrivacy_LabelOnlySelected(Int32(additionalCount)) + } else { + privacyText = component.strings.Story_ContextPrivacy_LabelOnlyMe + } + default: + privacyText = component.strings.Story_ContextPrivacy_LabelEveryone } - let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.slice.peer.id, ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).startStandalone() - - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - if component.slice.item.storyItem.isPinned { - self.component?.presentController(UndoOverlayController( - presentationData: presentationData, - content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil, customUndoText: nil), - elevatedLayout: false, - animateInAsReplacement: false, - blurred: true, - action: { _ in return false } - ), nil) - } else { - self.component?.presentController(UndoOverlayController( - presentationData: presentationData, - content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil, customUndoText: nil), - elevatedLayout: false, - animateInAsReplacement: false, - blurred: true, - action: { _ in return false } - ), nil) - } - }))) - - let saveText: String = component.strings.Story_Context_SaveToGallery - items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { - return - } - self.requestSave() - }))) - - if case let .user(accountUser) = component.slice.peer { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextStealthMode, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor) + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Privacy, textLayout: .secondLineWithValue(privacyText), icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in a(.default) guard let self else { return } - if accountUser.isPremium { - self.sendMessageContext.requestStealthMode(view: self) - } else { - self.presentStealthModeUpgradeScreen() - } + self.openItemPrivacySettings() }))) - } - - if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.openStoryEditing() + }))) + + items.append(.separator) + + items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromProfile : component.strings.Story_Context_SaveToProfile, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Stories/Context Menu/Unpin" : "Stories/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in a(.default) @@ -6160,42 +6205,106 @@ public final class StoryItemSetContainerComponent: Component { return } - let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) - |> deliverOnMainQueue).startStandalone(next: { [weak self] link in - guard let self, let component = self.component else { - return - } - if let link { - UIPasteboard.general.string = link - - component.presentController(UndoOverlayController( - presentationData: presentationData, - content: .linkCopied(text: component.strings.Story_ToastLinkCopied), - elevatedLayout: false, - animateInAsReplacement: false, - blurred: true, - action: { _ in return false } - ), nil) - } - }) + let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.slice.peer.id, ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).startStandalone() + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) + if component.slice.item.storyItem.isPinned { + self.component?.presentController(UndoOverlayController( + presentationData: presentationData, + content: .info(title: nil, text: component.strings.Story_ToastRemovedFromProfileText, timeout: nil, customUndoText: nil), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ), nil) + } else { + self.component?.presentController(UndoOverlayController( + presentationData: presentationData, + content: .info(title: component.strings.Story_ToastSavedToProfileTitle, text: component.strings.Story_ToastSavedToProfileText, timeout: nil, customUndoText: nil), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ), nil) + } }))) - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in + + let saveText: String = component.strings.Story_Context_SaveToGallery + items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in a(.default) guard let self else { return } - self.sendMessageContext.performShareAction(view: self) + self.requestSave() }))) + + if case let .user(accountUser) = component.slice.peer { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextStealthMode, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + if accountUser.isPremium { + self.sendMessageContext.requestStealthMode(view: self) + } else { + self.presentStealthModeUpgradeScreen() + } + }))) + } + + if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return + } + + let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) + |> deliverOnMainQueue).startStandalone(next: { [weak self] link in + guard let self, let component = self.component else { + return + } + if let link { + UIPasteboard.general.string = link + + component.presentController(UndoOverlayController( + presentationData: presentationData, + content: .linkCopied(text: component.strings.Story_ToastLinkCopied), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ), nil) + } + }) + }))) + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.sendMessageContext.performShareAction(view: self) + }))) + } + + let (tip, tipSignal) = self.getLinkedStickerPacks() + + return .single(ContextController.Items(id: 0, content: .list(items), tip: tip, tipSignal: tipSignal)) } - let (tip, tipSignal) = self.getLinkedStickerPacks() - - let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal) - - let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .bottom)), items: .single(contextItems), gesture: gesture) + let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .bottom)), items: contextItems, gesture: gesture) contextController.dismissed = { [weak self] in guard let self else { return @@ -6218,184 +6327,179 @@ public final class StoryItemSetContainerComponent: Component { self.dismissAllTooltips() + let baseRatePromise = ValuePromise(component.storyItemSharedState.baseRate) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - var items: [ContextMenuItem] = [] - - if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.editStories) { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { - return - } - self.openStoryEditing() - }))) - } - - if !items.isEmpty { - items.append(.separator) - } - - if channel.hasPermission(.editStories) { - items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromChannel : component.strings.Story_Context_SaveToChannel, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Stories/Context Menu/Unpin" : "Stories/Context Menu/Pin"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self, let component = self.component else { - return - } - - let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.slice.peer.id, ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).startStandalone() - - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - if component.slice.item.storyItem.isPinned { - self.scheduledStoryUnpinnedUndoOverlay = UndoOverlayController( - presentationData: presentationData, - content: .info(title: nil, text: presentationData.strings.Story_ToastRemovedFromChannelText, timeout: nil, customUndoText: nil), - elevatedLayout: false, - animateInAsReplacement: false, - blurred: true, - action: { _ in return false } - ) - } else { - self.component?.presentController(UndoOverlayController( - presentationData: presentationData, - content: .info(title: presentationData.strings.Story_ToastSavedToChannelTitle, text: presentationData.strings.Story_ToastSavedToChannelText, timeout: nil, customUndoText: nil), - elevatedLayout: false, - animateInAsReplacement: false, - blurred: true, - action: { _ in return false } - ), nil) - } - }))) - } - - if component.slice.additionalPeerData.canViewStats { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_ViewStats, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self, let component = self.component else { - return - } - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) - let statsController = component.context.sharedContext.makeStoryStatsController( - context: component.context, - updatedPresentationData: (presentationData, .single(presentationData)), - peerId: component.slice.peer.id, - storyId: component.slice.item.storyItem.id, - storyItem: component.slice.item.storyItem, - fromStory: true - ) - component.controller()?.push(statsController) - }))) - } - - let saveText: String = component.strings.Story_Context_SaveToGallery - items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self else { - return + + let contextItems = baseRatePromise.get() + |> mapToSignal { [weak self, weak component] baseRate -> Signal in + guard let self, let component else { + return .complete() } - self.requestSave() - }))) - - if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - - guard let self, let component = self.component else { - return + var items: [ContextMenuItem] = [] + + if case .file = component.slice.item.storyItem.media { + var speedValue: String = presentationData.strings.PlaybackSpeed_Normal + var speedIconText: String = "1x" + var didSetSpeedValue = false + for (text, iconText, speed) in speedList(strings: presentationData.strings) { + if abs(speed - baseRate) < 0.01 { + speedValue = text + speedIconText = iconText + didSetSpeedValue = true + break + } + } + if !didSetSpeedValue && baseRate != 1.0 { + speedValue = String(format: "%.1fx", baseRate) + speedIconText = speedValue } - let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) - |> deliverOnMainQueue).startStandalone(next: { [weak self] link in + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PlaybackSpeed_Title, textLayout: .secondLineWithValue(speedValue), icon: { theme in + return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + guard let self else { + c.dismiss(completion: nil) + return + } + + c.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) + }))) + items.append(.separator) + } + + if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.editStories) { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.openStoryEditing() + }))) + } + + if !items.isEmpty { + items.append(.separator) + } + + if channel.hasPermission(.editStories) { + items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromChannel : component.strings.Story_Context_SaveToChannel, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: component.slice.item.storyItem.isPinned ? "Stories/Context Menu/Unpin" : "Stories/Context Menu/Pin"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + guard let self, let component = self.component else { return } - if let link { - UIPasteboard.general.string = link - - component.presentController(UndoOverlayController( + + let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.slice.peer.id, ids: [component.slice.item.storyItem.id: component.slice.item.storyItem], isPinned: !component.slice.item.storyItem.isPinned).startStandalone() + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) + if component.slice.item.storyItem.isPinned { + self.scheduledStoryUnpinnedUndoOverlay = UndoOverlayController( presentationData: presentationData, - content: .linkCopied(text: component.strings.Story_ToastLinkCopied), + content: .info(title: nil, text: presentationData.strings.Story_ToastRemovedFromChannelText, timeout: nil, customUndoText: nil), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ) + } else { + self.component?.presentController(UndoOverlayController( + presentationData: presentationData, + content: .info(title: presentationData.strings.Story_ToastSavedToChannelTitle, text: presentationData.strings.Story_ToastSavedToChannelText, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, blurred: true, action: { _ in return false } ), nil) } - }) - }))) - items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in + }))) + } + + if component.slice.additionalPeerData.canViewStats { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_ViewStats, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return + } + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) + let statsController = component.context.sharedContext.makeStoryStatsController( + context: component.context, + updatedPresentationData: (presentationData, .single(presentationData)), + peerId: component.slice.peer.id, + storyId: component.slice.item.storyItem.id, + storyItem: component.slice.item.storyItem, + fromStory: true + ) + component.controller()?.push(statsController) + }))) + } + + let saveText: String = component.strings.Story_Context_SaveToGallery + items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in a(.default) guard let self else { return } - self.sendMessageContext.performShareAction(view: self) + self.requestSave() }))) - } - - var isHidden = false - if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden { - isHidden = storiesHidden - } - items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, a in - a(.default) - guard let self, let component = self.component else { - return - } - - let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden) - - let text = !isHidden ? component.strings.StoryFeed_TooltipArchive(component.slice.peer.compactDisplayTitle).string : component.strings.StoryFeed_TooltipUnarchive(component.slice.peer.compactDisplayTitle).string - let tooltipScreen = TooltipScreen( - context: component.context, - account: component.context.account, - sharedContext: component.context.sharedContext, - text: .markdown(text: text), - style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0), - icon: .peer(peer: component.slice.peer, isStory: true), - action: TooltipScreen.Action( - title: component.strings.Undo_Undo, - action: { - component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden) + if component.slice.item.storyItem.isPublic && (component.slice.peer.addressName != nil || !component.slice.peer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return } - ), - location: .bottom, - shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) } - ) - tooltipScreen.willBecomeDismissed = { [weak self] _ in - guard let self else { - return - } - self.sendMessageContext.tooltipScreen = nil - self.updateIsProgressPaused() + + let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id) + |> deliverOnMainQueue).startStandalone(next: { [weak self] link in + guard let self, let component = self.component else { + return + } + if let link { + UIPasteboard.general.string = link + + component.presentController(UndoOverlayController( + presentationData: presentationData, + content: .linkCopied(text: component.strings.Story_ToastLinkCopied), + elevatedLayout: false, + animateInAsReplacement: false, + blurred: true, + action: { _ in return false } + ), nil) + } + }) + }))) + items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Share, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + self.sendMessageContext.performShareAction(view: self) + }))) } - self.sendMessageContext.tooltipScreen?.dismiss() - self.sendMessageContext.tooltipScreen = tooltipScreen - self.updateIsProgressPaused() - component.controller()?.present(tooltipScreen, in: .current) - }))) - - if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) { - items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + + var isHidden = false + if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden { + isHidden = storiesHidden + } + items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: isHidden ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in a(.default) @@ -6403,46 +6507,88 @@ public final class StoryItemSetContainerComponent: Component { return } - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) - let actionSheet = ActionSheetController(presentationData: presentationData) + let _ = component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: !isHidden) - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: component.strings.Story_ContextDeleteStory, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - - guard let self, let component = self.component else { - return - } - component.delete() - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) - ]) - - actionSheet.dismissed = { [weak self] _ in + let text = !isHidden ? component.strings.StoryFeed_TooltipArchive(component.slice.peer.compactDisplayTitle).string : component.strings.StoryFeed_TooltipUnarchive(component.slice.peer.compactDisplayTitle).string + let tooltipScreen = TooltipScreen( + context: component.context, + account: component.context.account, + sharedContext: component.context.sharedContext, + text: .markdown(text: text), + style: .customBlur(UIColor(rgb: 0x1c1c1c), 0.0), + icon: .peer(peer: component.slice.peer, isStory: true), + action: TooltipScreen.Action( + title: component.strings.Undo_Undo, + action: { + component.context.engine.peers.updatePeerStoriesHidden(id: component.slice.peer.id, isHidden: isHidden) + } + ), + location: .bottom, + shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) } + ) + tooltipScreen.willBecomeDismissed = { [weak self] _ in guard let self else { return } - self.sendMessageContext.actionSheet = nil + self.sendMessageContext.tooltipScreen = nil self.updateIsProgressPaused() } - self.sendMessageContext.actionSheet = actionSheet + self.sendMessageContext.tooltipScreen?.dismiss() + self.sendMessageContext.tooltipScreen = tooltipScreen self.updateIsProgressPaused() - - component.presentController(actionSheet, nil) + component.controller()?.present(tooltipScreen, in: .current) }))) + + if (component.slice.item.storyItem.isMy && channel.hasPermission(.postStories)) || channel.hasPermission(.deleteStories) { + items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return + } + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) + let actionSheet = ActionSheetController(presentationData: presentationData) + + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: component.strings.Story_ContextDeleteStory, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + guard let self, let component = self.component else { + return + } + component.delete() + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + + actionSheet.dismissed = { [weak self] _ in + guard let self else { + return + } + self.sendMessageContext.actionSheet = nil + self.updateIsProgressPaused() + } + self.sendMessageContext.actionSheet = actionSheet + self.updateIsProgressPaused() + + component.presentController(actionSheet, nil) + }))) + } + + let (tip, tipSignal) = self.getLinkedStickerPacks() + return .single(ContextController.Items(id: 0, content: .list(items), tip: tip, tipSignal: tipSignal)) } - let (tip, tipSignal) = self.getLinkedStickerPacks() - - let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal) - - let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .bottom)), items: .single(contextItems), gesture: gesture) + let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .bottom)), items: contextItems, gesture: gesture) contextController.dismissed = { [weak self] in guard let self else { return @@ -6460,6 +6606,8 @@ public final class StoryItemSetContainerComponent: Component { return } + let baseRatePromise = ValuePromise(component.storyItemSharedState.baseRate) + let translationSettings = component.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) |> map { sharedData -> TranslationSettings in let translationSettings: TranslationSettings @@ -6480,9 +6628,10 @@ public final class StoryItemSetContainerComponent: Component { TelegramEngine.EngineData.Item.Peer.IsContact(id: component.slice.peer.id), TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId) ), - translationSettings + translationSettings, + baseRatePromise.get() ) - |> take(1)).startStandalone(next: { [weak self] result, translationSettings in + |> take(1)).startStandalone(next: { [weak self] result, translationSettings, baseRate in guard let self, let component = self.component, let controller = component.controller() else { return } @@ -6498,6 +6647,36 @@ public final class StoryItemSetContainerComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) var items: [ContextMenuItem] = [] + if case .file = component.slice.item.storyItem.media { + var speedValue: String = presentationData.strings.PlaybackSpeed_Normal + var speedIconText: String = "1x" + var didSetSpeedValue = false + for (text, iconText, speed) in speedList(strings: presentationData.strings) { + if abs(speed - baseRate) < 0.01 { + speedValue = text + speedIconText = iconText + didSetSpeedValue = true + break + } + } + if !didSetSpeedValue && baseRate != 1.0 { + speedValue = String(format: "%.1fx", baseRate) + speedIconText = speedValue + } + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PlaybackSpeed_Title, textLayout: .secondLineWithValue(speedValue), icon: { theme in + return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + guard let self else { + c.dismiss(completion: nil) + return + } + + c.pushItems(items: self.contextMenuSpeedItems(value: baseRatePromise) |> map { ContextController.Items(content: .list($0)) }) + }))) + items.append(.separator) + } + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings(), topSearchPeers: topSearchPeers) if !component.slice.peer.isService && isContact { @@ -7002,3 +7181,49 @@ private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to tar return keyframes } + +private func speedList(strings: PresentationStrings) -> [(String, String, Double)] { + return [ + ("0.5x", "0.5x", 0.5), + (strings.PlaybackSpeed_Normal, "1x", 1.0), + ("1.5x", "1.5x", 1.5), + ("2x", "2x", 2.0) + ] +} + +private func optionsRateImage(rate: String, isLarge: Bool, color: UIColor = .white) -> UIImage? { + return generateImage(isLarge ? CGSize(width: 30.0, height: 30.0) : CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in + UIGraphicsPushContext(context) + + context.clear(CGRect(origin: CGPoint(), size: size)) + + if let image = generateTintedImage(image: UIImage(bundleImageName: isLarge ? "Chat/Context Menu/Playspeed30" : "Chat/Context Menu/Playspeed24"), color: .white) { + image.draw(at: CGPoint(x: 0.0, y: 0.0)) + } + + let string = NSMutableAttributedString(string: rate, font: Font.with(size: isLarge ? 11.0 : 10.0, design: .round, weight: .semibold), textColor: color) + + var offset = CGPoint(x: 1.0, y: 0.0) + if rate.count >= 3 { + if rate == "0.5x" { + string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string)) + offset.x += -0.5 + } else { + string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string)) + offset.x += -0.3 + } + } else { + offset.x += -0.3 + } + + if !isLarge { + offset.x *= 0.5 + offset.y *= 0.5 + } + + let boundingRect = string.boundingRect(with: size, options: [], context: nil) + string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + floor((size.height - boundingRect.height) / 2.0))) + + UIGraphicsPopContext() + }) +} diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index ad68560a44..b048dc7ddb 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -695,7 +695,7 @@ public class VideoMessageCameraScreen: ViewController { func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) { let previewReady: Signal if #available(iOS 13.0, *) { - previewReady = self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing |> delay(0.2, queue: Queue.mainQueue()) + previewReady = self.cameraState.isDualCameraEnabled ? self.additionalPreviewView.isPreviewing : self.mainPreviewView.isPreviewing |> delay(0.25, queue: Queue.mainQueue()) } else { previewReady = .single(true) |> delay(0.35, queue: Queue.mainQueue()) } @@ -740,7 +740,7 @@ public class VideoMessageCameraScreen: ViewController { position: self.cameraState.position, isDualEnabled: self.cameraState.isDualCameraEnabled, audio: true, - photo: true, + photo: false, metadata: false, isRoundVideo: true ), diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 1e78b16722..f9d64a470d 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -452,23 +452,58 @@ func updateChatPresentationInterfaceStateImpl( selfController.leftNavigationButton = nil } + var buttonsAnimated = transition.isAnimated if let button = rightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) { if selfController.rightNavigationButton != button { - var animated = transition.isAnimated if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action { - animated = false + buttonsAnimated = false } if case .replyThread = selfController.chatLocation { - animated = false + buttonsAnimated = false } - selfController.navigationItem.setRightBarButton(button.buttonItem, animated: animated) selfController.rightNavigationButton = button } } else if let _ = selfController.rightNavigationButton { - selfController.navigationItem.setRightBarButton(nil, animated: transition.isAnimated) selfController.rightNavigationButton = nil } + if let button = secondaryRightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.secondaryRightNavigationButton, target: selfController, selector: #selector(selfController.secondaryRightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) { + if selfController.secondaryRightNavigationButton != button { + if let currentButton = selfController.secondaryRightNavigationButton?.action, currentButton == button.action { + buttonsAnimated = false + } + if case .replyThread = selfController.chatLocation { + buttonsAnimated = false + } + selfController.secondaryRightNavigationButton = button + } + } else if let _ = selfController.secondaryRightNavigationButton { + selfController.secondaryRightNavigationButton = nil + } + + var rightBarButtons: [UIBarButtonItem] = [] + if let rightNavigationButton = selfController.rightNavigationButton { + rightBarButtons.append(rightNavigationButton.buttonItem) + } + if let secondaryRightNavigationButton = selfController.secondaryRightNavigationButton { + rightBarButtons.append(secondaryRightNavigationButton.buttonItem) + } + var rightBarButtonsUpdated = false + let currentRightBarButtons = selfController.navigationItem.rightBarButtonItems ?? [] + if rightBarButtons.count != currentRightBarButtons.count { + rightBarButtonsUpdated = true + } else { + for i in 0 ..< rightBarButtons.count { + if rightBarButtons[i] !== currentRightBarButtons[i] { + rightBarButtonsUpdated = true + break + } + } + } + if rightBarButtonsUpdated { + selfController.navigationItem.setRightBarButtonItems(rightBarButtons, animated: buttonsAnimated) + } + if let controllerInteraction = selfController.controllerInteraction { if updatedChatPresentationInterfaceState.interfaceState.selectionState != controllerInteraction.selectionState { controllerInteraction.selectionState = updatedChatPresentationInterfaceState.interfaceState.selectionState @@ -524,4 +559,14 @@ func updateChatPresentationInterfaceStateImpl( } selfController.updateDownButtonVisibility() + + if case .standard(.embedded) = selfController.presentationInterfaceState.mode, let controllerInteraction = selfController.controllerInteraction, let interfaceInteraction = selfController.interfaceInteraction { + if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(selfController.presentationInterfaceState, context: selfController.context, currentPanel: selfController.customNavigationPanelNode as? ChatTitleAccessoryPanelNode, controllerInteraction: controllerInteraction, interfaceInteraction: interfaceInteraction, force: true) { + selfController.customNavigationPanelNode = titleAccessoryPanelNode as? ChatControllerCustomNavigationPanelNode + } else { + selfController.customNavigationPanelNode = nil + } + } + + selfController.stateUpdated?(transition) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 1a3f79504e..d3bc3494fb 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -122,6 +122,7 @@ import WallpaperGalleryScreen import WallpaperGridScreen import VideoMessageCameraScreen import TopMessageReactions +import PeerInfoScreen public enum ChatControllerPeekActions { case standard @@ -267,6 +268,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var chatTitleView: ChatTitleView? var leftNavigationButton: ChatNavigationButton? var rightNavigationButton: ChatNavigationButton? + var secondaryRightNavigationButton: ChatNavigationButton? var chatInfoNavigationButton: ChatNavigationButton? var moreBarButton: MoreHeaderButton @@ -452,6 +454,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G weak var slowmodeTooltipController: ChatSlowmodeHintController? weak var currentContextController: ContextController? + public var visibleContextController: ViewController? { + return self.currentContextController + } weak var sendMessageActionsController: ChatSendMessageActionSheetController? var searchResultsController: ChatSearchResultsController? @@ -506,6 +511,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G weak var currentWebAppController: ViewController? weak var currentImportMessageTooltip: UndoOverlayController? + + public var customNavigationBarContentNode: NavigationBarContentNode? + public var customNavigationPanelNode: ChatControllerCustomNavigationPanelNode? + public var stateUpdated: ((ContainedViewLayoutTransition) -> Void)? public override var customData: Any? { return self.chatLocation @@ -4721,13 +4730,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor))) self.moreInfoNavigationButton = ChatNavigationButton(action: .toggleInfoPanel, buttonItem: UIBarButtonItem(customDisplayNode: self.moreBarButton)!) self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in - guard let self = self else { + guard let self else { return } guard case let .peer(peerId) = self.chatLocation else { return } - ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture) + + if peerId == self.context.account.peerId { + PeerInfoScreenImpl.openSavedMessagesMoreMenu(context: self.context, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture) + } else { + ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture) + } } self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) @@ -7064,7 +7078,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if isTracking { strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition) } - strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) + strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode, isRotated: strongSelf.chatDisplayNode.historyNode.rotated) } @@ -12083,6 +12097,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + @objc func secondaryRightNavigationButtonAction() { + if let button = self.secondaryRightNavigationButton { + self.navigationButtonAction(button.action) + } + } + @objc func moreButtonPressed() { self.moreBarButton.play() self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 64547e1e83..f7044bc51e 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1253,7 +1253,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var titleAccessoryPanelBackgroundHeight: CGFloat? var titleAccessoryPanelHitTestSlop: CGFloat? var extraTransition = transition - if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction) { + if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) { if self.titleAccessoryPanelNode != titleAccessoryPanelNode { dismissedTitleAccessoryPanelNode = self.titleAccessoryPanelNode self.titleAccessoryPanelNode = titleAccessoryPanelNode @@ -1656,13 +1656,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { transition.updateFrame(node: backgroundEffectNode, frame: CGRect(origin: CGPoint(), size: layout.size)) } - transition.updateFrame(node: self.backgroundNode, frame: contentBounds) + let wallpaperBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height) + + transition.updateFrame(node: self.backgroundNode, frame: wallpaperBounds) var displayMode: WallpaperDisplayMode = .aspectFill if case .regular = layout.metrics.widthClass, layout.size.height == layout.deviceMetrics.screenSize.width { displayMode = .aspectFit } - self.backgroundNode.updateLayout(size: contentBounds.size, displayMode: displayMode, transition: transition) + self.backgroundNode.updateLayout(size: wallpaperBounds.size, displayMode: displayMode, transition: transition) transition.updateBounds(node: self.historyNodeContainer, bounds: contentBounds) transition.updatePosition(node: self.historyNodeContainer, position: contentBounds.center) @@ -1796,6 +1798,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if self.dismissedAsOverlay { inputBackgroundFrame.origin.y = layout.size.height } + if case .standard(.embedded) = self.chatPresentationInterfaceState.mode { + if self.inputPanelNode == nil { + inputBackgroundFrame.origin.y = layout.size.height + } + } let additionalScrollDistance: CGFloat = 0.0 var scrollToTop = false @@ -1977,7 +1984,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } if !self.historyNode.rotated { - apparentNavigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: 6.0), size: navigateButtonsSize) + apparentNavigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: insets.top + 6.0), size: navigateButtonsSize) } var isInputExpansionEnabled = false @@ -2033,11 +2040,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { inputPanelUpdateTransition = .immediate } - if case .standard(.embedded) = self.chatPresentationInterfaceState.mode { - self.inputPanelBackgroundNode.isHidden = true - self.inputPanelBackgroundSeparatorNode.isHidden = true - self.inputPanelBottomBackgroundSeparatorNode.isHidden = true - } self.inputPanelBackgroundNode.update(size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: intrinsicInputPanelBackgroundNodeSize.height + inputPanelBackgroundExtension), transition: inputPanelUpdateTransition, beginWithCurrentState: true) self.inputPanelBottomBackgroundSeparatorBaseOffset = intrinsicInputPanelBackgroundNodeSize.height inputPanelUpdateTransition.updateFrame(node: self.inputPanelBottomBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: intrinsicInputPanelBackgroundNodeSize.height + inputPanelBackgroundExtension), size: CGSize(width: intrinsicInputPanelBackgroundNodeSize.width, height: UIScreenPixel)), beginWithCurrentState: true) @@ -2661,13 +2663,18 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } self.searchNavigationNode = ChatSearchNavigationContentNode(context: self.context, theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, chatLocation: self.chatPresentationInterfaceState.chatLocation, interaction: interfaceInteraction, presentationInterfaceState: self.chatPresentationInterfaceState) } - self.navigationBar?.setContentNode(self.searchNavigationNode, animated: transitionIsAnimated) + if let navigationBar = self.navigationBar { + navigationBar.setContentNode(self.searchNavigationNode, animated: transitionIsAnimated) + } else { + self.controller?.customNavigationBarContentNode = self.searchNavigationNode + } self.searchNavigationNode?.update(presentationInterfaceState: self.chatPresentationInterfaceState) if activate { self.searchNavigationNode?.activate() } } else if let _ = self.searchNavigationNode { self.searchNavigationNode = nil + self.controller?.customNavigationBarContentNode = nil self.navigationBar?.setContentNode(nil, animated: transitionIsAnimated) } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index 32c7992abc..d0a217ce24 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -18,11 +18,6 @@ import PremiumUI extension ChatControllerImpl { func openMessageReactionContextMenu(message: Message, sourceView: ContextExtractedContentContainingView, gesture: ContextGesture?, value: MessageReaction.Reaction) { - if !self.chatDisplayNode.historyNode.rotated { - gesture?.cancel() - return - } - if message.areReactionsTags(accountPeerId: self.context.account.peerId) { if !self.presentationInterfaceState.isPremium { //TODO:localize diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index d961ac3504..dc3a9629e6 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -10,10 +10,6 @@ import ChatChannelSubscriberInputPanelNode import ChatMessageSelectionInputPanelNode func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputPanelNode?, currentSecondaryPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> (primary: ChatInputPanelNode?, secondary: ChatInputPanelNode?) { - if case .standard(.embedded) = chatPresentationInterfaceState.mode { - return (nil, nil) - } - if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil { return (nil, nil) } @@ -65,6 +61,10 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState } } + if case .standard(.embedded) = chatPresentationInterfaceState.mode { + return (nil, nil) + } + if let selectionState = chatPresentationInterfaceState.interfaceState.selectionState { if let _ = chatPresentationInterfaceState.reportReason { if let currentPanel = (currentPanel as? ChatMessageReportInputPanelNode) ?? (currentSecondaryPanel as? ChatMessageReportInputPanelNode) { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 46ef536683..a7364265f0 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -182,3 +182,11 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present return chatInfoNavigationButton } + +func secondaryRightNavigationButtonForChatInterfaceState(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?, chatInfoNavigationButton: ChatNavigationButton?, moreInfoNavigationButton: ChatNavigationButton?) -> ChatNavigationButton? { + if case .peer(context.account.peerId) = presentationInterfaceState.chatLocation { + return moreInfoNavigationButton + } + + return nil +} diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index e5d2a52832..3db63a6d90 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -5,7 +5,11 @@ import AccountContext import ChatPresentationInterfaceState import ChatControllerInteraction -func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ChatTitleAccessoryPanelNode? { +func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTitleAccessoryPanelNode? { + if !force, case .standard(.embedded) = chatPresentationInterfaceState.mode { + return nil + } + if case .overlay = chatPresentationInterfaceState.mode { return nil } diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 63002ef7ca..9b4547a611 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -856,13 +856,13 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran self.standaloneReactionAnimation = standaloneReactionAnimation } - func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?) { + func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?, isRotated: Bool) { guard let currentItemNode = self.itemNode else { return } if itemNode == nil || itemNode === currentItemNode { if let contextController = self.contextController { - contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) + contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: offset), transition: transition) } if let standaloneReactionAnimation = self.standaloneReactionAnimation { standaloneReactionAnimation.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) @@ -1084,7 +1084,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } } - func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?) { + func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?, isRotated: Bool) { for animatingItemNode in self.animatingItemNodes { animatingItemNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) } @@ -1094,7 +1094,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } } for messageReactionContext in self.messageReactionContexts { - messageReactionContext.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) + messageReactionContext.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode, isRotated: isRotated) } } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 69b0505429..70ce72f365 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -104,7 +104,8 @@ private enum ChatListSearchEntry: Comparable, Identifiable { topForumTopicItems: [], autoremoveTimeout: nil, storyState: nil, - requiresPremiumForMessaging: false + requiresPremiumForMessaging: false, + displayAsTopicList: false )), editing: false, hasActiveRevealControls: false, diff --git a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift index 688c33a1b5..6951efb955 100644 --- a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift @@ -25,7 +25,7 @@ private let backgroundTagImage: UIImage? = { } }() -final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UIScrollViewDelegate { +final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, ChatControllerCustomNavigationPanelNode, UIScrollViewDelegate { private struct Params: Equatable { var width: CGFloat var leftInset: CGFloat @@ -551,6 +551,10 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight, hitTestSlop: 0.0) } + func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, chatController: ChatController) -> LayoutResult { + return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, transition: transition, interfaceState: (chatController as! ChatControllerImpl).presentationInterfaceState) + } + private func update(params: Params, transition: ContainedViewLayoutTransition) { let panelHeight: CGFloat = 39.0 diff --git a/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift index f7973f8c49..8fd917a657 100644 --- a/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift @@ -3,13 +3,10 @@ import UIKit import Display import AsyncDisplayKit import ChatPresentationInterfaceState +import AccountContext class ChatTitleAccessoryPanelNode: ASDisplayNode { - struct LayoutResult { - var backgroundHeight: CGFloat - var insetHeight: CGFloat - var hitTestSlop: CGFloat - } + typealias LayoutResult = ChatControllerCustomNavigationPanelNode.LayoutResult var interfaceInteraction: ChatPanelInterfaceInteraction? diff --git a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift index cb8f91145c..18cb29e417 100644 --- a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift @@ -150,7 +150,6 @@ final class ManagedAudioRecorderContext { private let beganWithTone: (Bool) -> Void private var paused = true - private var manuallyPaused = false private let queue: Queue private let mediaManager: MediaManager @@ -414,11 +413,9 @@ final class ManagedAudioRecorderContext { return Signal { subscriber in queue.async { if let strongSelf = self { - if !strongSelf.manuallyPaused { - strongSelf.hasAudioSession = false - strongSelf.stop() - strongSelf.recordingState.set(.stopped) - } + strongSelf.hasAudioSession = false + strongSelf.stop() + strongSelf.recordingState.set(.stopped) subscriber.putCompletion() } } @@ -453,17 +450,13 @@ final class ManagedAudioRecorderContext { func pause() { assert(self.queue.isCurrent()) - self.manuallyPaused = true + self.stop() } func resume() { assert(self.queue.isCurrent()) - if self.manuallyPaused { - self.manuallyPaused = false - } else if self.paused { - self.start() - } + self.start() } func stop() { @@ -507,7 +500,7 @@ final class ManagedAudioRecorderContext { free(buffer.mData) } - if !self.processSamples || self.manuallyPaused { + if !self.processSamples { return } diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index cddcdac827..e130eb1341 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -41,6 +41,13 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam return false } } + } else if case let .peer(peer) = params.chatLocation, peer.id == params.context.account.peerId { + viewForumAsMessages = params.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.DisplaySavedChatsAsTopics() + ) + |> map { value in + return !value + } } let _ = (viewForumAsMessages @@ -93,6 +100,14 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam return } + if !viewForumAsMessages, params.subject == nil, case let .peer(peer) = params.chatLocation, peer.id == params.context.account.peerId { + if let controller = params.context.sharedContext.makePeerInfoController(context: params.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + params.navigationController.pushViewController(controller, animated: params.animated, completion: { + }) + return + } + } + var found = false var isFirst = true if params.useExisting {