mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-28 19:05:49 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
8f60d4e90d
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -442,7 +442,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
topForumTopicItems: [],
|
||||
autoremoveTimeout: nil,
|
||||
storyState: nil,
|
||||
requiresPremiumForMessaging: false
|
||||
requiresPremiumForMessaging: false,
|
||||
displayAsTopicList: false
|
||||
)),
|
||||
editing: false,
|
||||
hasActiveRevealControls: false,
|
||||
|
||||
@ -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
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -235,7 +235,8 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
},
|
||||
displaySavedMessagesAsTopicListPreferencesKey: PreferencesKeys.displaySavedChatsAsTopics()
|
||||
)
|
||||
}()
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -587,6 +587,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
|
||||
let (buttonWidth, continueLayout) = makeActionButtonLayout(
|
||||
maxContentsWidth,
|
||||
nil,
|
||||
buttonIconImage,
|
||||
cornerIcon,
|
||||
actionTitle,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -20,6 +20,7 @@ swift_library(
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode",
|
||||
"//submodules/ContextUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -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? {
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,6 +137,7 @@ swift_library(
|
||||
"//submodules/MediaPickerUI",
|
||||
"//submodules/AttachmentUI",
|
||||
"//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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] = []
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)))
|
||||
|
||||
|
||||
@ -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)))
|
||||
|
||||
|
||||
@ -937,7 +937,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
topForumTopicItems: [],
|
||||
autoremoveTimeout: nil,
|
||||
storyState: nil,
|
||||
requiresPremiumForMessaging: false
|
||||
requiresPremiumForMessaging: false,
|
||||
displayAsTopicList: false
|
||||
)),
|
||||
editing: false,
|
||||
hasActiveRevealControls: false,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
),
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -104,7 +104,8 @@ private enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
topForumTopicItems: [],
|
||||
autoremoveTimeout: nil,
|
||||
storyState: nil,
|
||||
requiresPremiumForMessaging: false
|
||||
requiresPremiumForMessaging: false,
|
||||
displayAsTopicList: false
|
||||
)),
|
||||
editing: false,
|
||||
hasActiveRevealControls: false,
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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?
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user