From 4853623813905f4a557f65024c28fb0718be9b50 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 7 Nov 2025 22:54:56 +0800 Subject: [PATCH] Stories --- .../Sources/PresentationCall.swift | 2 ++ .../Sources/PresentationCallManager.swift | 3 ++ .../Sources/PresentationGroupCall.swift | 8 +++-- .../TelegramEngine/Calls/GroupCalls.swift | 7 ++-- .../Calls/TelegramEngineCalls.swift | 4 +-- .../Sources/MessageInputPanelComponent.swift | 33 ++++++++++--------- .../Sources/PeerNameTextComponent.swift | 30 +++++++++-------- .../StoryContentLiveChatComponent.swift | 27 ++++++++++----- .../Sources/StoryItemContentComponent.swift | 10 ++++++ .../StoryItemSetContainerComponent.swift | 3 ++ 10 files changed, 83 insertions(+), 44 deletions(-) diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index 53f6227d60..c3d892b2c1 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -895,6 +895,7 @@ public final class PresentationCallImpl: PresentationCall { invite: nil, joinAsPeerId: nil, isStream: false, + streamPeerId: nil, keyPair: keyPair, conferenceSourceId: self.internalId, isConference: true, @@ -1081,6 +1082,7 @@ public final class PresentationCallImpl: PresentationCall { invite: nil, joinAsPeerId: nil, isStream: false, + streamPeerId: nil, keyPair: keyPair, conferenceSourceId: self.internalId, isConference: true, diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 993e94adc0..2a4275f114 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -893,6 +893,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { invite: nil, joinAsPeerId: nil, isStream: false, + streamPeerId: nil, keyPair: nil, conferenceSourceId: nil, isConference: false, @@ -1116,6 +1117,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { invite: invite, joinAsPeerId: joinAsPeerId, isStream: initialCall.isStream ?? false, + streamPeerId: nil, keyPair: nil, conferenceSourceId: nil, isConference: false, @@ -1157,6 +1159,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { invite: nil, joinAsPeerId: nil, isStream: false, + streamPeerId: nil, keyPair: keyPair, conferenceSourceId: nil, isConference: true, diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 2409fe8d8d..d06ec6059f 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -807,6 +807,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private var screencastStateDisposable: Disposable? public let isStream: Bool + private let streamPeerId: EnginePeer.Id? private let sharedAudioContext: SharedCallAudioContext? public let isConference: Bool @@ -854,6 +855,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { invite: String?, joinAsPeerId: EnginePeer.Id?, isStream: Bool, + streamPeerId: EnginePeer.Id?, keyPair: TelegramKeyPair?, conferenceSourceId: CallSessionInternalId?, isConference: Bool, @@ -888,6 +890,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.hasVideo = false self.hasScreencast = false self.isStream = isStream + self.streamPeerId = streamPeerId self.conferenceSourceId = conferenceSourceId self.isConference = isConference self.beginWithVideo = beginWithVideo @@ -2051,6 +2054,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { callId: callInfo.id, reference: reference, isStream: self.isStream, + streamPeerId: self.streamPeerId, preferMuted: isEffectivelyMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, @@ -2306,7 +2310,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { let accountContext = self.accountContext let peerId = self.peerId let rawAdminIds: Signal, NoError> - if let peerId { + if let peerId = peerId ?? self.streamPeerId { if peerId.namespace == Namespaces.Peer.CloudChannel { rawAdminIds = Signal { subscriber in let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: accountContext.engine, postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in @@ -2346,7 +2350,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } let peer: Signal - if let peerId { + if let peerId = peerId ?? self.streamPeerId { peer = accountContext.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) } else { peer = .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index a6d7fbba03..03edb0649a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -644,7 +644,7 @@ public class JoinGroupCallE2E { } } -func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, isStream: Bool, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal { +func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, isStream: Bool, streamPeerId: PeerId?, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal { enum InternalJoinError { case error(JoinGroupCallError) case restart @@ -750,7 +750,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, return joinRequest |> mapToSignal { updates -> Signal in let peer = account.postbox.transaction { transaction -> Peer? in - return peerId.flatMap(transaction.getPeer) + return (peerId ?? streamPeerId).flatMap(transaction.getPeer) } |> castError(InternalJoinError.self) @@ -820,6 +820,9 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, state.sortAscending = parsedCall.sortAscending state.adminIds = Set(peerAdminIds) + if isStream, let channel = peer as? TelegramChannel, channel.hasPermission(.manageCalls) { + state.adminIds.insert(account.peerId) + } let connectionMode: JoinGroupCallResult.ConnectionMode if let clientParamsData = parsedClientParams.data(using: .utf8), let dict = (try? JSONSerialization.jsonObject(with: clientParamsData, options: [])) as? [String: Any] { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift index 380d126e4c..883ba9764a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift @@ -58,8 +58,8 @@ public extension TelegramEngine { return _internal_getGroupCallParticipants(account: self.account, reference: reference, offset: offset, ssrcs: ssrcs, limit: limit, sortAscending: sortAscending, isStream: false) } - public func joinGroupCall(peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, isStream: Bool, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal { - return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, reference: reference, isStream: isStream, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, generateE2E: generateE2E) + public func joinGroupCall(peerId: PeerId?, joinAs: PeerId?, callId: Int64, reference: InternalGroupCallReference, isStream: Bool, streamPeerId: PeerId?, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, generateE2E: ((Data?) -> JoinGroupCallE2E?)?) -> Signal { + return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, reference: reference, isStream: isStream, streamPeerId: streamPeerId, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, generateE2E: generateE2E) } public func joinGroupCallAsScreencast(callId: Int64, accessHash: Int64, joinPayload: String) -> Signal { diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 19848504e5..21e1c00fcd 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -945,25 +945,13 @@ public final class MessageInputPanelComponent: Component { var inlineActions: [ChatTextInputPanelComponent.InlineAction] = [] if component.liveChatState?.isEnabled == true { - if component.paidMessageAction != nil && self.textInputPanelExternalState.textInputState.inputText.length == 0 { - inlineActions.append(ChatTextInputPanelComponent.InlineAction( - kind: .paidMessage, - action: { [weak self] in - guard let self else { - return - } - self.component?.paidMessageAction?() - } - )) - } else if let inputMode { + if let inputMode { let mappedInputMode: ChatTextInputPanelComponent.InputMode switch inputMode { case .text: mappedInputMode = .text - case .emoji: + case .emoji, .stickers: mappedInputMode = .emoji - case .stickers: - mappedInputMode = .stickers } inlineActions.append(ChatTextInputPanelComponent.InlineAction( kind: .inputMode(mappedInputMode), @@ -975,6 +963,17 @@ public final class MessageInputPanelComponent: Component { } )) } + if component.paidMessageAction != nil && self.textInputPanelExternalState.textInputState.inputText.length == 0 { + inlineActions.append(ChatTextInputPanelComponent.InlineAction( + kind: .paidMessage, + action: { [weak self] in + guard let self else { + return + } + self.component?.paidMessageAction?() + } + )) + } } let placeholder: String @@ -1106,15 +1105,17 @@ public final class MessageInputPanelComponent: Component { containerSize: availableSize ) let inputPanelFrame = CGRect(origin: CGPoint(), size: inputPanelSize) - if let inputPanelView = inputPanel.view { + var inputPanelViewIsActive = false + if let inputPanelView = inputPanel.view as? ChatTextInputPanelComponent.View { if inputPanelView.superview == nil { inputPanel.parentState = state self.addSubview(inputPanelView) } transition.setFrame(view: inputPanelView, frame: inputPanelFrame) + inputPanelViewIsActive = inputPanelView.isActive } - component.externalState.isEditing = self.textInputPanelExternalState.isEditing + component.externalState.isEditing = self.textInputPanelExternalState.isEditing || inputPanelViewIsActive component.externalState.hasText = self.textInputPanelExternalState.textInputState.inputText.length != 0 component.externalState.isKeyboardHidden = component.hideKeyboard component.externalState.insertText = { [weak self] text in diff --git a/submodules/TelegramUI/Components/PeerNameTextComponent/Sources/PeerNameTextComponent.swift b/submodules/TelegramUI/Components/PeerNameTextComponent/Sources/PeerNameTextComponent.swift index 946251a9ac..c36fad578a 100644 --- a/submodules/TelegramUI/Components/PeerNameTextComponent/Sources/PeerNameTextComponent.swift +++ b/submodules/TelegramUI/Components/PeerNameTextComponent/Sources/PeerNameTextComponent.swift @@ -124,7 +124,18 @@ public final class PeerNameTextComponent: Component { containerSize: availableSize ) - var titleX: CGFloat = 0.0 + let titleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + titleView.layer.anchorPoint = CGPoint() + self.addSubview(titleView) + } + transition.setPosition(view: titleView, position: titleFrame.origin) + titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) + } + + var size = CGSize(width: titleFrame.maxX, height: titleSize.height) + if let iconContent { let icon: ComponentView if let current = self.icon { @@ -148,30 +159,21 @@ public final class PeerNameTextComponent: Component { environment: {}, containerSize: containerSize ) - let iconFrame = CGRect(origin: CGPoint(x: titleX, y: floorToScreenPixels((titleSize.height - iconSize.height) * 0.5)), size: iconSize) - titleX += iconSize.width + 4.0 + size.width += 3.0 + let iconFrame = CGRect(origin: CGPoint(x: size.width, y: floorToScreenPixels((titleSize.height - iconSize.height) * 0.5)), size: iconSize) if let iconView = icon.view { if iconView.superview == nil { self.addSubview(iconView) } transition.setFrame(view: iconView, frame: iconFrame) } + size.width += iconSize.width } else if let icon = self.icon { self.icon = nil icon.view?.removeFromSuperview() } - let titleFrame = CGRect(origin: CGPoint(x: titleX, y: 0.0), size: titleSize) - if let titleView = self.title.view { - if titleView.superview == nil { - titleView.layer.anchorPoint = CGPoint() - self.addSubview(titleView) - } - transition.setPosition(view: titleView, position: titleFrame.origin) - titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) - } - - return CGSize(width: titleFrame.maxX, height: titleSize.height) + return size } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift index fba88b394d..bb56037395 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift @@ -36,6 +36,7 @@ final class StoryContentLiveChatComponent: Component { let storyPeerId: EnginePeer.Id let insets: UIEdgeInsets let isEmbeddedInCamera: Bool + let minPaidStars: Int? let controller: () -> ViewController? init( @@ -47,6 +48,7 @@ final class StoryContentLiveChatComponent: Component { storyPeerId: EnginePeer.Id, insets: UIEdgeInsets, isEmbeddedInCamera: Bool, + minPaidStars: Int?, controller: @escaping () -> ViewController? ) { self.external = external @@ -57,6 +59,7 @@ final class StoryContentLiveChatComponent: Component { self.storyPeerId = storyPeerId self.insets = insets self.isEmbeddedInCamera = isEmbeddedInCamera + self.minPaidStars = minPaidStars self.controller = controller } @@ -85,6 +88,9 @@ final class StoryContentLiveChatComponent: Component { if lhs.isEmbeddedInCamera != rhs.isEmbeddedInCamera { return false } + if lhs.minPaidStars != rhs.minPaidStars { + return false + } return true } @@ -436,6 +442,12 @@ final class StoryContentLiveChatComponent: Component { isMyMessage = true canDelete = true } + var isMessageFromAdmin = false + if message.isFromAdmin { + isMessageFromAdmin = true + } else if message.author?.id == component.storyPeerId { + isMessageFromAdmin = true + } //TODO:localize if !isMyMessage, let author = message.author { @@ -467,13 +479,6 @@ final class StoryContentLiveChatComponent: Component { }))) } - #if DEBUG - if "".isEmpty { - isAdmin = true - canDelete = true - } - #endif - if canDelete { items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, _ in guard let self else { @@ -484,7 +489,7 @@ final class StoryContentLiveChatComponent: Component { guard let self else { return } - if isAdmin && !isMyMessage { + if isAdmin && !isMyMessage && !isMessageFromAdmin { self.displayDeleteMessageAndBan(id: id) } else { self.displayDeleteMessageConfirmation(id: id) @@ -616,6 +621,12 @@ final class StoryContentLiveChatComponent: Component { for message in messagesState.pinnedMessages.reversed() { if let author = message.author, let paidStars = message.paidStars { + if let minPaidStars = component.minPaidStars { + if Int(paidStars) < minPaidStars { + continue + } + } + if let current = topMessageByPeerId[author.id] { if let currentPaidStars = current.paidStars, currentPaidStars < paidStars { topMessageByPeerId[author.id] = message diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 5d6e89e962..19dda418c3 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -296,6 +296,9 @@ final class StoryItemContentComponent: Component { if let _ = self.overlaysView.hitTest(self.convert(self.convert(point, to: self.overlaysView), to: self.overlaysView), with: nil) { return false } + if self.liveChat != nil { + return false + } return true } @@ -452,6 +455,7 @@ final class StoryItemContentComponent: Component { invite: nil, joinAsPeerId: nil, isStream: !(component.isEmbeddedInCamera && liveStream.kind == .rtc), + streamPeerId: component.peer.id, keyPair: nil, conferenceSourceId: nil, isConference: false, @@ -938,6 +942,11 @@ final class StoryItemContentComponent: Component { self.liveChat = liveChat } + var minPaidStars: Int? + if let mediaStreamCallState = self.mediaStreamCallState { + minPaidStars = mediaStreamCallState.minMessagePrice.flatMap(Int.init) + } + let _ = liveChat.update( transition: mediaStreamTransition, component: AnyComponent(StoryContentLiveChatComponent( @@ -949,6 +958,7 @@ final class StoryItemContentComponent: Component { storyPeerId: component.peer.id, insets: environment.containerInsets, isEmbeddedInCamera: component.isEmbeddedInCamera, + minPaidStars: minPaidStars, controller: { [weak self] in guard let self, let component = self.component else { return nil diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 205ba69915..f57622000b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -949,6 +949,9 @@ public final class StoryItemSetContainerComponent: Component { self.sendMessageContext.currentInputMode = .text if hasFirstResponder(self) { view.deactivateInput() + if self.sendMessageContext.inputMediaNode != nil { + self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged(isFocused: false)))) + } } else { self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged(isFocused: false)))) }