This commit is contained in:
Isaac 2025-11-04 15:32:19 +04:00
parent ff32f34405
commit 029654c14a
3 changed files with 123 additions and 26 deletions

View File

@ -1994,7 +1994,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
if let image = commentsButtonCenterIcon.image { if let image = commentsButtonCenterIcon.image {
commentsButtonCenterIcon.bounds = image.size.centered(in: iconFrame) commentsButtonCenterIcon.bounds = image.size.centered(in: iconFrame)
} }
transition.updateTransformRotation(view: commentsButtonCenterIcon, angle: !isExpanded ? (CGFloat.pi * 3.0 / 4.0) : 0.0) transition.updateTransformRotation(view: commentsButtonCenterIcon, angle: isExpanded ? (CGFloat.pi * 3.0 / 4.0) : 0.0)
commentsButtonIcon.contentsLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center commentsButtonIcon.contentsLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center
commentsButtonIcon.contentsLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) commentsButtonIcon.contentsLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)

View File

@ -1032,7 +1032,7 @@ public final class MessageInputPanelComponent: Component {
strings: component.strings, strings: component.strings,
chatPeerId: component.chatLocation?.peerId ?? component.context.account.peerId, chatPeerId: component.chatLocation?.peerId ?? component.context.account.peerId,
inlineActions: inlineActions, inlineActions: inlineActions,
leftAction: ChatTextInputPanelComponent.LeftAction(kind: .toggleExpanded(isVisible: component.liveChatState?.isEnabled == true, isExpanded: component.liveChatState?.isExpanded ?? true, hasUnseen: component.liveChatState?.hasUnseenMessages ?? false), action: { [weak self] in leftAction: ChatTextInputPanelComponent.LeftAction(kind: .toggleExpanded(isVisible: component.liveChatState?.isEnabled == true, isExpanded: component.liveChatState?.isExpanded ?? true && component.liveChatState?.isEmpty == false, hasUnseen: component.liveChatState?.hasUnseenMessages ?? false), action: { [weak self] in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }

View File

@ -88,6 +88,8 @@ public final class StoryLiveChatMessageComponent: Component {
private let contentContainer: UIView private let contentContainer: UIView
private var avatarNode: AvatarNode? private var avatarNode: AvatarNode?
private let textExternal = MultilineTextWithEntitiesComponent.External() private let textExternal = MultilineTextWithEntitiesComponent.External()
private let authorTitle = ComponentView<Empty>()
private var adminBadgeText: ComponentView<Empty>?
private let text = ComponentView<Empty>() private let text = ComponentView<Empty>()
private var crownIcon: UIImageView? private var crownIcon: UIImageView?
private var backgroundView: UIImageView? private var backgroundView: UIImageView?
@ -232,13 +234,11 @@ public final class StoryLiveChatMessageComponent: Component {
} }
let textString = NSMutableAttributedString() let textString = NSMutableAttributedString()
textString.append(NSAttributedString(string: component.message.author?.displayTitle(strings: component.strings, displayOrder: .firstLast) ?? " ", font: Font.semibold(15.0), textColor: secondaryTextColor))
if !component.message.text.isEmpty { if !component.message.text.isEmpty {
textString.append(NSAttributedString(string: " ", font: Font.semibold(15.0), textColor: secondaryTextColor))
textString.append(NSAttributedString(string: component.message.text, font: Font.regular(15.0), textColor: primaryTextColor)) textString.append(NSAttributedString(string: component.message.text, font: Font.regular(15.0), textColor: primaryTextColor))
} }
var textTopLeftCutout: CGFloat? var textTopLeftCutout: CGFloat = 0.0
if let topPlace = component.topPlace { if let topPlace = component.topPlace {
let crownIcon: UIImageView let crownIcon: UIImageView
if let current = self.crownIcon { if let current = self.crownIcon {
@ -254,7 +254,9 @@ public final class StoryLiveChatMessageComponent: Component {
crownIcon.tintColor = secondaryTextColor crownIcon.tintColor = secondaryTextColor
if let image = crownIcon.image { if let image = crownIcon.image {
textTopLeftCutout = image.size.width + 4.0 if !component.message.isFromAdmin {
textTopLeftCutout = image.size.width + 4.0
}
} }
} else { } else {
if let crownIcon = self.crownIcon { if let crownIcon = self.crownIcon {
@ -268,17 +270,58 @@ public final class StoryLiveChatMessageComponent: Component {
textBottomRightCutout = starsAmountTextSize.width + 20.0 textBottomRightCutout = starsAmountTextSize.width + 20.0
} }
var textCutout: TextNodeCutout?
if textBottomRightCutout != nil || textTopLeftCutout != nil {
textCutout = TextNodeCutout(topLeft: textTopLeftCutout.flatMap({ CGSize(width: $0, height: 8.0) }), bottomRight: textBottomRightCutout.flatMap({ CGSize(width: $0, height: 8.0) }))
}
var maxTextWidth: CGFloat = availableSize.width - insets.left - insets.right - avatarSize - avatarSpacing var maxTextWidth: CGFloat = availableSize.width - insets.left - insets.right - avatarSize - avatarSpacing
if let starsAmountTextSize, displayStarsAmountBackground { if let starsAmountTextSize, displayStarsAmountBackground {
var cutoutWidth: CGFloat = starsAmountTextSize.width + 20.0 var cutoutWidth: CGFloat = starsAmountTextSize.width + 20.0
cutoutWidth += 30.0 cutoutWidth += 30.0
maxTextWidth -= cutoutWidth maxTextWidth -= cutoutWidth
} }
let authorTitleSize = self.authorTitle.update(
transition: .immediate,
component: AnyComponent(MultilineTextWithEntitiesComponent(
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
placeholderColor: .gray,
text: .plain(NSAttributedString(string: component.message.author?.displayTitle(strings: component.strings, displayOrder: .firstLast) ?? " ", font: Font.semibold(15.0), textColor: secondaryTextColor)),
maximumNumberOfLines: 1,
lineSpacing: 0.1
)),
environment: {},
containerSize: CGSize(width: min(maxTextWidth - 80.0, 180.0), height: 100000.0)
)
if !component.message.isFromAdmin {
textTopLeftCutout += authorTitleSize.width + 6.0
}
var adminBadgeTextSize: CGSize?
if component.message.isFromAdmin && !displayStarsAmountBackground {
let adminBadgeText: ComponentView<Empty>
if let current = self.adminBadgeText {
adminBadgeText = current
} else {
adminBadgeText = ComponentView()
self.adminBadgeText = adminBadgeText
}
adminBadgeTextSize = adminBadgeText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.strings.ChatAdmins_AdminLabel, font: Font.regular(11.0), textColor: secondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
)
} else {
if let adminBadgeText = self.adminBadgeText {
self.adminBadgeText = nil
adminBadgeText.view?.removeFromSuperview()
}
}
let textCutout = TextNodeCutout(topLeft: CGSize(width: textTopLeftCutout, height: 8.0), bottomRight: textBottomRightCutout.flatMap({ CGSize(width: $0, height: 8.0) }))
let textSize = self.text.update( let textSize = self.text.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextWithEntitiesComponent( component: AnyComponent(MultilineTextWithEntitiesComponent(
@ -320,23 +363,48 @@ public final class StoryLiveChatMessageComponent: Component {
} else { } else {
avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
} }
if component.message.isFromAdmin {
avatarNode.setStoryStats(storyStats: AvatarNode.StoryStats(totalCount: 1, unseenCount: 1, hasUnseenCloseFriendsItems: false, hasLiveItems: true), presentationParams: AvatarNode.StoryPresentationParams(colors: AvatarNode.Colors(theme: component.theme), lineWidth: 1.0, inactiveLineWidth: 1.0), transition: .immediate)
} else {
avatarNode.setStoryStats(storyStats: nil, presentationParams: AvatarNode.StoryPresentationParams(colors: AvatarNode.Colors(theme: component.theme), lineWidth: 1.0, inactiveLineWidth: 1.0), transition: .immediate)
}
} else { } else {
avatarNode.setCustomLetters([" "]) avatarNode.setCustomLetters([" "])
avatarNode.setStoryStats(storyStats: nil, presentationParams: AvatarNode.StoryPresentationParams(colors: AvatarNode.Colors(theme: component.theme), lineWidth: 2.0, inactiveLineWidth: 2.0), transition: .immediate)
} }
} }
let textFrame = CGRect(origin: CGPoint(x: insets.left + avatarSize + avatarSpacing, y: avatarFrame.minY + 3.0), size: textSize) var authorTitleFrame = CGRect(origin: CGPoint(x: insets.left + avatarSize + avatarSpacing, y: avatarFrame.minY + 3.0), size: authorTitleSize)
if let image = self.crownIcon?.image {
authorTitleFrame.origin.x += image.size.width + 4.0
}
if let authorTitleView = self.authorTitle.view {
if authorTitleView.superview == nil {
authorTitleView.layer.anchorPoint = CGPoint()
self.extractedContainerNode.contentNode.view.addSubview(authorTitleView)
}
transition.setPosition(view: authorTitleView, position: authorTitleFrame.origin)
authorTitleView.bounds = CGRect(origin: CGPoint(), size: authorTitleFrame.size)
}
var textFrame = CGRect(origin: CGPoint(x: insets.left + avatarSize + avatarSpacing, y: avatarFrame.minY + 3.0), size: textSize)
if component.message.isFromAdmin {
textFrame.origin.y = authorTitleFrame.maxY
textFrame.size.width = max(textFrame.width, authorTitleFrame.maxX - textFrame.minX)
textFrame.size.height = max(textFrame.height, authorTitleFrame.height)
}
textFrame.size.width = max(textFrame.width, textTopLeftCutout + (textBottomRightCutout ?? 0.0))
if let textView = self.text.view { if let textView = self.text.view {
if textView.superview == nil { if textView.superview == nil {
textView.layer.anchorPoint = CGPoint() textView.layer.anchorPoint = CGPoint()
self.extractedContainerNode.contentNode.view.addSubview(textView) self.extractedContainerNode.contentNode.view.addSubview(textView)
} }
transition.setPosition(view: textView, position: textFrame.origin) transition.setPosition(view: textView, position: textFrame.origin)
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size) textView.bounds = CGRect(origin: CGPoint(), size: textSize)
} }
if let crownIcon = self.crownIcon, let image = crownIcon.image { if let crownIcon = self.crownIcon, let image = crownIcon.image {
crownIcon.frame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.minY - 1.0), size: image.size) crownIcon.frame = CGRect(origin: CGPoint(x: authorTitleFrame.minX - 4.0 - image.size.width, y: authorTitleFrame.minY - 1.0), size: image.size)
} }
let backgroundOrigin = CGPoint(x: avatarFrame.minX - avatarBackgroundInset, y: avatarFrame.minY - avatarBackgroundInset) let backgroundOrigin = CGPoint(x: avatarFrame.minX - avatarBackgroundInset, y: avatarFrame.minY - avatarBackgroundInset)
@ -344,8 +412,11 @@ public final class StoryLiveChatMessageComponent: Component {
if let starsAmountTextSize, displayStarsAmountBackground { if let starsAmountTextSize, displayStarsAmountBackground {
backgroundFrame.size.width += starsAmountTextSize.width + 30.0 backgroundFrame.size.width += starsAmountTextSize.width + 30.0
} }
if let adminBadgeTextSize {
backgroundFrame.size.width = max(backgroundFrame.width, authorTitleFrame.maxX + 4.0 + adminBadgeTextSize.width + 10.0)
}
if let textLayout = self.textExternal.layout { if let textLayout = self.textExternal.layout {
if textLayout.numberOfLines > 1 { if textLayout.numberOfLines > 1 || (component.message.isFromAdmin && !displayStarsAmountBackground) {
backgroundFrame.size.height = max(backgroundFrame.size.height, textFrame.maxY + 8.0 - backgroundOrigin.y) backgroundFrame.size.height = max(backgroundFrame.size.height, textFrame.maxY + 8.0 - backgroundOrigin.y)
} }
} }
@ -380,7 +451,14 @@ public final class StoryLiveChatMessageComponent: Component {
let backgroundCornerRadius = (avatarSize + avatarBackgroundInset * 2.0) * 0.5 let backgroundCornerRadius = (avatarSize + avatarBackgroundInset * 2.0) * 0.5
if let paidStars = component.message.paidStars, let baseColor = GroupCallMessagesContext.getStarAmountParamMapping(params: LiveChatMessageParams(appConfig: component.context.currentAppConfiguration.with({ $0 })), value: paidStars).color { var displayBackground = false
if component.message.paidStars != nil {
displayBackground = true
} else if component.message.isFromAdmin {
displayBackground = true
}
if displayBackground {
let backgroundView: UIImageView let backgroundView: UIImageView
if let current = self.backgroundView { if let current = self.backgroundView {
backgroundView = current backgroundView = current
@ -392,19 +470,30 @@ public final class StoryLiveChatMessageComponent: Component {
} }
transition.setFrame(view: backgroundView, frame: backgroundFrame) transition.setFrame(view: backgroundView, frame: backgroundFrame)
backgroundView.tintColor = StoryLiveChatMessageComponent.getMessageColor(color: baseColor).withAlphaComponent(component.layout.transparentBackground ? 0.7 : 1.0) if let paidStars = component.message.paidStars, let baseColor = GroupCallMessagesContext.getStarAmountParamMapping(params: LiveChatMessageParams(appConfig: component.context.currentAppConfiguration.with({ $0 })), value: paidStars).color {
backgroundView.tintColor = StoryLiveChatMessageComponent.getMessageColor(color: baseColor).withAlphaComponent(component.layout.transparentBackground ? 0.7 : 1.0)
let effectLayer: StarsParticleEffectLayer
if let current = self.effectLayer {
effectLayer = current
} else { } else {
effectLayer = StarsParticleEffectLayer() backgroundView.tintColor = UIColor(white: 0.0, alpha: 0.3)
self.effectLayer = effectLayer
backgroundView.layer.addSublayer(effectLayer)
} }
transition.setFrame(layer: effectLayer, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) if component.message.paidStars != nil {
effectLayer.update(color: UIColor(white: 1.0, alpha: 0.5), size: backgroundFrame.size, cornerRadius: backgroundCornerRadius, transition: transition) let effectLayer: StarsParticleEffectLayer
if let current = self.effectLayer {
effectLayer = current
} else {
effectLayer = StarsParticleEffectLayer()
self.effectLayer = effectLayer
backgroundView.layer.addSublayer(effectLayer)
}
transition.setFrame(layer: effectLayer, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
effectLayer.update(color: UIColor(white: 1.0, alpha: 0.5), size: backgroundFrame.size, cornerRadius: backgroundCornerRadius, transition: transition)
} else {
if let effectLayer = self.effectLayer {
self.effectLayer = nil
effectLayer.removeFromSuperlayer()
}
}
} else if let backgroundView = self.backgroundView { } else if let backgroundView = self.backgroundView {
self.backgroundView = nil self.backgroundView = nil
backgroundView.removeFromSuperview() backgroundView.removeFromSuperview()
@ -415,6 +504,14 @@ public final class StoryLiveChatMessageComponent: Component {
} }
} }
if let adminBadgeTextView = self.adminBadgeText?.view, let adminBadgeTextSize {
let adminBadgeTextFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - 10.0 - adminBadgeTextSize.width, y: backgroundFrame.minY + 9.0), size: adminBadgeTextSize)
if adminBadgeTextView.superview == nil {
self.extractedContainerNode.contentNode.view.addSubview(adminBadgeTextView)
}
transition.setFrame(view: adminBadgeTextView, frame: adminBadgeTextFrame)
}
let contentFrame = CGRect(origin: CGPoint(), size: size) let contentFrame = CGRect(origin: CGPoint(), size: size)
transition.setPosition(view: self.contentContainer, position: contentFrame.center) transition.setPosition(view: self.contentContainer, position: contentFrame.center)
transition.setBounds(view: self.contentContainer, bounds: CGRect(origin: CGPoint(), size: contentFrame.size)) transition.setBounds(view: self.contentContainer, bounds: CGRect(origin: CGPoint(), size: contentFrame.size))