This commit is contained in:
Isaac 2025-11-13 18:56:38 +08:00
parent e8fe2b0042
commit ab3d486026
4 changed files with 105 additions and 67 deletions

View File

@ -938,7 +938,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
messageLifetime = Int32(value) messageLifetime = Int32(value)
} }
if isStream { if streamPeerId != nil {
messageLifetime = Int32.max messageLifetime = Int32.max
} }
@ -948,7 +948,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
reference: .id(id: initialCall.description.id, accessHash: initialCall.description.accessHash), reference: .id(id: initialCall.description.id, accessHash: initialCall.description.accessHash),
e2eContext: self.e2eContext, e2eContext: self.e2eContext,
messageLifetime: messageLifetime, messageLifetime: messageLifetime,
isLiveStream: isStream isLiveStream: streamPeerId != nil
) )
self.messagesStatePromise.set(self.messagesContext!.state) self.messagesStatePromise.set(self.messagesContext!.state)
} }

View File

@ -126,7 +126,7 @@ private final class BadgeComponent: Component {
} }
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.badgeView.frame.contains(point) { if self.isUserInteractionEnabled && self.badgeView.frame.contains(point) {
return self return self
} else { } else {
return nil return nil
@ -1401,60 +1401,72 @@ private final class ChatSendStarsScreenComponent: Component {
} }
let sideInset: CGFloat = floor((availableSize.width - fillingSize) * 0.5) + 16.0 let sideInset: CGFloat = floor((availableSize.width - fillingSize) * 0.5) + 16.0
let context = component.context var isOnlyDisplay = false
let balanceSize = self.balanceOverlay.update( switch component.initialData.subjectInitialData {
transition: .immediate, case let .react(reactData):
component: AnyComponent( if case let .liveStream(_, _, _, _, _, isOnlyDisplayValue) = reactData.reactSubject {
StarsBalanceOverlayComponent( isOnlyDisplay = isOnlyDisplayValue
context: component.context, }
peerId: component.context.account.peerId, case .liveStreamMessage:
theme: environment.theme, break
currency: .stars, }
action: { [weak self] in
guard let self, let component = self.component, let starsContext = context.starsContext, let navigationController = self.environment?.controller()?.navigationController as? NavigationController else { let context = component.context
return if !isOnlyDisplay {
} let balanceSize = self.balanceOverlay.update(
self.environment?.controller()?.dismiss() transition: .immediate,
component: AnyComponent(
let targetPeerId: EnginePeer.Id StarsBalanceOverlayComponent(
switch component.initialData.subjectInitialData { context: component.context,
case let .react(reactData): peerId: component.context.account.peerId,
targetPeerId = reactData.peer.id theme: environment.theme,
case let .liveStreamMessage(liveStreamMessageData): currency: .stars,
targetPeerId = liveStreamMessageData.peer.id action: { [weak self] in
} guard let self, let component = self.component, let starsContext = context.starsContext, let navigationController = self.environment?.controller()?.navigationController as? NavigationController else {
return
let customTheme = environment.theme }
self.environment?.controller()?.dismiss()
let _ = (context.engine.payments.starsTopUpOptions()
|> take(1) let targetPeerId: EnginePeer.Id
|> deliverOnMainQueue).startStandalone(next: { options in switch component.initialData.subjectInitialData {
let controller = context.sharedContext.makeStarsPurchaseScreen( case let .react(reactData):
context: context, targetPeerId = reactData.peer.id
starsContext: starsContext, case let .liveStreamMessage(liveStreamMessageData):
options: options, targetPeerId = liveStreamMessageData.peer.id
purpose: .generic, }
targetPeerId: targetPeerId,
customTheme: customTheme, let customTheme = environment.theme
completion: { _ in }
) let _ = (context.engine.payments.starsTopUpOptions()
navigationController.pushViewController(controller) |> take(1)
}) |> deliverOnMainQueue).startStandalone(next: { options in
} let controller = context.sharedContext.makeStarsPurchaseScreen(
) context: context,
), starsContext: starsContext,
environment: {}, options: options,
containerSize: availableSize purpose: .generic,
) targetPeerId: targetPeerId,
if let view = self.balanceOverlay.view { customTheme: customTheme,
if view.superview == nil { completion: { _ in }
self.addSubview(view) )
navigationController.pushViewController(controller)
view.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) })
view.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil) }
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) )
),
environment: {},
containerSize: availableSize
)
if let view = self.balanceOverlay.view {
if view.superview == nil {
self.addSubview(view)
view.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
view.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - balanceSize.width) / 2.0), y: environment.statusBarHeight + 5.0), size: balanceSize)
} }
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - balanceSize.width) / 2.0), y: environment.statusBarHeight + 5.0), size: balanceSize)
} }
if self.component == nil { if self.component == nil {
@ -1500,7 +1512,7 @@ private final class ChatSendStarsScreenComponent: Component {
} }
} }
}) })
case let .liveStream(_, _, _, _, availableSendAsPeers): case let .liveStream(_, _, _, _, availableSendAsPeers, _):
self.channelsForPublicReaction = availableSendAsPeers.filter({ $0.id != reactData.myPeer.id }) self.channelsForPublicReaction = availableSendAsPeers.filter({ $0.id != reactData.myPeer.id })
} }
case let .liveStreamMessage(liveStreamMessageData): case let .liveStreamMessage(liveStreamMessageData):
@ -1653,7 +1665,7 @@ private final class ChatSendStarsScreenComponent: Component {
self.isPastTopCutoff = nil self.isPastTopCutoff = nil
} }
if case let .liveStream(_, _, _, liveChatMessageParams, _) = reactData.reactSubject { if case let .liveStream(_, _, _, liveChatMessageParams, _, _) = reactData.reactSubject {
let color = GroupCallMessagesContext.getStarAmountParamMapping(params: liveChatMessageParams, value: Int64(self.amount.realValue)).color ?? GroupCallMessagesContext.Message.Color(rawValue: 0x985FDC) let color = GroupCallMessagesContext.getStarAmountParamMapping(params: liveChatMessageParams, value: Int64(self.amount.realValue)).color ?? GroupCallMessagesContext.Message.Color(rawValue: 0x985FDC)
sliderColor = StoryLiveChatMessageComponent.getMessageColor(color: color) sliderColor = StoryLiveChatMessageComponent.getMessageColor(color: color)
} }
@ -1680,6 +1692,10 @@ private final class ChatSendStarsScreenComponent: Component {
self.scrollContentView.addSubview(self.badgeStars) self.scrollContentView.addSubview(self.badgeStars)
self.scrollContentView.addSubview(sliderBackgroundView) self.scrollContentView.addSubview(sliderBackgroundView)
self.scrollContentView.addSubview(sliderView) self.scrollContentView.addSubview(sliderView)
if isOnlyDisplay {
sliderView.isUserInteractionEnabled = false
}
} }
transition.setFrame(view: sliderView, frame: sliderFrame) transition.setFrame(view: sliderView, frame: sliderFrame)
@ -1703,6 +1719,9 @@ private final class ChatSendStarsScreenComponent: Component {
var badgeFrame = CGRect(origin: CGPoint(x: sliderForegroundFrame.minX + sliderForegroundFrame.width - floorToScreenPixels(sliderMinWidth * 0.5), y: sliderForegroundFrame.minY - 8.0), size: badgeSize) var badgeFrame = CGRect(origin: CGPoint(x: sliderForegroundFrame.minX + sliderForegroundFrame.width - floorToScreenPixels(sliderMinWidth * 0.5), y: sliderForegroundFrame.minY - 8.0), size: badgeSize)
if let badgeView = self.badge.view as? BadgeComponent.View { if let badgeView = self.badge.view as? BadgeComponent.View {
if badgeView.superview == nil { if badgeView.superview == nil {
if isOnlyDisplay {
badgeView.isUserInteractionEnabled = false
}
self.scrollContentView.insertSubview(badgeView, belowSubview: self.badgeStars) self.scrollContentView.insertSubview(badgeView, belowSubview: self.badgeStars)
} }
@ -2238,7 +2257,7 @@ private final class ChatSendStarsScreenComponent: Component {
var peerColor: UIColor = UIColor(rgb: 0xFFB10D) var peerColor: UIColor = UIColor(rgb: 0xFFB10D)
var topPlace: Int? var topPlace: Int?
if case let .liveStream(_, _, _, liveChatMessageParams, _) = reactData.reactSubject { if case let .liveStream(_, _, _, liveChatMessageParams, _, _) = reactData.reactSubject {
let color = GroupCallMessagesContext.getStarAmountParamMapping(params: liveChatMessageParams, value: Int64(topPeer.count)).color ?? GroupCallMessagesContext.Message.Color(rawValue: 0x985FDC) let color = GroupCallMessagesContext.getStarAmountParamMapping(params: liveChatMessageParams, value: Int64(topPeer.count)).color ?? GroupCallMessagesContext.Message.Color(rawValue: 0x985FDC)
peerColor = StoryLiveChatMessageComponent.getMessageColor(color: color) peerColor = StoryLiveChatMessageComponent.getMessageColor(color: color)
topPlace = validIds.count - 1 topPlace = validIds.count - 1
@ -2488,7 +2507,12 @@ private final class ChatSendStarsScreenComponent: Component {
let buttonString: String let buttonString: String
switch component.initialData.subjectInitialData { switch component.initialData.subjectInitialData {
case .react: case .react:
buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount.realValue)").string if isOnlyDisplay {
//TODO:localize
buttonString = "Close"
} else {
buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount.realValue)").string
}
case .liveStreamMessage: case .liveStreamMessage:
//TODO:localize //TODO:localize
buttonString = "Add # \(self.amount.realValue)" buttonString = "Add # \(self.amount.realValue)"
@ -2522,6 +2546,19 @@ private final class ChatSendStarsScreenComponent: Component {
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }
switch component.initialData.subjectInitialData {
case let .react(reactData):
if case let .liveStream(_, _, _, _, _, isOnlyDisplay) = reactData.reactSubject {
if isOnlyDisplay {
self.environment?.controller()?.dismiss()
return
}
}
case .liveStreamMessage:
break
}
guard let balance = self.balance else { guard let balance = self.balance else {
return return
} }
@ -2604,10 +2641,11 @@ private final class ChatSendStarsScreenComponent: Component {
var buttonDescriptionTextSize: CGSize? var buttonDescriptionTextSize: CGSize?
if case .react = component.initialData.subjectInitialData { if case .react = component.initialData.subjectInitialData {
//TODO:localize
buttonDescriptionTextSize = self.buttonDescriptionText.update( buttonDescriptionTextSize = self.buttonDescriptionText.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
text: .markdown(text: environment.strings.SendStarReactions_TermsOfServiceFooter, attributes: MarkdownAttributes( text: .markdown(text: isOnlyDisplay ? "You can't send star reactions to own story." : environment.strings.SendStarReactions_TermsOfServiceFooter, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor), body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemSecondaryTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemSecondaryTextColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor),
@ -2716,7 +2754,7 @@ private final class ChatSendStarsScreenComponent: Component {
public class ChatSendStarsScreen: ViewControllerComponentContainer { public class ChatSendStarsScreen: ViewControllerComponentContainer {
public enum ReactSubject { public enum ReactSubject {
case message(EngineMessage.Id) case message(EngineMessage.Id)
case liveStream(peerId: EnginePeer.Id, storyId: Int32, minAmount: Int, liveChatMessageParams: LiveChatMessageParams, availableSendAsPeers: [EnginePeer]) case liveStream(peerId: EnginePeer.Id, storyId: Int32, minAmount: Int, liveChatMessageParams: LiveChatMessageParams, availableSendAsPeers: [EnginePeer], isDisplayOnly: Bool)
} }
fileprivate enum SubjectInitialData { fileprivate enum SubjectInitialData {
@ -2925,7 +2963,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
switch reactSubject { switch reactSubject {
case .message: case .message:
channelsForPublicReaction = context.engine.peers.channelsForPublicReaction(useLocalCache: true) channelsForPublicReaction = context.engine.peers.channelsForPublicReaction(useLocalCache: true)
case let .liveStream(_, _, _, _, availableSendAsPeers): case let .liveStream(_, _, _, _, availableSendAsPeers, _):
channelsForPublicReaction = .single(availableSendAsPeers) channelsForPublicReaction = .single(availableSendAsPeers)
} }
@ -2958,7 +2996,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
} }
var minAmount = 1 var minAmount = 1
if case let .liveStream(_, _, minAmountValue, _, _) = reactSubject { if case let .liveStream(_, _, minAmountValue, _, _, _) = reactSubject {
minAmount = minAmountValue minAmount = minAmountValue
} }

View File

@ -3306,10 +3306,10 @@ public final class StoryItemSetContainerComponent: Component {
} }
}, },
sendStarsAction: (isLiveStream && canSendStars) ? { [weak self] sourceView, isLongPress in sendStarsAction: (isLiveStream && canSendStars) ? { [weak self] sourceView, isLongPress in
guard let self else { guard let self, let component = self.component else {
return return
} }
if isLongPress { if isLongPress || component.isEmbeddedInCamera {
self.sendMessageContext.openSendStars(view: self) self.sendMessageContext.openSendStars(view: self)
} else { } else {
self.sendMessageContext.performSendStars(view: self, buttonView: sourceView, count: 1, isFromExpandedView: false) self.sendMessageContext.performSendStars(view: self, buttonView: sourceView, count: 1, isFromExpandedView: false)

View File

@ -4041,7 +4041,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) {
context: component.context, context: component.context,
peerId: peerId, peerId: peerId,
myPeer: (sendAsPeer?.peer).flatMap(EnginePeer.init), myPeer: (sendAsPeer?.peer).flatMap(EnginePeer.init),
reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id, minAmount: Int(minAmount), liveChatMessageParams: LiveChatMessageParams(appConfig: component.context.currentAppConfiguration.with({ $0 })), availableSendAsPeers: self.sendAsData?.availablePeers.map({ EnginePeer($0.peer) }) ?? []), reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id, minAmount: Int(minAmount), liveChatMessageParams: LiveChatMessageParams(appConfig: component.context.currentAppConfiguration.with({ $0 })), availableSendAsPeers: component.isEmbeddedInCamera ? [] : (self.sendAsData?.availablePeers.map({ EnginePeer($0.peer) }) ?? []), isDisplayOnly: component.isEmbeddedInCamera),
topPeers: topPeers, topPeers: topPeers,
completion: { [weak self, weak view] amount, privacy, _, _ in completion: { [weak self, weak view] amount, privacy, _, _ in
guard let self, let view else { guard let self, let view else {