Merge commit '7b5f9a89b2d88fc75f7e7941c9114624574f40a3'

This commit is contained in:
Isaac 2025-11-07 22:55:15 +08:00
commit 64ab80da02
10 changed files with 94 additions and 49 deletions

View File

@ -973,7 +973,7 @@ public protocol MediaEditorScreenResult {
public protocol TelegramRootControllerInterface: NavigationController {
@discardableResult
func openStoryCamera(customTarget: Stories.PendingTarget?, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
func openStoryCamera(customTarget: Stories.PendingTarget?, resumeLiveStream: Bool, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
func proceedWithStoryUpload(target: Stories.PendingTarget, results: [MediaEditorScreenResult], existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void)
func getContactsController() -> ViewController?

View File

@ -2909,6 +2909,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
var premiumNeeded = false
var hasActiveCall = false
var hasActiveGroupCall = false
var hasLiveStream = false
if let componentView = self.chatListHeaderView(), let storyPeerListView = componentView.storyPeerListView(), storyPeerListView.isLiveStreaming {
hasLiveStream = true
}
let storiesCountLimit = self.context.userLimits.maxExpiringStoriesCount
var storiesCount = 0
@ -2938,7 +2943,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
if reachedCountLimit {
if !hasLiveStream && reachedCountLimit {
let context = self.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .expiringStories, count: Int32(storiesCount), action: {
@ -2955,7 +2960,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
if premiumNeeded || hasActiveCall || hasActiveGroupCall {
if !hasLiveStream && (premiumNeeded || hasActiveCall || hasActiveGroupCall) {
if let storyCameraTooltip = self.storyCameraTooltip {
self.storyCameraTooltip = nil
storyCameraTooltip.dismiss()
@ -3042,7 +3047,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
let coordinator = rootController.openStoryCamera(customTarget: nil, transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
let coordinator = rootController.openStoryCamera(customTarget: nil, resumeLiveStream: hasLiveStream, transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
coordinator?.animateIn()
}
}
@ -6420,7 +6425,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if let current = self.storyCameraTransitionInCoordinator {
coordinator = current
} else {
coordinator = rootController.openStoryCamera(customTarget: nil, transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] target, _ in
coordinator = rootController.openStoryCamera(customTarget: nil, resumeLiveStream: false, transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] target, _ in
guard let self, let target else {
return nil
}

View File

@ -155,6 +155,19 @@ final class CameraLiveStreamComponent: Component {
self.storyContentState = state
self.state?.updated()
})
self.inputMediaNodeDataPromise.set(
ChatEntityKeyboardInputNode.inputData(
context: component.context,
chatPeerId: nil,
areCustomEmojiEnabled: true,
hasTrending: true,
hasSearch: true,
hideBackground: true,
maskEdge: .clip,
sendGif: nil
)
)
}
if let storyContentState = self.storyContentState, let slice = storyContentState.slice {

View File

@ -374,6 +374,11 @@ private final class CameraScreenComponent: CombinedComponent {
}
if let controller = getController() {
if controller.resumeLiveStream {
controller.updateCameraState({ $0.updatedMode(.live).updatedIsWaitingForStream(true) }, update: false, transition: .immediate)
self.startLiveStream(rtmp: false)
}
if let customTarget = controller.customTarget {
self.sendAsPeerId = customTarget
self.isCustomTarget = true
@ -1080,6 +1085,7 @@ private final class CameraScreenComponent: CombinedComponent {
})
}
private var storyListContext: PeerExpiringStoryListContext?
func startLiveStream(rtmp: Bool) {
guard let controller = self.getController() else {
return
@ -1117,41 +1123,36 @@ private final class CameraScreenComponent: CombinedComponent {
})
}
// let _ = (self.context.engine.messages.storySubscriptions(isHidden: false)
// |> take(1)
// |> deliverOnMainQueue).start(next: { [weak self, weak controller] subscriptions in
// guard let self else {
// return
// }
// if subscriptions.accountItem?.hasLiveItems == true {
// let storyList = PeerExpiringStoryListContext(account: self.context.account, peerId: peerId)
// let _ = (storyList.state
// |> filter { !$0.isLoading }
// |> take(1)
// |> deliverOnMainQueue).start(next: { [weak self, weak controller] state in
// guard let self else {
// return
// }
// for item in state.items.reversed() {
// let _ = (self.context.engine.messages.getStory(peerId: peerId, id: item.id)
// |> deliverOnMainQueue).start(next: { [weak self, weak controller] item in
// guard let self, let item else {
// return
// }
// if case .liveStream = item.media {
// self.liveStreamStory = item
// controller?.updateCameraState({ $0.updatedIsStreaming(true) }, transition: .spring(duration: 0.4))
// self.updated(transition: .immediate)
// return
// }
// })
// }
// startNewStream()
// })
// } else {
let _ = (self.context.engine.messages.storySubscriptions(isHidden: false)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self, weak controller] subscriptions in
guard let self else {
return
}
if subscriptions.accountItem?.hasLiveItems == true {
let storyList = PeerExpiringStoryListContext(account: self.context.account, peerId: peerId)
self.storyListContext = storyList
let _ = (storyList.state
|> filter { !$0.isLoading }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self, weak controller] state in
guard let self else {
return
}
for item in state.items.reversed() {
if case let .item(item) = item, case .liveStream = item.media {
self.liveStreamStory = item
controller?.updateCameraState({ $0.updatedIsStreaming(true) }, transition: .spring(duration: 0.4))
self.updated(transition: .immediate)
return
}
}
startNewLiveStream()
})
} else {
startNewLiveStream()
// }
// })
}
})
}
func endLiveStream() {
@ -2815,7 +2816,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
return false
}
}
if let code = filteredCodes.first, !self.cameraState.isCollageEnabled && self.cameraState.recording == CameraState.Recording.none {
if let code = filteredCodes.first, !self.cameraState.isCollageEnabled && self.cameraState.recording == CameraState.Recording.none && self.cameraState.mode != .live {
self.controller?.updateFocusedCode(code)
} else {
self.controller?.updateFocusedCode(nil)
@ -3915,6 +3916,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
private let context: AccountContext
fileprivate let mode: Mode
fileprivate let customTarget: EnginePeer.Id?
fileprivate let resumeLiveStream: Bool
fileprivate let holder: CameraHolder?
fileprivate let transitionIn: TransitionIn?
fileprivate let transitionOut: (Bool) -> TransitionOut?
@ -3972,15 +3974,18 @@ public class CameraScreenImpl: ViewController, CameraScreen {
public var isEmbedded = false
fileprivate func updateCameraState(_ f: (CameraState) -> CameraState, transition: ComponentTransition) {
fileprivate func updateCameraState(_ f: (CameraState) -> CameraState, update: Bool = true, transition: ComponentTransition) {
self.node.cameraState = f(self.node.cameraState)
self.node.requestUpdateLayout(transition: transition)
if update {
self.node.requestUpdateLayout(transition: transition)
}
}
public init(
context: AccountContext,
mode: Mode,
customTarget: EnginePeer.Id? = nil,
resumeLiveStream: Bool = false,
holder: CameraHolder? = nil,
transitionIn: TransitionIn?,
transitionOut: @escaping (Bool) -> TransitionOut?,
@ -3989,6 +3994,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
self.context = context
self.mode = mode
self.customTarget = customTarget
self.resumeLiveStream = resumeLiveStream
self.holder = holder
self.transitionIn = transitionIn
self.transitionOut = transitionOut
@ -4030,7 +4036,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
super.displayNodeDidLoad()
self.node.didAppear = { [weak self] in
guard let self else {
guard let self, !self.resumeLiveStream else {
return
}
self.postingAvailabilityDisposable = (self.postingAvailabilityPromise.get()

View File

@ -634,7 +634,7 @@ public final class GiftItemComponent: Component {
if case let .starGift(gift, _) = component.subject, gift.flags.contains(.isAuction) {
buttonColor = component.theme.overallDarkAppearance ? UIColor(rgb: 0xffc337) : UIColor(rgb: 0xd3720a)
//todo:localize
price = "Place a Bid"
price = "Join"
} else {
if priceValue.contains("#") {
buttonColor = component.theme.overallDarkAppearance ? UIColor(rgb: 0xffc337) : UIColor(rgb: 0xd3720a)

View File

@ -10646,7 +10646,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
let coordinator = rootController.openStoryCamera(customTarget: self.peerId == self.context.account.peerId ? nil : .peer(self.peerId), transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
let coordinator = rootController.openStoryCamera(customTarget: self.peerId == self.context.account.peerId ? nil : .peer(self.peerId), resumeLiveStream: false, transitionIn: cameraTransitionIn, transitionedIn: {}, transitionOut: self.storyCameraTransitionOut())
coordinator?.animateIn()
}
case .channelBoostRequired:

View File

@ -310,7 +310,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
return
}
if let rootController = component.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
let coordinator = rootController.openStoryCamera(customTarget: nil, transitionIn: nil, transitionedIn: {}, transitionOut: { _, _ in return nil })
let coordinator = rootController.openStoryCamera(customTarget: nil, resumeLiveStream: false, transitionIn: nil, transitionedIn: {}, transitionOut: { _, _ in return nil })
coordinator?.animateIn()
}
}

View File

@ -1147,13 +1147,21 @@ final class StoryItemContentComponent: Component {
if self.liveCallStatsDisposable == nil {
self.liveCallStatsDisposable = (mediaStreamCall.members
|> deliverOnMainQueue).startStandalone(next: { [weak self] members in
guard let self, let environment = self.environment else {
guard let self, let component = self.component, let environment = self.environment else {
return
}
//TODO:localize
let subtitle: String
if let members {
subtitle = "\(max(1, members.totalCount)) watching"
var totalCount = members.totalCount
if component.isEmbeddedInCamera {
totalCount -= 1
}
if totalCount == 0 && component.isEmbeddedInCamera {
subtitle = "no viewers"
} else {
subtitle = "\(max(1, totalCount)) watching"
}
} else {
subtitle = "loading..."
}

View File

@ -533,6 +533,18 @@ public final class StoryPeerListComponent: Component {
}
}
public var isLiveStreaming: Bool {
guard let component = self.component else {
return false
}
for itemSet in self.sortedItems {
if itemSet.peer.id == component.context.account.peerId, itemSet.hasLiveItems {
return true
}
}
return false
}
public func transitionViewForItem(peerId: EnginePeer.Id) -> (UIView, StoryContainerScreen.TransitionView)? {
if self.collapsedButton.isUserInteractionEnabled {
return nil

View File

@ -301,7 +301,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
}
@discardableResult
public func openStoryCamera(customTarget: Stories.PendingTarget?, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator? {
public func openStoryCamera(customTarget: Stories.PendingTarget?, resumeLiveStream: Bool, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator? {
guard let controller = self.viewControllers.last as? ViewController else {
return nil
}
@ -335,6 +335,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
context: context,
mode: .story,
customTarget: mediaEditorCustomTarget,
resumeLiveStream: resumeLiveStream,
transitionIn: transitionIn.flatMap {
if let sourceView = $0.sourceView {
return CameraScreenImpl.TransitionIn(