diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index ef5cd03b33..c232006fb2 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -419,6 +419,8 @@ public protocol PresentationGroupCall: AnyObject { var memberEvents: Signal { get } var reconnectedAsEvents: Signal { get } + var onMutedSpeechActivityDetected: ((Bool) -> Void)? { get set } + func toggleScheduledSubscription(_ subscribe: Bool) func schedule(timestamp: Int32) func startScheduled() diff --git a/submodules/AudioBlob/BUILD b/submodules/AudioBlob/BUILD index 72c69fa1aa..dc0d2ee1d4 100644 --- a/submodules/AudioBlob/BUILD +++ b/submodules/AudioBlob/BUILD @@ -10,9 +10,11 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/LegacyComponents:LegacyComponents", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/LegacyComponents", + "//submodules/MetalEngine", + "//submodules/TelegramUI/Components/Calls/CallScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/AudioBlob/Sources/BlobView.swift b/submodules/AudioBlob/Sources/BlobView.swift index c1b287fbe5..f4418ed1e8 100644 --- a/submodules/AudioBlob/Sources/BlobView.swift +++ b/submodules/AudioBlob/Sources/BlobView.swift @@ -3,6 +3,8 @@ import UIKit import AsyncDisplayKit import Display import LegacyComponents +import CallScreen +import MetalEngine public final class VoiceBlobNode: ASDisplayNode { public init( @@ -36,9 +38,7 @@ public final class VoiceBlobNode: ASDisplayNode { } public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration { - private let smallBlob: BlobNode - private let mediumBlob: BlobNode - private let bigBlob: BlobNode + private let blobsLayer: CallBlobsLayer private let maxLevel: CGFloat @@ -65,7 +65,7 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco ) { self.maxLevel = maxLevel - self.smallBlob = BlobNode( + /*self.smallBlob = BlobNode( pointsCount: 8, minRandomness: 0.1, maxRandomness: 0.5, @@ -97,7 +97,9 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco maxScale: bigBlobRange.max, scaleSpeed: 0.2, isCircle: false - ) + )*/ + + self.blobsLayer = CallBlobsLayer(colors: [UIColor.white, UIColor.white.withAlphaComponent(0.3), UIColor.white.withAlphaComponent(0.15)]) var updateInHierarchy: ((Bool) -> Void)? self.hierarchyTrackingNode = HierarchyTrackingNode({ value in @@ -108,18 +110,21 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco self.addSubnode(self.hierarchyTrackingNode) - self.addSubnode(self.bigBlob) + /*self.addSubnode(self.bigBlob) self.addSubnode(self.mediumBlob) - self.addSubnode(self.smallBlob) + self.addSubnode(self.smallBlob)*/ - displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in + self.layer.addSublayer(self.blobsLayer) + + self.displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in guard let strongSelf = self else { return } strongSelf.presentationAudioLevel = strongSelf.presentationAudioLevel * 0.9 + strongSelf.audioLevel * 0.1 + strongSelf.updateAudioLevel() - strongSelf.smallBlob.level = strongSelf.presentationAudioLevel + /*strongSelf.smallBlob.level = strongSelf.presentationAudioLevel strongSelf.mediumBlob.level = strongSelf.presentationAudioLevel - strongSelf.bigBlob.level = strongSelf.presentationAudioLevel + strongSelf.bigBlob.level = strongSelf.presentationAudioLevel*/ } updateInHierarchy = { [weak self] value in @@ -138,12 +143,20 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco } public func setColor(_ color: UIColor, animated: Bool) { + let transition: ContainedViewLayoutTransition + if animated { + transition = .animated(duration: 0.2, curve: .easeInOut) + } else { + transition = .immediate + } + transition.updateTintColor(layer: self.blobsLayer, color: color) + if let isManuallyInHierarchy = self.isManuallyInHierarchy, !isManuallyInHierarchy { return } - smallBlob.setColor(color, animated: animated) + /*smallBlob.setColor(color, animated: animated) mediumBlob.setColor(color.withAlphaComponent(0.3), animated: animated) - bigBlob.setColor(color.withAlphaComponent(0.15), animated: animated) + bigBlob.setColor(color.withAlphaComponent(0.15), animated: animated)*/ } public func updateLevel(_ level: CGFloat) { @@ -153,9 +166,9 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco public func updateLevel(_ level: CGFloat, immediately: Bool = false) { let normalizedLevel = min(1, max(level / maxLevel, 0)) - smallBlob.updateSpeedLevel(to: normalizedLevel) + /*smallBlob.updateSpeedLevel(to: normalizedLevel) mediumBlob.updateSpeedLevel(to: normalizedLevel) - bigBlob.updateSpeedLevel(to: normalizedLevel) + bigBlob.updateSpeedLevel(to: normalizedLevel)*/ audioLevel = normalizedLevel if immediately { @@ -163,6 +176,13 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco } } + private func updateAudioLevel() { + let additionalAvatarScale = CGFloat(max(0.0, min(self.presentationAudioLevel * 18.0, 5.0)) * 0.05) + let blobAmplificationFactor: CGFloat = 2.0 + let blobScale = 1.0 + additionalAvatarScale * blobAmplificationFactor + self.blobsLayer.transform = CATransform3DMakeScale(blobScale, blobScale, 1.0) + } + public func startAnimating() { self.startAnimating(immediately: false) } @@ -171,13 +191,13 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco guard !isAnimating else { return } isAnimating = true - if !immediately { + /*if !immediately { mediumBlob.layer.animateScale(from: 0.75, to: 1, duration: 0.35, removeOnCompletion: false) bigBlob.layer.animateScale(from: 0.75, to: 1, duration: 0.35, removeOnCompletion: false) } else { mediumBlob.layer.removeAllAnimations() bigBlob.layer.removeAllAnimations() - } + }*/ updateBlobsState() @@ -192,8 +212,8 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco guard isAnimating else { return } isAnimating = false - mediumBlob.layer.animateScale(from: 1.0, to: 0.75, duration: duration, removeOnCompletion: false) - bigBlob.layer.animateScale(from: 1.0, to: 0.75, duration: duration, removeOnCompletion: false) + /*mediumBlob.layer.animateScale(from: 1.0, to: 0.75, duration: duration, removeOnCompletion: false) + bigBlob.layer.animateScale(from: 1.0, to: 0.75, duration: duration, removeOnCompletion: false)*/ updateBlobsState() @@ -201,7 +221,7 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco } private func updateBlobsState() { - if self.isAnimating { + /*if self.isAnimating { if self.smallBlob.frame.size != .zero { smallBlob.startAnimating() mediumBlob.startAnimating() @@ -211,15 +231,19 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco smallBlob.stopAnimating() mediumBlob.stopAnimating() bigBlob.stopAnimating() - } + }*/ } override public func layoutSubviews() { super.layoutSubviews() - self.smallBlob.frame = bounds + /*self.smallBlob.frame = bounds self.mediumBlob.frame = bounds - self.bigBlob.frame = bounds + self.bigBlob.frame = bounds*/ + + let blobsFrame = bounds.insetBy(dx: floor(bounds.width * 0.12), dy: floor(bounds.height * 0.12)) + self.blobsLayer.position = blobsFrame.center + self.blobsLayer.bounds = CGRect(origin: CGPoint(), size: blobsFrame.size) self.updateBlobsState() } diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index 15849fc8fa..0152458b2c 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -1265,7 +1265,15 @@ public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bo return false } } else if case .customChatContents = state.chatLocation { - return true + if case let .customChatContents(contents) = state.subject { + if case .hashTagSearch = contents.kind { + return false + } else { + return true + } + } else { + return true + } } else { return false } diff --git a/submodules/GraphUI/Sources/ChartNode.swift b/submodules/GraphUI/Sources/ChartNode.swift index ae351ac01a..c8fec06e72 100644 --- a/submodules/GraphUI/Sources/ChartNode.swift +++ b/submodules/GraphUI/Sources/ChartNode.swift @@ -87,7 +87,7 @@ public func createChartController(_ data: String, type: ChartType, rate: Double controller = StackedBarsChartController(chartsCollection: collection) controller.isZoomable = false case .currency: - controller = StackedBarsChartController(chartsCollection: collection, isCrypto: true, rate: rate) + controller = StackedBarsChartController(chartsCollection: collection, currency: .ton, rate: rate) controller.isZoomable = false case .step: controller = StepBarsChartController(chartsCollection: collection) diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index ed0b1e120e..e80e972eba 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -853,6 +853,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { public let isStream: Bool + public var onMutedSpeechActivityDetected: ((Bool) -> Void)? + init( accountContext: AccountContext, audioSession: ManagedAudioSession, @@ -1674,8 +1676,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { strongSelf.requestCall(movingFromBroadcastToRtc: false) } } - }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: false, disableAudioInput: self.isStream, preferX264: self.accountContext.sharedContext.immediateExperimentalUISettings.preferredVideoCodec == "H264", logPath: allocateCallLogPath(account: self.account) - )) + }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: false, disableAudioInput: self.isStream, preferX264: self.accountContext.sharedContext.immediateExperimentalUISettings.preferredVideoCodec == "H264", logPath: allocateCallLogPath(account: self.account), onMutedSpeechActivityDetected: { [weak self] value in + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + strongSelf.onMutedSpeechActivityDetected?(value) + } + })) } self.genericCallContext = genericCallContext @@ -2967,7 +2975,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.hasScreencast = true - let screencastCallContext = OngoingGroupCallContext(audioSessionActive: .single(true), video: self.screencastCapturer, requestMediaChannelDescriptions: { _, _ in EmptyDisposable }, rejoinNeeded: { }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false, disableAudioInput: true, preferX264: false, logPath: "") + let screencastCallContext = OngoingGroupCallContext(audioSessionActive: .single(true), video: self.screencastCapturer, requestMediaChannelDescriptions: { _, _ in EmptyDisposable }, rejoinNeeded: { }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false, disableAudioInput: true, preferX264: false, logPath: "", onMutedSpeechActivityDetected: { _ in }) self.screencastCallContext = screencastCallContext self.screencastJoinDisposable.set((screencastCallContext.joinPayload diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index e21733c55b..9bd0f391da 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -2452,6 +2452,24 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController } }) } + + var lastTimestamp = 0.0 + self.call.onMutedSpeechActivityDetected = { [weak self] value in + Queue.mainQueue().async { + guard let self, value else { + return + } + let timestamp = CFAbsoluteTimeGetCurrent() + if lastTimestamp + 1000.0 < timestamp { + lastTimestamp = timestamp + + //TODO:localize + self.presentUndoOverlay(content: .info(title: nil, text: "Your microphone is muted.", timeout: nil, customUndoText: nil), action: { _ in + return false + }) + } + } + } } deinit { diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Metal/CallScreenShaders.metal b/submodules/TelegramUI/Components/Calls/CallScreen/Metal/CallScreenShaders.metal index 1402203f29..c850eeada5 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Metal/CallScreenShaders.metal +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Metal/CallScreenShaders.metal @@ -230,10 +230,10 @@ vertex BlobVertexOut callBlobVertex( } fragment half4 callBlobFragment( - BlobVertexOut in [[stage_in]] + BlobVertexOut in [[stage_in]], + const device float4 &color [[ buffer(0) ]] ) { - half alpha = 0.35; - return half4(1.0 * alpha, 1.0 * alpha, 1.0 * alpha, alpha); + return half4(color.r * color.a, color.g * color.a, color.b * color.a, color.a); } kernel void videoBiPlanarToRGBA( diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBlobsLayer.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBlobsLayer.swift index 337334e6b3..1fcba0be18 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBlobsLayer.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/CallBlobsLayer.swift @@ -7,10 +7,13 @@ public final class CallBlobsLayer: MetalEngineSubjectLayer, MetalEngineSubject { public var internalData: MetalEngineSubjectInternalData? private struct Blob { + var color: SIMD4 var points: [Float] var nextPoints: [Float] - init(count: Int) { + init(count: Int, color: SIMD4) { + self.color = color + self.points = (0 ..< count).map { _ in Float.random(in: 0.0 ... 1.0) } @@ -71,7 +74,7 @@ public final class CallBlobsLayer: MetalEngineSubjectLayer, MetalEngineSubject { private var displayLinkSubscription: SharedDisplayLinkDriver.Link? - override public init() { + public init(colors: [UIColor] = [UIColor(white: 1.0, alpha: 0.35), UIColor(white: 1.0, alpha: 0.35)]) { super.init() self.didEnterHierarchy = { [weak self] in @@ -100,8 +103,14 @@ public final class CallBlobsLayer: MetalEngineSubjectLayer, MetalEngineSubject { } self.isOpaque = false - self.blobs = (0 ..< 2).map { _ in - Blob(count: 8) + self.blobs = colors.reversed().map { color in + var r: CGFloat = 0.0 + var g: CGFloat = 0.0 + var b: CGFloat = 0.0 + var a: CGFloat = 0.0 + color.getRed(&r, green: &g, blue: &b, alpha: &a) + + return Blob(count: 8, color: SIMD4(Float(r), Float(g), Float(b), Float(a))) } } @@ -137,6 +146,9 @@ public final class CallBlobsLayer: MetalEngineSubjectLayer, MetalEngineSubject { encoder.setVertexBytes(&points, length: MemoryLayout.size * points.count, index: 1) encoder.setVertexBytes(&count, length: MemoryLayout.size, index: 2) + var color = blobs[i].color + encoder.setFragmentBytes(&color, length: MemoryLayout.size * 4, index: 0) + encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3 * 8 * points.count) } }) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index d8951af158..154c26a9d3 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -1747,7 +1747,16 @@ extension ChatControllerImpl { return } if let messageId = messageId { - if canSendMessagesToChat(strongSelf.presentationInterfaceState) { + let intrinsicCanSendMessagesHere = canSendMessagesToChat(strongSelf.presentationInterfaceState) + var canSendMessagesHere = intrinsicCanSendMessagesHere + if case .standard(.embedded) = strongSelf.presentationInterfaceState.mode { + canSendMessagesHere = false + } + if case .inline = strongSelf.presentationInterfaceState.mode { + canSendMessagesHere = false + } + + if canSendMessagesHere { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ @@ -1771,11 +1780,18 @@ extension ChatControllerImpl { messageId: messageId, quote: nil ) + completion(.immediate, { guard let self else { return } - moveReplyMessageToAnotherChat(selfController: self, replySubject: replySubject) + if intrinsicCanSendMessagesHere { + if let peerId = self.chatLocation.peerId { + moveReplyToChat(selfController: self, peerId: peerId, threadId: self.chatLocation.threadId, replySubject: replySubject, completion: {}) + } + } else { + moveReplyMessageToAnotherChat(selfController: self, replySubject: replySubject) + } }) } } else { diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 2a26689216..cd000238ec 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -617,73 +617,8 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj selfController.searchResultsController = nil strongController.dismiss() } else { - if let navigationController = selfController.navigationController as? NavigationController { - for controller in navigationController.viewControllers { - if let maybeChat = controller as? ChatControllerImpl { - if case .peer(peerId) = maybeChat.chatLocation { - var isChatPinnedMessages = false - if case .pinnedMessages = maybeChat.presentationInterfaceState.subject { - isChatPinnedMessages = true - } - if !isChatPinnedMessages { - maybeChat.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }) }) - selfController.dismiss() - strongController.dismiss() - return - } - } - } - } - } - - let _ = (ChatInterfaceState.update(engine: selfController.context.engine, peerId: peerId, threadId: threadId, { currentState in - return currentState.withUpdatedReplyMessageSubject(replySubject) - }) - |> deliverOnMainQueue).startStandalone(completed: { [weak selfController] in - guard let selfController else { - return - } - let proceed: (ChatController) -> Void = { [weak selfController] chatController in - guard let selfController else { - return - } - selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withoutSelectionState() }) }) - - let navigationController: NavigationController? - if let parentController = selfController.parentController { - navigationController = (parentController.navigationController as? NavigationController) - } else { - navigationController = selfController.effectiveNavigationController - } - - if let navigationController = navigationController { - var viewControllers = navigationController.viewControllers - if threadId != nil { - viewControllers.insert(chatController, at: viewControllers.count - 2) - } else { - viewControllers.insert(chatController, at: viewControllers.count - 1) - } - navigationController.setViewControllers(viewControllers, animated: false) - - selfController.controllerNavigationDisposable.set((chatController.ready.get() - |> SwiftSignalKit.filter { $0 } - |> take(1) - |> deliverOnMainQueue).startStrict(next: { [weak navigationController] _ in - viewControllers.removeAll(where: { $0 is PeerSelectionController }) - navigationController?.setViewControllers(viewControllers, animated: true) - })) - } - } - if let threadId = threadId { - let _ = (selfController.context.sharedContext.chatControllerForForumThread(context: selfController.context, peerId: peerId, threadId: threadId) - |> deliverOnMainQueue).startStandalone(next: { chatController in - proceed(chatController) - }) - } else { - let chatController = ChatControllerImpl(context: selfController.context, chatLocation: .peer(id: peerId)) - chatController.activateInput(type: .text) - proceed(chatController) - } + moveReplyToChat(selfController: selfController, peerId: peerId, threadId: threadId, replySubject: replySubject, completion: { [weak strongController] in + strongController?.dismiss() }) } } @@ -692,6 +627,86 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj }) } +func moveReplyToChat(selfController: ChatControllerImpl, peerId: EnginePeer.Id, threadId: Int64?, replySubject: ChatInterfaceState.ReplyMessageSubject, completion: @escaping () -> Void) { + if let navigationController = selfController.effectiveNavigationController { + for controller in navigationController.viewControllers { + if let maybeChat = controller as? ChatControllerImpl { + if case .peer(peerId) = maybeChat.chatLocation { + var isChatPinnedMessages = false + if case .pinnedMessages = maybeChat.presentationInterfaceState.subject { + isChatPinnedMessages = true + } + if !isChatPinnedMessages { + maybeChat.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }) }) + + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === maybeChat }), index != viewControllers.count - 1 { + viewControllers.removeSubrange((index + 1) ..< viewControllers.count) + navigationController.setViewControllers(viewControllers, animated: true) + } else { + selfController.dismiss() + } + + completion() + return + } + } + } + } + } + + let _ = (ChatInterfaceState.update(engine: selfController.context.engine, peerId: peerId, threadId: threadId, { currentState in + return currentState.withUpdatedReplyMessageSubject(replySubject) + }) + |> deliverOnMainQueue).startStandalone(completed: { [weak selfController] in + guard let selfController else { + return + } + let proceed: (ChatController) -> Void = { [weak selfController] chatController in + guard let selfController else { + return + } + selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withoutSelectionState() }) }) + + let navigationController: NavigationController? + if let parentController = selfController.parentController { + navigationController = (parentController.navigationController as? NavigationController) + } else { + navigationController = selfController.effectiveNavigationController + } + + if let navigationController = navigationController { + var viewControllers = navigationController.viewControllers + if threadId != nil { + viewControllers.insert(chatController, at: viewControllers.count - 2) + } else { + viewControllers.insert(chatController, at: viewControllers.count - 1) + } + navigationController.setViewControllers(viewControllers, animated: false) + + selfController.controllerNavigationDisposable.set((chatController.ready.get() + |> SwiftSignalKit.filter { $0 } + |> take(1) + |> timeout(0.2, queue: .mainQueue(), alternate: .single(true)) + |> deliverOnMainQueue).startStrict(next: { [weak navigationController] _ in + viewControllers.removeAll(where: { $0 is PeerSelectionController }) + navigationController?.setViewControllers(viewControllers, animated: true) + })) + } + } + if let threadId = threadId { + let _ = (selfController.context.sharedContext.chatControllerForForumThread(context: selfController.context, peerId: peerId, threadId: threadId) + |> deliverOnMainQueue).startStandalone(next: { chatController in + proceed(chatController) + }) + } else { + let chatController = ChatControllerImpl(context: selfController.context, chatLocation: .peer(id: peerId)) + chatController.activateInput(type: .text) + proceed(chatController) + } + }) +} + private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, getContextController: @escaping () -> ContextController?, replySelectionState: Promise) -> ContextController.Source? { guard let peerId = selfController.chatLocation.peerId else { return nil diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 932855e496..e5a53ffd22 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -282,7 +282,7 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, accountPeerId: PeerId) -> Bool { if case let .customChatContents(contents) = chatPresentationInterfaceState.subject, case .hashTagSearch = contents.kind { - return false + return true } if case .customChatContents = chatPresentationInterfaceState.chatLocation { return true @@ -303,7 +303,7 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS } switch chatPresentationInterfaceState.mode { case .inline: - return false + return true case .standard(.embedded): return false default: @@ -905,15 +905,17 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState messageActions.editTags = Set() } - return (MessageContextMenuData( + let data = MessageContextMenuData( starStatus: stickerSaveStatus, - canReply: canReply && !isEmbeddedMode, + canReply: canReply, canPin: canPin && !isEmbeddedMode, canEdit: canEdit && !isEmbeddedMode, canSelect: canSelect && !isEmbeddedMode, resourceStatus: resourceStatus, messageActions: messageActions - ), updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer) + ) + + return (data, updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer) } return dataSignal diff --git a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift index da675ea763..318de27a4d 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift @@ -323,7 +323,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction } func makeCustomContents() -> UIView? { - if self.sendButtonHasApplyIcon { + if self.sendButtonHasApplyIcon || self.effectBadgeView != nil { let result = UIView() result.frame = self.bounds if let copyView = self.sendContainerNode.view.snapshotView(afterScreenUpdates: false) { diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index daefa3fa48..c673e528cb 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -463,7 +463,7 @@ public final class OngoingGroupCallContext { private let audioSessionActiveDisposable = MetaDisposable() - init(queue: Queue, inputDeviceId: String, outputDeviceId: String, audioSessionActive: Signal, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String) { + init(queue: Queue, inputDeviceId: String, outputDeviceId: String, audioSessionActive: Signal, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String, onMutedSpeechActivityDetected: @escaping (Bool) -> Void) { self.queue = queue #if os(iOS) @@ -576,6 +576,9 @@ public final class OngoingGroupCallContext { disableAudioInput: disableAudioInput, preferX264: preferX264, logPath: logPath, + onMutedSpeechActivityDetected: { value in + onMutedSpeechActivityDetected(value) + }, audioDevice: audioDevice ) #else @@ -669,7 +672,8 @@ public final class OngoingGroupCallContext { enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, preferX264: preferX264, - logPath: logPath + logPath: logPath, + onMutedSpeechActivityDetected: { _ in } ) #endif @@ -1109,10 +1113,10 @@ public final class OngoingGroupCallContext { } } - public init(inputDeviceId: String = "", outputDeviceId: String = "", audioSessionActive: Signal, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String) { + public init(inputDeviceId: String = "", outputDeviceId: String = "", audioSessionActive: Signal, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool, logPath: String, onMutedSpeechActivityDetected: @escaping (Bool) -> Void) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, audioSessionActive: audioSessionActive, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, preferX264: preferX264, logPath: logPath) + return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, audioSessionActive: audioSessionActive, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, preferX264: preferX264, logPath: logPath, onMutedSpeechActivityDetected: onMutedSpeechActivityDetected) }) } diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index d7b772dfd0..8b2c52d3cd 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -414,6 +414,7 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) { disableAudioInput:(bool)disableAudioInput preferX264:(bool)preferX264 logPath:(NSString * _Nonnull)logPath +onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice; - (void)stop; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 74f1f6f5a9..fe3df0061a 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -1669,6 +1669,8 @@ private: rtc::Thread *_currentAudioDeviceModuleThread; SharedCallAudioDevice * _audioDevice; + + void (^_onMutedSpeechActivityDetected)(bool); } @end @@ -1691,6 +1693,7 @@ private: disableAudioInput:(bool)disableAudioInput preferX264:(bool)preferX264 logPath:(NSString * _Nonnull)logPath +onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice { self = [super init]; if (self != nil) { @@ -1703,6 +1706,8 @@ audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice { _networkStateUpdated = [networkStateUpdated copy]; _videoCapturer = videoCapturer; + _onMutedSpeechActivityDetected = [onMutedSpeechActivityDetected copy]; + _audioDevice = audioDevice; std::shared_ptr> audioDeviceModule; if (_audioDevice) { @@ -1917,12 +1922,19 @@ audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice { return std::make_shared(task); }, .minOutgoingVideoBitrateKbit = minOutgoingVideoBitrateKbit, - .createAudioDeviceModule = [weakSelf, queue, disableAudioInput, audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr { + .createAudioDeviceModule = [weakSelf, queue, disableAudioInput, audioDeviceModule, onMutedSpeechActivityDetected = _onMutedSpeechActivityDetected](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr { if (audioDeviceModule) { return audioDeviceModule->getSyncAssumingSameThread()->audioDeviceModule(); } else { rtc::Thread *audioDeviceModuleThread = rtc::Thread::Current(); auto resultModule = rtc::make_ref_counted(false, disableAudioInput, disableAudioInput ? 2 : 1); + if (resultModule) { + resultModule->mutedSpeechDetectionChanged = ^(bool value) { + if (onMutedSpeechActivityDetected) { + onMutedSpeechActivityDetected(value); + } + }; + } [queue dispatch:^{ __strong GroupCallThreadLocalContext *strongSelf = weakSelf; if (strongSelf) { @@ -1932,6 +1944,14 @@ audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice { }]; return resultModule; } + }, + .onMutedSpeechActivityDetected = [weakSelf, queue](bool value) { + [queue dispatch:^{ + __strong GroupCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf && strongSelf->_onMutedSpeechActivityDetected) { + strongSelf->_onMutedSpeechActivityDetected(value); + } + }]; } })); } diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 8721352f45..8344f8ca2a 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 8721352f452128adec41c254b8407a4cb18cbbeb +Subproject commit 8344f8ca2a043d0812743260c31a086a82190489