This commit is contained in:
Isaac 2025-10-27 00:22:14 +04:00
parent d3d1b0ee39
commit 84171b89f4
9 changed files with 251 additions and 117 deletions

View file

@ -2669,6 +2669,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
self.stateValue.messagesAreEnabled = state.messagesAreEnabled.isEnabled
self.stateValue.canEnableMessages = state.messagesAreEnabled.canChange
self.stateValue.sendPaidMessageStars = state.messagesAreEnabled.sendPaidMessagesStars
self.stateValue.recordingStartTimestamp = state.recordingStartTimestamp
self.stateValue.title = state.title
self.stateValue.scheduleTimestamp = state.scheduleTimestamp

View file

@ -371,7 +371,8 @@ public final class ConferenceCallE2EContext {
offset: "",
ssrcs: [],
limit: 100,
sortAscending: true
sortAscending: true,
isStream: false
)
|> map(Optional.init)
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in

View file

@ -507,7 +507,7 @@ public enum GetGroupCallParticipantsError {
case generic
}
func _internal_getGroupCallParticipants(account: Account, reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
func _internal_getGroupCallParticipants(account: Account, reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?, isStream: Bool) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
let accountPeerId = account.peerId
let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, GroupCallParticipantsContext.State.MessagesAreEnabled?, Bool, Int, Bool, Bool), GetGroupCallParticipantsError>
@ -525,8 +525,15 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro
return combineLatest(
account.network.request(Api.functions.phone.getGroupParticipants(call: reference.apiInputGroupCall, ids: [], sources: ssrcs.map { Int32(bitPattern: $0) }, offset: offset, limit: limit))
|> map(Optional.init)
|> mapError { _ -> GetGroupCallParticipantsError in
return .generic
}
|> `catch` { error -> Signal<Api.phone.GroupParticipants?, GetGroupCallParticipantsError> in
if isStream {
return .single(nil)
}
return .fail(error)
},
sortAscendingValue
)
@ -539,21 +546,27 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, messagesAreEnabled, isVideoEnabled, unmutedVideoLimit, isStream, isCreator) = sortAscendingAndScheduleTimestamp
switch result {
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
totalCount = Int(count)
version = apiVersion
if participants.count != 0 && !nextOffset.isEmpty {
nextParticipantsFetchOffset = nextOffset
} else {
nextParticipantsFetchOffset = nil
if let result {
switch result {
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
totalCount = Int(count)
version = apiVersion
if participants.count != 0 && !nextOffset.isEmpty {
nextParticipantsFetchOffset = nextOffset
} else {
nextParticipantsFetchOffset = nil
}
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
parsedParticipants = participants.compactMap { GroupCallParticipantsContext.Participant($0, transaction: transaction) }
}
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
parsedParticipants = participants.compactMap { GroupCallParticipantsContext.Participant($0, transaction: transaction) }
} else {
totalCount = 0
version = 0
nextParticipantsFetchOffset = nil
}
parsedParticipants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: sortAscendingValue) })
@ -645,7 +658,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
|> castError(InternalJoinError.self)
|> mapToSignal { e2eData -> Signal<JoinGroupCallResult, InternalJoinError> in
return account.postbox.transaction { transaction -> Api.InputPeer? in
if let joinAs = joinAs {
if let joinAs {
return transaction.getPeer(joinAs).flatMap(apiInputPeer)
} else {
return .inputPeerSelf
@ -653,7 +666,7 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
}
|> castError(InternalJoinError.self)
|> mapToSignal { inputJoinAs -> Signal<JoinGroupCallResult, InternalJoinError> in
guard let inputJoinAs = inputJoinAs else {
guard let inputJoinAs else {
return .fail(.error(.generic))
}
@ -719,38 +732,13 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
}
}
let getParticipantsRequest: Signal<GroupCallParticipantsContext.State, InternalJoinError>
if isStream {
getParticipantsRequest = .single(GroupCallParticipantsContext.State(
participants: [],
nextParticipantsFetchOffset: nil,
adminIds: Set(),
isCreator: false,
defaultParticipantsAreMuted: .init(isMuted: true, canChange: false),
messagesAreEnabled: .init(isEnabled: true, canChange: false, sendPaidMessagesStars: nil),
sortAscending: true,
recordingStartTimestamp: nil,
title: nil,
scheduleTimestamp: nil,
subscribedToScheduled: false,
totalCount: 0,
isVideoEnabled: false,
unmutedVideoLimit: 0,
isStream: true,
version: 0
))
} else {
getParticipantsRequest = _internal_getGroupCallParticipants(account: account, reference: reference, offset: "", ssrcs: [], limit: 100, sortAscending: true)
|> mapError { _ -> InternalJoinError in
return .error(.generic)
}
let getParticipantsRequest = _internal_getGroupCallParticipants(account: account, reference: reference, offset: "", ssrcs: [], limit: 100, sortAscending: true, isStream: isStream)
|> mapError { _ -> InternalJoinError in
return .error(.generic)
}
return combineLatest(
joinRequest,
getParticipantsRequest
)
|> mapToSignal { updates, participantsState -> Signal<JoinGroupCallResult, InternalJoinError> in
return joinRequest
|> mapToSignal { updates -> Signal<JoinGroupCallResult, InternalJoinError> in
let peer = account.postbox.transaction { transaction -> Peer? in
return peerId.flatMap(transaction.getPeer)
}
@ -758,9 +746,10 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?,
return combineLatest(
peerAdminIds |> castError(InternalJoinError.self) |> take(1),
peer
peer,
getParticipantsRequest
)
|> mapToSignal { peerAdminIds, peer -> Signal<JoinGroupCallResult, InternalJoinError> in
|> mapToSignal { peerAdminIds, peer, participantsState -> Signal<JoinGroupCallResult, InternalJoinError> in
var state = participantsState
if let peer {
if let channel = peer as? TelegramChannel {
@ -2149,7 +2138,7 @@ public final class GroupCallParticipantsContext {
Logger.shared.log("GroupCallParticipantsContext", "will request ssrcs=\(ssrcs)")
self.disposable.set((_internal_getGroupCallParticipants(account: self.account, reference: self.reference, offset: "", ssrcs: Array(ssrcs), limit: 100, sortAscending: true)
self.disposable.set((_internal_getGroupCallParticipants(account: self.account, reference: self.reference, offset: "", ssrcs: Array(ssrcs), limit: 100, sortAscending: true, isStream: false)
|> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else {
return
@ -2367,7 +2356,7 @@ public final class GroupCallParticipantsContext {
self.updateQueue.removeAll()
self.disposable.set((_internal_getGroupCallParticipants(account: self.account, reference: self.reference, offset: "", ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending)
self.disposable.set((_internal_getGroupCallParticipants(account: self.account, reference: self.reference, offset: "", ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending, isStream: false)
|> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else {
return
@ -2672,7 +2661,7 @@ public final class GroupCallParticipantsContext {
}
self.isLoadingMore = true
self.disposable.set((_internal_getGroupCallParticipants(account: self.account, reference: self.reference, offset: token, ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending)
self.disposable.set((_internal_getGroupCallParticipants(account: self.account, reference: self.reference, offset: token, ssrcs: [], limit: 100, sortAscending: self.stateValue.state.sortAscending, isStream: false)
|> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else {
return

View file

@ -55,7 +55,7 @@ public extension TelegramEngine {
}
public func getGroupCallParticipants(reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
return _internal_getGroupCallParticipants(account: self.account, reference: reference, offset: offset, ssrcs: ssrcs, limit: limit, sortAscending: sortAscending)
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<JoinGroupCallResult, JoinGroupCallError> {

View file

@ -788,6 +788,7 @@ private final class ChatSendStarsScreenComponent: Component {
private struct Amount: Equatable {
private let sliderSteps: [Int]
private let minRealValue: Int
private let maxRealValue: Int
let maxSliderValue: Int
private let isLogarithmic: Bool
@ -795,14 +796,16 @@ private final class ChatSendStarsScreenComponent: Component {
private(set) var realValue: Int
private(set) var sliderValue: Int
private static func makeSliderSteps(maxRealValue: Int, isLogarithmic: Bool) -> [Int] {
private static func makeSliderSteps(minRealValue: Int, maxRealValue: Int, isLogarithmic: Bool) -> [Int] {
if isLogarithmic {
var sliderSteps: [Int] = [ 1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 10_000 ]
var sliderSteps: [Int] = [1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 10_000 ]
sliderSteps.removeAll(where: { $0 <= minRealValue })
sliderSteps.insert(minRealValue, at: 0)
sliderSteps.removeAll(where: { $0 >= maxRealValue })
sliderSteps.append(maxRealValue)
return sliderSteps
} else {
return [1, maxRealValue]
return [minRealValue, maxRealValue]
}
}
@ -834,8 +837,9 @@ private final class ChatSendStarsScreenComponent: Component {
}
}
init(realValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) {
self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic)
init(realValue: Int, minRealValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) {
self.sliderSteps = Amount.makeSliderSteps(minRealValue: minRealValue, maxRealValue: maxRealValue, isLogarithmic: isLogarithmic)
self.minRealValue = minRealValue
self.maxRealValue = maxRealValue
self.maxSliderValue = maxSliderValue
self.isLogarithmic = isLogarithmic
@ -844,8 +848,9 @@ private final class ChatSendStarsScreenComponent: Component {
self.sliderValue = Amount.remapValueToSlider(realValue: self.realValue, maxSliderValue: self.maxSliderValue, steps: self.sliderSteps)
}
init(sliderValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) {
self.sliderSteps = Amount.makeSliderSteps(maxRealValue: maxRealValue, isLogarithmic: isLogarithmic)
init(sliderValue: Int, minRealValue: Int, maxRealValue: Int, maxSliderValue: Int, isLogarithmic: Bool) {
self.sliderSteps = Amount.makeSliderSteps(minRealValue: minRealValue, maxRealValue: maxRealValue, isLogarithmic: isLogarithmic)
self.minRealValue = minRealValue
self.maxRealValue = maxRealValue
self.maxSliderValue = maxSliderValue
self.isLogarithmic = isLogarithmic
@ -855,11 +860,11 @@ private final class ChatSendStarsScreenComponent: Component {
}
func withRealValue(_ realValue: Int) -> Amount {
return Amount(realValue: realValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic)
return Amount(realValue: realValue, minRealValue: self.minRealValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic)
}
func withSliderValue(_ sliderValue: Int) -> Amount {
return Amount(sliderValue: sliderValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic)
return Amount(sliderValue: sliderValue, minRealValue: self.minRealValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic)
}
}
@ -926,7 +931,7 @@ private final class ChatSendStarsScreenComponent: Component {
private var balance: StarsAmount?
private var amount: Amount = Amount(realValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true)
private var amount: Amount = Amount(realValue: 1, minRealValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true)
private var didChangeAmount: Bool = false
private var privacyPeer: PrivacyPeer = .account
@ -1423,7 +1428,7 @@ private final class ChatSendStarsScreenComponent: Component {
switch component.initialData.subjectInitialData {
case let .react(reactData):
self.currentMyPeer = reactData.myPeer
self.amount = Amount(realValue: 50, maxRealValue: reactData.maxAmount, maxSliderValue: 999, isLogarithmic: true)
self.amount = Amount(realValue: max(reactData.minAmount, 50), minRealValue: reactData.minAmount, maxRealValue: reactData.maxAmount, maxSliderValue: 999, isLogarithmic: true)
if let myTopPeer = reactData.myTopPeer {
if myTopPeer.isAnonymous {
@ -1460,7 +1465,9 @@ private final class ChatSendStarsScreenComponent: Component {
})
case let .liveStreamMessage(liveStreamMessageData):
self.currentMyPeer = nil
self.amount = Amount(realValue: 50, maxRealValue: liveStreamMessageData.maxAmount, maxSliderValue: 999, isLogarithmic: true)
let currentValue: Int = liveStreamMessageData.currentAmount ?? 50
self.amount = Amount(realValue: max(currentValue, liveStreamMessageData.minAmount), minRealValue: liveStreamMessageData.minAmount, maxRealValue: liveStreamMessageData.maxAmount, maxSliderValue: 999, isLogarithmic: true)
self.privacyPeer = .account
}
@ -2614,7 +2621,7 @@ private final class ChatSendStarsScreenComponent: Component {
public class ChatSendStarsScreen: ViewControllerComponentContainer {
public enum ReactSubject {
case message(EngineMessage.Id)
case liveStream(peerId: EnginePeer.Id, storyId: Int32)
case liveStream(peerId: EnginePeer.Id, storyId: Int32, minAmount: Int)
}
fileprivate enum SubjectInitialData {
@ -2627,10 +2634,11 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
let currentSentAmount: Int?
let topPeers: [ChatSendStarsScreen.TopPeer]
let myTopPeer: ChatSendStarsScreen.TopPeer?
let minAmount: Int
let maxAmount: Int
let completion: (Int64, TelegramPaidReactionPrivacy, Bool, ChatSendStarsScreen.TransitionOut) -> Void
init(peer: EnginePeer, myPeer: EnginePeer, defaultPrivacyPeer: ChatSendStarsScreenComponent.PrivacyPeer, channelsForPublicReaction: [EnginePeer], reactSubject: ReactSubject, currentSentAmount: Int?, topPeers: [ChatSendStarsScreen.TopPeer], myTopPeer: ChatSendStarsScreen.TopPeer?, maxAmount: Int, completion: @escaping (Int64, TelegramPaidReactionPrivacy, Bool, ChatSendStarsScreen.TransitionOut) -> Void) {
init(peer: EnginePeer, myPeer: EnginePeer, defaultPrivacyPeer: ChatSendStarsScreenComponent.PrivacyPeer, channelsForPublicReaction: [EnginePeer], reactSubject: ReactSubject, currentSentAmount: Int?, topPeers: [ChatSendStarsScreen.TopPeer], myTopPeer: ChatSendStarsScreen.TopPeer?, minAmount: Int, maxAmount: Int, completion: @escaping (Int64, TelegramPaidReactionPrivacy, Bool, ChatSendStarsScreen.TransitionOut) -> Void) {
self.peer = peer
self.myPeer = myPeer
self.defaultPrivacyPeer = defaultPrivacyPeer
@ -2639,6 +2647,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
self.currentSentAmount = currentSentAmount
self.topPeers = topPeers
self.myTopPeer = myTopPeer
self.minAmount = minAmount
self.maxAmount = maxAmount
self.completion = completion
}
@ -2647,20 +2656,26 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
final class LiveStreamMessage {
let peer: EnginePeer
let myPeer: EnginePeer
let minAmount: Int
let maxAmount: Int
let currentAmount: Int?
let text: NSAttributedString
let completion: (Int64, ChatSendStarsScreen.TransitionOut) -> Void
init(
peer: EnginePeer,
myPeer: EnginePeer,
minAmount: Int,
maxAmount: Int,
currentAmount: Int?,
text: NSAttributedString,
completion: @escaping (Int64, ChatSendStarsScreen.TransitionOut) -> Void
) {
self.peer = peer
self.myPeer = myPeer
self.minAmount = minAmount
self.maxAmount = maxAmount
self.currentAmount = currentAmount
self.text = text
self.completion = completion
}
@ -2838,6 +2853,11 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
maxAmount = Int(value)
}
var minAmount = 1
if case let .liveStream(_, _, minAmountValue) = reactSubject {
minAmount = minAmountValue
}
return combineLatest(
context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
@ -2911,6 +2931,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
count: Int(topPeer.count)
)
},
minAmount: minAmount,
maxAmount: maxAmount,
completion: completion
)),
@ -2923,6 +2944,8 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
context: AccountContext,
peerId: EnginePeer.Id,
text: NSAttributedString,
minAmount: Int,
currentAmount: Int?,
completion: @escaping (Int64, TransitionOut) -> Void
) -> Signal<InitialData?, NoError> {
let balance: Signal<StarsAmount?, NoError>
@ -2958,7 +2981,9 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
subjectInitialData: .liveStreamMessage(SubjectInitialData.LiveStreamMessage(
peer: peer,
myPeer: myPeer,
minAmount: minAmount,
maxAmount: maxAmount,
currentAmount: currentAmount,
text: text,
completion: completion
)),

View file

@ -421,6 +421,32 @@ final class StoryContentLiveChatComponent: Component {
}
return true
}
struct StarStats {
var myStars: Int64
var pendingMyStars: Int64
var totalStars: Int64
var topItems: [GroupCallMessagesContext.TopStarsItem]
init(myStars: Int64, pendingMyStars: Int64, totalStars: Int64, topItems: [GroupCallMessagesContext.TopStarsItem]) {
self.myStars = myStars
self.pendingMyStars = pendingMyStars
self.totalStars = totalStars
self.topItems = topItems
}
}
struct Info {
var starStats: StarStats?
var isChatEmpty: Bool
var isChatExpanded: Bool
init(starStats: StarStats?, isChatEmpty: Bool, isChatExpanded: Bool) {
self.starStats = starStats
self.isChatEmpty = isChatEmpty
self.isChatExpanded = isChatExpanded
}
}
final class View: UIView {
private let listContainer: UIView
@ -443,23 +469,26 @@ final class StoryContentLiveChatComponent: Component {
private var currentListIsEmpty: Bool = true
private var isMessageContextMenuOpen: Bool = false
public var isChatEmpty: Bool {
guard let messagesState = self.messagesState else {
return true
}
return messagesState.messages.isEmpty
}
private(set) var isChatExpanded: Bool = false
private var isChatExpanded: Bool = false
public var starStars: (myStars: Int64, pendingMyStars: Int64, totalStars: Int64, topItems: [GroupCallMessagesContext.TopStarsItem])? {
guard let messagesState = self.messagesState else {
return nil
public var currentInfo: Info {
var starStats: StoryContentLiveChatComponent.StarStats?
var isChatEmpty = true
if let messagesState = self.messagesState {
isChatEmpty = messagesState.messages.isEmpty
var myStars: Int64 = 0
if let item = messagesState.topStars.first(where: { $0.isMy }) {
myStars = item.amount
}
starStats = StoryContentLiveChatComponent.StarStats(myStars: myStars + messagesState.pendingMyStars, pendingMyStars: messagesState.pendingMyStars, totalStars: messagesState.totalStars + messagesState.pendingMyStars, topItems: messagesState.topStars)
}
var myStars: Int64 = 0
if let item = messagesState.topStars.first(where: { $0.isMy }) {
myStars = item.amount
}
return (myStars + messagesState.pendingMyStars, pendingMyStars: messagesState.pendingMyStars, messagesState.totalStars + messagesState.pendingMyStars, messagesState.topStars)
return Info(
starStats: starStats,
isChatEmpty: isChatEmpty,
isChatExpanded: self.isChatExpanded
)
}
override init(frame: CGRect) {
@ -643,10 +672,8 @@ final class StoryContentLiveChatComponent: Component {
let state = await (component.call.state |> take(1)).get()
var isAdmin = state.canManageCall
if component.storyPeerId == component.context.account.peerId {
isAdmin = true
}
var isAdmin = false
isAdmin = state.canManageCall
var canDelete = isAdmin
var isMyMessage = false
guard let messagesState = self.messagesState, let message = messagesState.messages.first(where: { $0.id == id }) else {

View file

@ -96,13 +96,43 @@ final class StoryItemContentComponent: Component {
return true
}
struct StarStats {
var myStars: Int64
var pendingMyStars: Int64
var totalStars: Int64
var topItems: [GroupCallMessagesContext.TopStarsItem]
init(myStars: Int64, pendingMyStars: Int64, totalStars: Int64, topItems: [GroupCallMessagesContext.TopStarsItem]) {
self.myStars = myStars
self.pendingMyStars = pendingMyStars
self.totalStars = totalStars
self.topItems = topItems
}
}
struct LiveChatState {
var isExpanded: Bool
var hasUnseenMessages: Bool
var areMessagesEnabled: Bool
var minMessagePrice: Int64?
var starStats: StarStats?
init(isExpanded: Bool, hasUnseenMessages: Bool) {
init(isExpanded: Bool, hasUnseenMessages: Bool, areMessagesEnabled: Bool, minMessagePrice: Int64?, starStats: StarStats?) {
self.isExpanded = isExpanded
self.hasUnseenMessages = hasUnseenMessages
self.areMessagesEnabled = areMessagesEnabled
self.minMessagePrice = minMessagePrice
self.starStats = starStats
}
}
private struct MediaStreamCallState: Equatable {
var areMessagesEnabled: Bool
var minMessagePrice: Int64?
init(areMessagesEnabled: Bool, minMessagePrice: Int64?) {
self.areMessagesEnabled = areMessagesEnabled
self.minMessagePrice = minMessagePrice
}
}
@ -111,7 +141,9 @@ final class StoryItemContentComponent: Component {
private let overlaysView: StoryItemOverlaysView
private var videoNode: UniversalVideoNode?
private(set) var mediaStreamCall: PresentationGroupCallImpl?
private var mediaStreamCallState: MediaStreamCallState?
private var liveCallStateDisposable: Disposable?
private var liveCallStatsDisposable: Disposable?
private var mediaStream: ComponentView<Empty>?
private var loadingEffectView: StoryItemLoadingEffectView?
private var loadingEffectAppearanceTimer: SwiftSignalKit.Timer?
@ -160,22 +192,28 @@ final class StoryItemContentComponent: Component {
guard let liveChatView = self.liveChat?.view as? StoryContentLiveChatComponent.View else {
return nil
}
if liveChatView.isChatEmpty {
return nil
let currentInfo = liveChatView.currentInfo
let mediaStreamCallState = self.mediaStreamCallState
let starStats = currentInfo.starStats.flatMap { starStats in
return StarStats(
myStars: starStats.myStars,
pendingMyStars: starStats.pendingMyStars,
totalStars: starStats.totalStars,
topItems: starStats.topItems
)
}
return LiveChatState(
isExpanded: liveChatView.isChatExpanded,
hasUnseenMessages: self.liveChatExternal.hasUnseenMessages
isExpanded: currentInfo.isChatExpanded,
hasUnseenMessages: self.liveChatExternal.hasUnseenMessages,
areMessagesEnabled: mediaStreamCallState?.areMessagesEnabled ?? false,
minMessagePrice: mediaStreamCallState?.minMessagePrice,
starStats: starStats
)
}
public var starStars: (myStars: Int64, pendingMyStars: Int64, totalStars: Int64, topItems: [GroupCallMessagesContext.TopStarsItem])? {
guard let liveChatView = self.liveChat?.view as? StoryContentLiveChatComponent.View else {
return nil
}
return liveChatView.starStars
}
public func toggleLiveChatExpanded() {
guard let liveChatView = self.liveChat?.view as? StoryContentLiveChatComponent.View else {
return
@ -227,6 +265,7 @@ final class StoryItemContentComponent: Component {
self.videoProgressDisposable?.dispose()
self.currentFetchPriority?.disposable.dispose()
self.liveCallStateDisposable?.dispose()
self.liveCallStatsDisposable?.dispose()
}
func allowsInstantPauseOnTouch(point: CGPoint) -> Bool {
@ -1015,7 +1054,27 @@ final class StoryItemContentComponent: Component {
if let mediaStreamCall = self.mediaStreamCall {
if self.liveCallStateDisposable == nil {
self.liveCallStateDisposable = (mediaStreamCall.members
self.liveCallStateDisposable = (mediaStreamCall.state
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
guard let self else {
return
}
let mappedState = MediaStreamCallState(
areMessagesEnabled: state.messagesAreEnabled,
minMessagePrice: state.sendPaidMessageStars
)
if self.mediaStreamCallState != mappedState {
self.mediaStreamCallState = mappedState
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
}
})
}
if self.liveCallStatsDisposable == nil {
self.liveCallStatsDisposable = (mediaStreamCall.members
|> deliverOnMainQueue).startStandalone(next: { [weak self] members in
guard let self, let environment = self.environment else {
return
@ -1033,9 +1092,15 @@ final class StoryItemContentComponent: Component {
}
})
}
} else if let liveCallStateDisposable = self.liveCallStateDisposable {
self.liveCallStateDisposable = nil
liveCallStateDisposable.dispose()
} else {
if let liveCallStateDisposable = self.liveCallStateDisposable {
self.liveCallStateDisposable = nil
liveCallStateDisposable.dispose()
}
if let liveCallStatsDisposable = self.liveCallStatsDisposable {
self.liveCallStatsDisposable = nil
liveCallStatsDisposable.dispose()
}
}
switch selectedMedia {

View file

@ -2971,18 +2971,28 @@ public final class StoryItemSetContainerComponent: Component {
var liveChatState: MessageInputPanelComponent.LiveChatState?
var starStats: MessageInputPanelComponent.StarStats?
var sendPaidMessageStars = isLiveStream ? self.sendMessageContext.currentLiveStreamMessageStars : component.slice.additionalPeerData.sendPaidMessageStars
if let visibleItemView = self.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View {
liveChatState = visibleItemView.liveChatState.flatMap { liveChatState in
return MessageInputPanelComponent.LiveChatState(
isExpanded: liveChatState.isExpanded,
hasUnseenMessages: liveChatState.hasUnseenMessages
)
}
starStats = visibleItemView.starStars.flatMap { starStats in
return MessageInputPanelComponent.StarStats(
myStars: starStats.myStars,
totalStars: starStats.totalStars
if let liveChatStateValue = visibleItemView.liveChatState {
liveChatState = MessageInputPanelComponent.LiveChatState(
isExpanded: liveChatStateValue.isExpanded,
hasUnseenMessages: liveChatStateValue.hasUnseenMessages
)
starStats = liveChatStateValue.starStats.flatMap { starStats in
return MessageInputPanelComponent.StarStats(
myStars: starStats.myStars,
totalStars: starStats.totalStars
)
}
if let minMessagePrice = liveChatStateValue.minMessagePrice {
if let current = sendPaidMessageStars {
if current < StarsAmount(value: minMessagePrice, nanos: 0) {
sendPaidMessageStars = StarsAmount(value: minMessagePrice, nanos: 0)
}
} else {
sendPaidMessageStars = StarsAmount(value: minMessagePrice, nanos: 0)
}
}
}
}
@ -2995,7 +3005,7 @@ public final class StoryItemSetContainerComponent: Component {
strings: component.strings,
style: .story,
placeholder: inputPlaceholder,
sendPaidMessageStars: isLiveStream ? self.sendMessageContext.currentLiveStreamMessageStars : component.slice.additionalPeerData.sendPaidMessageStars,
sendPaidMessageStars: sendPaidMessageStars,
maxLength: maxInputLength,
maxEmojiCount: maxEmojiCount,
queryTypes: [.mention, .hashtag, .emoji],

View file

@ -1335,10 +1335,21 @@ final class StoryItemSetContainerSendMessage {
inputText = text
}
guard let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View else {
return
}
var minAmount: Int64 = 1
if let minMessagePrice = visibleItemView.liveChatState?.minMessagePrice {
minAmount = minMessagePrice
}
let initialData = await ChatSendStarsScreen.initialDataLiveStreamMessage(
context: component.context,
peerId: peerId,
text: inputText,
minAmount: Int(minAmount),
currentAmount: (self.currentLiveStreamMessageStars?.value).flatMap { Int($0) },
completion: { [weak self, weak view] amount, _ in
guard let self, let view else {
return
@ -3893,8 +3904,9 @@ final class StoryItemSetContainerSendMessage {
}
var topPeers: [ReactionsMessageAttribute.TopPeer] = []
var minAmount: Int64 = 1
if let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View {
if let topItems = visibleItemView.starStars?.topItems {
if let topItems = visibleItemView.liveChatState?.starStats?.topItems {
topPeers = topItems.map { item -> ReactionsMessageAttribute.TopPeer in
return ReactionsMessageAttribute.TopPeer(
peerId: item.peerId,
@ -3905,12 +3917,16 @@ final class StoryItemSetContainerSendMessage {
)
}
}
if let minMessagePrice = visibleItemView.liveChatState?.minMessagePrice {
minAmount = minMessagePrice
}
}
let initialData = await ChatSendStarsScreen.initialData(
context: component.context,
peerId: peerId,
reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id),
reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id, minAmount: Int(minAmount)),
topPeers: topPeers,
completion: { [weak view] amount, privacy, isBecomingTop, transitionOut in
guard let view, let component = view.component else {
@ -4024,7 +4040,7 @@ final class StoryItemSetContainerSendMessage {
var totalStars = count
if let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View {
if let pendingMyStars = visibleItemView.starStars?.pendingMyStars {
if let pendingMyStars = visibleItemView.liveChatState?.starStats?.pendingMyStars {
totalStars += Int(pendingMyStars)
}
}