Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mikhail Filimonov 2024-01-29 09:15:35 +04:00
commit 8f60d4e90d
79 changed files with 1705 additions and 687 deletions

View File

@ -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<Bool> { get }
@ -919,9 +937,16 @@ public protocol ChatController: ViewController {
var purposefulAction: (() -> Void)? { get set }
var stateUpdated: ((ContainedViewLayoutTransition) -> Void)? { get set }
var selectedMessageIds: Set<EngineMessage.Id>? { get }
var presentationInterfaceStateSignal: Signal<Any, NoError> { 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)

View File

@ -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 {

View File

@ -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)")
}
}
}

View File

@ -40,11 +40,12 @@ private final class VideoRecorderImpl {
private var pendingAudioSampleBuffers: [CMSampleBuffer] = []
private var _duration: CMTime = .zero
private var _duration = Atomic<CMTime>(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<Error?>(value: nil)
private var stopped = false
private var _stopped = Atomic<Bool>(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

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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<Bool>(false, ignoreRepeated: true)
private let suggestedChatListNotice = Promise<ChatListNotice?>(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<EngineContactList?, NoError>
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)

View File

@ -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

View File

@ -114,6 +114,8 @@ public func chatListFilterPredicate(filter: ChatListFilterData, accountPeerId: E
}
func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account) -> Signal<ChatListNodeViewUpdate, NoError> {
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
))
}

View File

@ -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
}

View File

@ -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]

View File

@ -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 {

View File

@ -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() {

View File

@ -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 {

View File

@ -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]

View File

@ -566,6 +566,9 @@ final class MutableChatListView {
private var currentHiddenPeerIds = Set<PeerId>()
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
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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<GlobalMessageIdsNamespace>,
@ -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
}
}

View File

@ -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,

View File

@ -442,7 +442,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
topForumTopicItems: [],
autoremoveTimeout: nil,
storyState: nil,
requiresPremiumForMessaging: false
requiresPremiumForMessaging: false,
displayAsTopicList: false
)),
editing: false,
hasActiveRevealControls: false,

View File

@ -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
))
}

View File

@ -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 {

View File

@ -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
))
}

View File

@ -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 {

View File

@ -235,7 +235,8 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
}
return result
}
},
displaySavedMessagesAsTopicListPreferencesKey: PreferencesKeys.displaySavedChatsAsTopics()
)
}()

View File

@ -18,6 +18,14 @@ public enum EnginePeerCachedInfoItem<T> {
}
}
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<T>, rhs: EnginePeerCachedInfoItem<T>) -> 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
}
}
}
}
}

View File

@ -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:

View File

@ -328,9 +328,10 @@ public extension TelegramEngine {
}
public func chatList(group: EngineChatList.Group, count: Int) -> Signal<EngineChatList, NoError> {
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)
}
}

View File

@ -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()
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -587,6 +587,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
let (buttonWidth, continueLayout) = makeActionButtonLayout(
maxContentsWidth,
nil,
buttonIconImage,
cornerIcon,
actionTitle,

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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
]
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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))

View File

@ -20,6 +20,7 @@ swift_library(
"//submodules/ComponentFlow",
"//submodules/AppBundle",
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode",
"//submodules/ContextUI",
],
visibility = [
"//visibility:public",

View File

@ -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? {

View File

@ -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<Bool, NoError> { 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) {
}
}
}

View File

@ -137,6 +137,7 @@ swift_library(
"//submodules/MediaPickerUI",
"//submodules/AttachmentUI",
"//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent",
"//submodules/Components/MultilineTextComponent",
],
visibility = [
"//visibility:public",

View File

@ -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<Bool>()
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 {

View File

@ -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)

View File

@ -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<Bool>()
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 {

View File

@ -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))

View File

@ -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))

View File

@ -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<Bool, NoError>
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<Bool, NoError>
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<Bool, NoError>
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<PeerInfoScreenData, NoError> in
|> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags -> Signal<PeerInfoScreenData, NoError> 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
))
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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<Empty>?
let titleCredibilityIconView: ComponentHostView<Empty>
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<Empty>
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

View File

@ -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<Bool>()
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] = []

View File

@ -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<PresentationData, NoError>)?, 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)
}
}

View File

@ -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

View File

@ -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<Bool>()
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)))

View File

@ -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<Bool>()
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)))

View File

@ -937,7 +937,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
topForumTopicItems: [],
autoremoveTimeout: nil,
storyState: nil,
requiresPremiumForMessaging: false
requiresPremiumForMessaging: false,
displayAsTopicList: false
)),
editing: false,
hasActiveRevealControls: false,

View File

@ -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",

View File

@ -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
}

View File

@ -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

View File

@ -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<Double>) -> 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<Double?>(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<Double>(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<ContextController.Items , NoError> 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<Double>(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<ContextController.Items , NoError> 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<Double>(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()
})
}

View File

@ -695,7 +695,7 @@ public class VideoMessageCameraScreen: ViewController {
func withReadyCamera(isFirstTime: Bool = false, _ f: @escaping () -> Void) {
let previewReady: Signal<Bool, NoError>
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
),

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

@ -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) {

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -104,7 +104,8 @@ private enum ChatListSearchEntry: Comparable, Identifiable {
topForumTopicItems: [],
autoremoveTimeout: nil,
storyState: nil,
requiresPremiumForMessaging: false
requiresPremiumForMessaging: false,
displayAsTopicList: false
)),
editing: false,
hasActiveRevealControls: false,

View File

@ -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

View File

@ -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?

View File

@ -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
}

View File

@ -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 {