diff --git a/submodules/TabBarUI/Sources/TabBarContollerNode.swift b/submodules/TabBarUI/Sources/TabBarContollerNode.swift index a74d93e01f..abee862105 100644 --- a/submodules/TabBarUI/Sources/TabBarContollerNode.swift +++ b/submodules/TabBarUI/Sources/TabBarContollerNode.swift @@ -17,13 +17,16 @@ final class TabBarControllerNode: ASDisplayNode { private struct Params: Equatable { let layout: ContainerViewLayout let toolbar: Toolbar? + let isTabBarHidden: Bool init( layout: ContainerViewLayout, - toolbar: Toolbar? + toolbar: Toolbar?, + isTabBarHidden: Bool ) { self.layout = layout self.toolbar = toolbar + self.isTabBarHidden = isTabBarHidden } } @@ -160,7 +163,7 @@ final class TabBarControllerNode: ASDisplayNode { } func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) -> CGFloat { - let params = Params(layout: layout, toolbar: toolbar) + let params = Params(layout: layout, toolbar: toolbar, isTabBarHidden: self.tabBarHidden) if let layoutResult = self.layoutResult, layoutResult.params == params { return layoutResult.bottomInset } else { diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index afb1a55e29..1aeac9d9a7 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -950,7 +950,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { var sharedAudioContext = sharedAudioContext if sharedAudioContext == nil { - var useSharedAudio = true + var useSharedAudio = !isStream var canReuseCurrent = true if let data = self.accountContext.currentAppConfiguration.with({ $0 }).data { if data["ios_killswitch_group_shared_audio"] != nil { diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift index a7ada36fb3..c573aa26bf 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift @@ -178,6 +178,7 @@ final class CameraLiveStreamComponent: Component { hideUI: false, visibilityFraction: 1.0, isPanning: false, + isCentral: true, pinchState: nil, presentController: { c, a in // guard let self, let environment = self.environment else { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 353e4f9764..0647e7b5d2 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -1624,6 +1624,7 @@ private final class StoryContainerScreenComponent: Component { hideUI: (i == focusedIndex && (self.itemSetPanState?.didBegin == false || self.itemSetPinchState != nil)), visibilityFraction: 1.0 - abs(panFraction + cubeAdditionalRotationFraction), isPanning: self.itemSetPanState?.didBegin == true, + isCentral: i == focusedIndex, pinchState: self.itemSetPinchState, presentController: { [weak self] c, a in guard let self, let environment = self.environment else { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index 1b1bffb73c..c4ba0356a8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -44,7 +44,7 @@ public final class StoryContentItem: Equatable { } open class View: UIView { - open func setProgressMode(_ progressMode: ProgressMode) { + open func setProgressMode(mode: StoryContentItem.ProgressMode, isCentral: Bool) { } open func rewind() { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 64272cce1b..20b4d17370 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -183,7 +183,7 @@ final class StoryItemContentComponent: Component { private var unsupportedText: ComponentView? private var unsupportedButton: ComponentView? - private var progressMode: StoryContentItem.ProgressMode = .pause + private var progressMode: (mode: StoryContentItem.ProgressMode, isCentral: Bool) = (.pause, false) private var currentProgressTimer: SwiftSignalKit.Timer? private var currentProgressTimerValue: Double = 0.0 private var videoProgressDisposable: Disposable? @@ -302,7 +302,7 @@ final class StoryItemContentComponent: Component { if self.videoNode != nil { return } - if case .pause = self.progressMode { + if case .pause = self.progressMode.mode { return } @@ -357,7 +357,7 @@ final class StoryItemContentComponent: Component { } var shouldLoop = false - if self.progressMode == .blurred { + if self.progressMode.mode == .blurred { shouldLoop = true } else if let component = self.component, component.item.isPending { shouldLoop = true @@ -410,11 +410,62 @@ final class StoryItemContentComponent: Component { }) } } + + if case let .liveStream(liveStream) = currentMessageMedia { + let mediaStreamCall: PresentationGroupCallImpl + if let current = self.mediaStreamCall { + mediaStreamCall = current + } else { + let initialCall = EngineGroupCallDescription( + id: liveStream.call.id, + accessHash: liveStream.call.accessHash, + title: nil, + scheduleTimestamp: nil, + subscribedToScheduled: false, + isStream: true + ) + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) + mediaStreamCall = PresentationGroupCallImpl( + accountContext: component.context, + audioSession: component.context.sharedContext.mediaManager.audioSession, + callKitIntegration: nil, + getDeviceAccessData: { + ( + presentationData: presentationData, + present: { c, a in + + }, + openSettings: { + + } + ) + }, + initialCall: (initialCall, .id(id: liveStream.call.id, accessHash: liveStream.call.accessHash)), + internalId: CallSessionInternalId(), + peerId: nil, + isChannel: false, + invite: nil, + joinAsPeerId: nil, + isStream: true, + keyPair: nil, + conferenceSourceId: nil, + isConference: false, + beginWithVideo: false, + sharedAudioContext: nil, + unmuteByDefault: false + ) + self.mediaStreamCall = mediaStreamCall + + if update && !self.isUpdating { + self.state?.updated(transition: .immediate, isLocal: true) + } + } + } } - override func setProgressMode(_ progressMode: StoryContentItem.ProgressMode) { - if self.progressMode != progressMode { - self.progressMode = progressMode + override func setProgressMode(mode: StoryContentItem.ProgressMode, isCentral: Bool) { + if self.progressMode.mode != mode || self.progressMode.isCentral != isCentral { + self.progressMode = (mode, isCentral) self.updateProgressMode(update: true) if let component = self.component, !self.overlaysView.bounds.isEmpty { @@ -459,8 +510,10 @@ final class StoryItemContentComponent: Component { private func updateProgressMode(update: Bool) { if let videoNode = self.videoNode { - let canPlay = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy - + var canPlay = self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy + if case .pause = self.progressMode.mode { + canPlay = false + } if canPlay { videoNode.play() } else { @@ -468,13 +521,32 @@ final class StoryItemContentComponent: Component { } } + var shouldUpdate = false + if let mediaStreamCall = self.mediaStreamCall { + //print("call progressMode: \(self.progressMode)") + var canPlay = true + if case .pause = self.progressMode.mode, (!self.progressMode.isCentral || !self.hierarchyTrackingLayer.isInHierarchy) { + canPlay = false + } + if !canPlay { + self.mediaStreamCall = nil + shouldUpdate = true + + let _ = mediaStreamCall.leave(terminateIfPossible: false).startStandalone() + } + } + self.initializeVideoIfReady(update: update) self.updateVideoPlaybackProgress() self.updateProgressTimer() + + if shouldUpdate { + self.state?.updated(transition: .immediate, isLocal: true) + } } private func updateProgressTimer() { - var needsTimer = self.progressMode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy + var needsTimer = self.progressMode.mode != .pause && self.contentLoaded && self.hierarchyTrackingLayer.isInHierarchy if let component = self.component { if component.item.isPending { if case .file = self.currentMessageMedia { @@ -490,7 +562,7 @@ final class StoryItemContentComponent: Component { timeout: 1.0 / 60.0, repeat: true, completion: { [weak self] in - guard let self, self.progressMode != .pause, self.contentLoaded, self.hierarchyTrackingLayer.isInHierarchy else { + guard let self, self.progressMode.mode != .pause, self.contentLoaded, self.hierarchyTrackingLayer.isInHierarchy else { return } @@ -510,7 +582,7 @@ final class StoryItemContentComponent: Component { } } - if self.progressMode != .play { + if self.progressMode.mode != .play { return } @@ -681,7 +753,7 @@ final class StoryItemContentComponent: Component { size: size, isCaptureProtected: component.item.isForwardingDisabled, attemptSynchronous: synchronousLoad, - isActive: self.progressMode == .play, + isActive: self.progressMode.mode == .play, transition: transition ) } @@ -842,7 +914,7 @@ final class StoryItemContentComponent: Component { } } - if case let .liveStream(liveStream) = messageMedia { + if case let .liveStream(liveStream) = messageMedia, let mediaStreamCall = self.mediaStreamCall { var mediaStreamTransition = transition let mediaStream: ComponentView if let current = self.mediaStream { @@ -853,55 +925,6 @@ final class StoryItemContentComponent: Component { self.mediaStream = mediaStream } - let mediaStreamCall: PresentationGroupCallImpl - if let current = self.mediaStreamCall { - mediaStreamCall = current - } else { - let initialCall = EngineGroupCallDescription( - id: liveStream.call.id, - accessHash: liveStream.call.accessHash, - title: nil, - scheduleTimestamp: nil, - subscribedToScheduled: false, - isStream: true - ) - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) - mediaStreamCall = PresentationGroupCallImpl( - accountContext: component.context, - audioSession: component.context.sharedContext.mediaManager.audioSession, - callKitIntegration: nil, - getDeviceAccessData: { - ( - presentationData: presentationData, - present: { c, a in - - }, - openSettings: { - - } - ) - }, - initialCall: (initialCall, .id(id: liveStream.call.id, accessHash: liveStream.call.accessHash)), - internalId: CallSessionInternalId(), - peerId: nil, - isChannel: false, - invite: nil, - joinAsPeerId: nil, - isStream: true, - keyPair: nil, - conferenceSourceId: nil, - isConference: false, - beginWithVideo: false, - sharedAudioContext: nil, - unmuteByDefault: false - ) - self.mediaStreamCall = mediaStreamCall - - let _ = mediaStreamCall.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: mediaStreamCall.accountContext.account.peerId, isLiveStream: true, revokePreviousCredentials: false).startStandalone(next: { params in - print("url: \(params.url), streamKey: \(params.streamKey)") - }) - } - let liveChat: ComponentView if let current = self.liveChat { liveChat = current diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 2a3a7a26bf..b1537c23a7 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -115,6 +115,7 @@ public final class StoryItemSetContainerComponent: Component { public let hideUI: Bool public let visibilityFraction: CGFloat public let isPanning: Bool + public let isCentral: Bool public let pinchState: PinchState? public let presentController: (ViewController, Any?) -> Void public let presentInGlobalOverlay: (ViewController, Any?) -> Void @@ -155,6 +156,7 @@ public final class StoryItemSetContainerComponent: Component { hideUI: Bool, visibilityFraction: CGFloat, isPanning: Bool, + isCentral: Bool, pinchState: PinchState?, presentController: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, @@ -194,6 +196,7 @@ public final class StoryItemSetContainerComponent: Component { self.hideUI = hideUI self.visibilityFraction = visibilityFraction self.isPanning = isPanning + self.isCentral = isCentral self.pinchState = pinchState self.presentController = presentController self.presentInGlobalOverlay = presentInGlobalOverlay @@ -269,6 +272,9 @@ public final class StoryItemSetContainerComponent: Component { if lhs.isPanning != rhs.isPanning { return false } + if lhs.isCentral != rhs.isCentral { + return false + } if lhs.pinchState != rhs.pinchState { return false } @@ -1715,7 +1721,7 @@ public final class StoryItemSetContainerComponent: Component { itemProgressMode = .pause } - view.setProgressMode(itemProgressMode) + view.setProgressMode(mode: itemProgressMode, isCentral: index == centralIndex && component.isCentral) var isChannel = false var canShare = true @@ -1952,6 +1958,9 @@ public final class StoryItemSetContainerComponent: Component { } func updateIsProgressPaused() { + guard let component = self.component else { + return + } let progressMode = self.itemProgressMode() var centralId: StoryId? if let component = self.component { @@ -1965,7 +1974,7 @@ public final class StoryItemSetContainerComponent: Component { if id != centralId { itemMode = .pause } - view.setProgressMode(itemMode) + view.setProgressMode(mode: itemMode, isCentral: id == centralId && component.isCentral) } } } @@ -2865,7 +2874,7 @@ public final class StoryItemSetContainerComponent: Component { } let inputPlaceholder: MessageInputPanelComponent.Placeholder - if let stealthModeTimeout = component.stealthModeTimeout { + if let stealthModeTimeout = component.stealthModeTimeout, !isLiveStream { let minutes = Int(stealthModeTimeout / 60) let seconds = Int(stealthModeTimeout % 60) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index e43cac4869..99129d4098 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -56,7 +56,7 @@ import ChatSendAsContextMenu private var ObjCKey_DeinitWatcher: Int? -final class StoryItemSetContainerSendMessage { +final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { enum InputMode { case text case media @@ -4032,20 +4032,53 @@ final class StoryItemSetContainerSendMessage { } func performSendStars(view: StoryItemSetContainerComponent.View, buttonView: UIView?, count: Int, isFromExpandedView: Bool) { - guard let component = view.component else { - return - } - - if isFromExpandedView { - if let currentSendStarsUndoController = self.currentSendStarsUndoController { - self.currentSendStarsUndoController = nil - currentSendStarsUndoController.dismiss() + Task { @MainActor [weak self, weak view] in + guard let self, let view, let component = view.component else { + return } - self.commitSendStars(view: view, count: count, delay: false) - } else { - Task { @MainActor [weak view] in - guard let view, let component = view.component else { + guard let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View else { + return + } + + if isFromExpandedView { + if let currentSendStarsUndoController = self.currentSendStarsUndoController { + self.currentSendStarsUndoController = nil + currentSendStarsUndoController.dismiss() + } + + self.commitSendStars(view: view, count: count, delay: false) + } else { + let starsContextState = await component.context.starsContext?.state.get() + guard let balance = starsContextState?.balance else { + return + } + + var totalExpectedStars = count + if let pendingMyStars = visibleItemView.liveChatState?.starStats?.pendingMyStars, pendingMyStars > 0 { + totalExpectedStars += Int(pendingMyStars) + } + if Int64(totalExpectedStars) > balance.value { + guard let starsContext = component.context.starsContext, let navigationController = component.controller()?.navigationController as? NavigationController else { + return + } + guard let targetPeerId = component.slice.item.peerId else { + return + } + + let customTheme = component.theme + let options = await component.context.engine.payments.starsTopUpOptions().get() + let controller = component.context.sharedContext.makeStarsPurchaseScreen( + context: component.context, + starsContext: starsContext, + options: options, + purpose: .generic, + targetPeerId: targetPeerId, + customTheme: customTheme, + completion: { _ in } + ) + navigationController.pushViewController(controller) + return } @@ -4126,78 +4159,74 @@ final class StoryItemSetContainerSendMessage { } ) } - } - - guard let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View else { - return - } - - self.currentLiveStreamStarsIsActive = true - self.currentLiveStreamStarsIsActiveTimer?.invalidate() - self.currentLiveStreamStarsIsActiveTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self, weak view] _ in - guard let self, let view else { - return - } - self.currentLiveStreamStarsIsActive = false - view.state?.updated(transition: .spring(duration: 0.4)) - }) - - var totalStars = 0 - if let pendingMyStars = visibleItemView.liveChatState?.starStats?.pendingMyStars, pendingMyStars > 0 { - totalStars += count - totalStars += Int(pendingMyStars) - self.commitSendStars(view: view, count: count, delay: true) - } else { - let minAmount: Int64 = 1 - var count = count - count = max(Int(minAmount), count) - totalStars += count - self.commitSendStars(view: view, count: count, delay: true) - } - - let title: String - /*if case .anonymous = privacy { - title = self.presentationData.strings.Chat_ToastStarsSent_AnonymousTitle(Int32(self.currentSendStarsUndoCount)) - } else if case .peer = privacy, let privacyPeer { - let rawTitle = self.presentationData.strings.Chat_ToastStarsSent_TitleChannel(Int32(self.currentSendStarsUndoCount)) - title = rawTitle.replacingOccurrences(of: "{name}", with: privacyPeer.compactDisplayTitle) - } else*/ do { - title = component.strings.Chat_ToastStarsSent_Title(Int32(totalStars)) - } - - let textItems = AnimatedTextComponent.extractAnimatedTextString(string: component.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [ - 0: .number(totalStars, minDigits: 1), - 1: .text(component.strings.Chat_ToastStarsSent_TextStarAmount(Int32(totalStars))) - ]) - - if let current = self.currentSendStarsUndoController { - current.content = .starsSent(context: component.context, title: title, text: textItems, hasUndo: true) - } else { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) - let controller = UndoOverlayController(presentationData: presentationData, content: .starsSent(context: component.context, title: title, text: textItems, hasUndo: true), elevatedLayout: false, position: .top, action: { [weak view] action in - guard let view else { - return false + self.currentLiveStreamStarsIsActive = true + self.currentLiveStreamStarsIsActiveTimer?.invalidate() + self.currentLiveStreamStarsIsActiveTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self, weak view] _ in + guard let self, let view else { + return } - if case .undo = action { - guard let component = view.component else { - return false - } - guard case .liveStream = component.slice.item.storyItem.media else { - return false - } - guard let visibleItem = view.visibleItems[component.slice.item.id], let itemView = visibleItem.view.view as? StoryItemContentComponent.View else { - return false - } - guard let call = itemView.mediaStreamCall else { - return false - } - call.cancelSendStars() - } - return false + self.currentLiveStreamStarsIsActive = false + view.state?.updated(transition: .spring(duration: 0.4)) }) - self.currentSendStarsUndoController = controller - self.view?.component?.controller()?.present(controller, in: .current) + + var totalStars = 0 + if let pendingMyStars = visibleItemView.liveChatState?.starStats?.pendingMyStars, pendingMyStars > 0 { + totalStars += count + totalStars += Int(pendingMyStars) + self.commitSendStars(view: view, count: count, delay: true) + } else { + let minAmount: Int64 = 1 + var count = count + count = max(Int(minAmount), count) + totalStars += count + + self.commitSendStars(view: view, count: count, delay: true) + } + + let title: String + /*if case .anonymous = privacy { + title = self.presentationData.strings.Chat_ToastStarsSent_AnonymousTitle(Int32(self.currentSendStarsUndoCount)) + } else if case .peer = privacy, let privacyPeer { + let rawTitle = self.presentationData.strings.Chat_ToastStarsSent_TitleChannel(Int32(self.currentSendStarsUndoCount)) + title = rawTitle.replacingOccurrences(of: "{name}", with: privacyPeer.compactDisplayTitle) + } else*/ do { + title = component.strings.Chat_ToastStarsSent_Title(Int32(totalStars)) + } + + let textItems = AnimatedTextComponent.extractAnimatedTextString(string: component.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [ + 0: .number(totalStars, minDigits: 1), + 1: .text(component.strings.Chat_ToastStarsSent_TextStarAmount(Int32(totalStars))) + ]) + + if let current = self.currentSendStarsUndoController { + current.content = .starsSent(context: component.context, title: title, text: textItems, hasUndo: true) + } else { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) + let controller = UndoOverlayController(presentationData: presentationData, content: .starsSent(context: component.context, title: title, text: textItems, hasUndo: true), elevatedLayout: false, position: .top, action: { [weak view] action in + guard let view else { + return false + } + if case .undo = action { + guard let component = view.component else { + return false + } + guard case .liveStream = component.slice.item.storyItem.media else { + return false + } + guard let visibleItem = view.visibleItems[component.slice.item.id], let itemView = visibleItem.view.view as? StoryItemContentComponent.View else { + return false + } + guard let call = itemView.mediaStreamCall else { + return false + } + call.cancelSendStars() + } + return false + }) + self.currentSendStarsUndoController = controller + self.view?.component?.controller()?.present(controller, in: .current) + } } } }