This commit is contained in:
Isaac 2025-10-24 20:09:32 +04:00
parent 5633833085
commit fcdfd8e00f
16 changed files with 915 additions and 42 deletions

View file

@ -1306,6 +1306,10 @@ public struct ComponentTransition {
}
public func animateBlur(layer: CALayer, fromRadius: CGFloat, toRadius: CGFloat, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
if case .none = self.animation {
return
}
if let blurFilter = CALayer.blur() {
blurFilter.setValue(toRadius as NSNumber, forKey: "inputRadius")
layer.filters = [blurFilter]

View file

@ -4079,9 +4079,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
}
}
public func deleteMessage(id: GroupCallMessagesContext.Message.Id) {
public func deleteMessage(id: GroupCallMessagesContext.Message.Id, reportSpam: Bool) {
if let messagesContext = self.messagesContext {
messagesContext.deleteMessage(id: id)
messagesContext.deleteMessage(id: id, reportSpam: reportSpam)
}
}
}

View file

@ -4465,7 +4465,7 @@ public final class GroupCallMessagesContext {
})
}
func deleteMessage(id: Message.Id) {
func deleteMessage(id: Message.Id, reportSpam: Bool) {
var updatedState: State?
if let index = self.state.messages.firstIndex(where: { $0.id == id }) {
if updatedState == nil {
@ -4482,6 +4482,12 @@ public final class GroupCallMessagesContext {
if let updatedState {
self.state = updatedState
}
var flags: Int32 = 0
if reportSpam {
flags |= 1 << 0
}
let _ = self.account.network.request(Api.functions.phone.deleteGroupCallMessages(flags: flags, call: self.reference.apiInputGroupCall, messages: [Int32(clamping: id.id)])).startStandalone()
}
}
@ -4526,9 +4532,9 @@ public final class GroupCallMessagesContext {
}
}
public func deleteMessage(id: Message.Id) {
public func deleteMessage(id: Message.Id, reportSpam: Bool) {
self.impl.with { impl in
impl.deleteMessage(id: id)
impl.deleteMessage(id: id, reportSpam: reportSpam)
}
}

View file

@ -427,6 +427,14 @@ private final class AdminUserActionsSheetComponent: Component {
)
}
private func calculateLiveStreamResult() -> AdminUserActionsSheet.LiveStreamResult {
return AdminUserActionsSheet.LiveStreamResult(
reportSpam: !self.optionReportSelectedPeers.isEmpty,
deleteAll: !self.optionDeleteAllSelectedPeers.isEmpty,
ban: !self.optionBanSelectedPeers.isEmpty
)
}
private func updateScrolling(transition: ComponentTransition) {
guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else {
return
@ -578,7 +586,7 @@ private final class AdminUserActionsSheetComponent: Component {
if themeUpdated {
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor
self.backgroundLayer.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor.cgColor
self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
self.navigationBarSeparator.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
@ -663,6 +671,9 @@ private final class AdminUserActionsSheetComponent: Component {
}
}
}
case .liveStream:
availableOptions.append(.deleteAll)
availableOptions.append(.ban)
}
let optionsItem: (OptionsSection) -> AnyComponentWithIdentity<Empty> = { section in
@ -912,6 +923,13 @@ private final class AdminUserActionsSheetComponent: Component {
titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount))
}
}
case let .liveStream(messageCount, deleteAllMessageCount, _):
titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(messageCount))
if let deleteAllMessageCount {
if self.optionDeleteAllSelectedPeers == Set(component.peers.map(\.peer.id)) {
titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount))
}
}
}
let titleSize = self.title.update(
@ -965,6 +983,7 @@ private final class AdminUserActionsSheetComponent: Component {
transition: optionsSectionTransition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
style: .glass,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.Chat_AdminActionSheet_RestrictSectionHeader,
@ -1042,6 +1061,7 @@ private final class AdminUserActionsSheetComponent: Component {
}
if case let .channel(channel) = component.chatPeer, channel.isMonoForum {
} else if case .liveStream = component.mode {
} else {
var allConfigItems: [(ConfigItem, Bool)] = []
if !self.allowedMediaRights.isEmpty || !self.allowedParticipantRights.isEmpty {
@ -1362,9 +1382,11 @@ private final class AdminUserActionsSheetComponent: Component {
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
style: .glass,
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 54.0 * 0.5
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
@ -1389,11 +1411,13 @@ private final class AdminUserActionsSheetComponent: Component {
completion(self.calculateMonoforumResult())
case let .chat(_, _, completion):
completion(self.calculateChatResult())
case let .liveStream(_, _, completion):
completion(self.calculateLiveStreamResult())
}
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 54.0)
)
let bottomPanelHeight = 8.0 + environment.safeInsets.bottom + actionButtonSize.height
let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize)
@ -1462,6 +1486,7 @@ private final class AdminUserActionsSheetComponent: Component {
public class AdminUserActionsSheet: ViewControllerComponentContainer {
public enum Mode {
case chat(messageCount: Int, deleteAllMessageCount: Int?, completion: (ChatResult) -> Void)
case liveStream(messageCount: Int, deleteAllMessageCount: Int?, completion: (LiveStreamResult) -> Void)
case monoforum(completion: (MonoforumResult) -> Void)
}
@ -1479,6 +1504,18 @@ public class AdminUserActionsSheet: ViewControllerComponentContainer {
}
}
public final class LiveStreamResult {
public let reportSpam: Bool
public let deleteAll: Bool
public let ban: Bool
init(reportSpam: Bool, deleteAll: Bool, ban: Bool) {
self.reportSpam = reportSpam
self.deleteAll = deleteAll
self.ban = ban
}
}
public final class MonoforumResult {
public let ban: Bool
public let reportSpam: Bool
@ -1493,9 +1530,9 @@ public class AdminUserActionsSheet: ViewControllerComponentContainer {
private var isDismissed: Bool = false
public init(context: AccountContext, chatPeer: EnginePeer, peers: [RenderedChannelParticipant], mode: Mode) {
public init(context: AccountContext, chatPeer: EnginePeer, peers: [RenderedChannelParticipant], mode: Mode, customTheme: PresentationTheme? = nil) {
self.context = context
super.init(context: context, component: AdminUserActionsSheetComponent(context: context, chatPeer: chatPeer, peers: peers, mode: mode), navigationBarAppearance: .none)
super.init(context: context, component: AdminUserActionsSheetComponent(context: context, chatPeer: chatPeer, peers: peers, mode: mode), navigationBarAppearance: .none, theme: customTheme.flatMap({ .custom($0) }) ?? .default)
self.statusBar.statusBarStyle = .Ignore
self.navigationPresentation = .flatModal

View file

@ -27,6 +27,9 @@ swift_library(
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramUI/Components/GlassControls",
"//submodules/Components/BundleIconComponent",
"//submodules/Components/MultilineTextComponent",
],
visibility = [
"//visibility:public",

View file

@ -18,8 +18,11 @@ import TelegramNotices
import GlassBackgroundComponent
import ComponentFlow
import ComponentDisplayAdapters
import GlassControls
import BundleIconComponent
import MultilineTextComponent
private enum SubscriberAction: Equatable {
private enum SubscriberAction: Equatable, Hashable {
case join
case joinGroup
case applyToJoin
@ -143,7 +146,10 @@ private func actionForPeer(context: AccountContext, peer: Peer, interfaceState:
private let badgeFont = Font.regular(14.0)
public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
private let buttonBackgroundView: GlassBackgroundView
private let panelContainer = UIView()
private let panel = ComponentView<Empty>()
/*private let buttonBackgroundView: GlassBackgroundView
private let button: HighlightableButton
private let buttonTitle: ImmediateTextNode
private let buttonTintTitle: ImmediateTextNode
@ -158,7 +164,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
private let suggestedPostButtonBackgroundView: GlassBackgroundView
private let suggestedPostButton: HighlightableButton
private let suggestedPostButtonIconView: UIImageView
private let suggestedPostButtonIconView: UIImageView*/
private var action: SubscriberAction?
@ -171,7 +177,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, Bool, LayoutMetrics)?
public override init() {
self.button = HighlightableButton()
/*self.button = HighlightableButton()
self.buttonBackgroundView = GlassBackgroundView()
self.buttonBackgroundView.isUserInteractionEnabled = false
self.buttonTitle = ImmediateTextNode()
@ -203,18 +209,20 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
self.suggestedPostButtonIconView = GlassBackgroundView.ContentImageView()
self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButtonIconView)
self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButton)
self.suggestedPostButtonBackgroundView.isHidden = true
self.suggestedPostButtonBackgroundView.isHidden = true*/
super.init()
self.view.addSubview(self.buttonBackgroundView)
/*self.view.addSubview(self.buttonBackgroundView)
self.view.addSubview(self.helpButtonBackgroundView)
self.view.addSubview(self.giftButtonBackgroundView)
self.view.addSubview(self.suggestedPostButtonBackgroundView)
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
self.helpButton.addTarget(self, action: #selector(self.helpPressed), for: .touchUpInside)
self.giftButton.addTarget(self, action: #selector(self.giftPressed), for: .touchUpInside)
self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), for: .touchUpInside)
self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), for: .touchUpInside)*/
self.view.addSubview(self.panelContainer)
}
deinit {
@ -330,11 +338,17 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
}
#endif*/
if giftCount < 2 && !self.giftButton.isHidden {
let giftItemView = (self.panel.view as? GlassControlPanelComponent.View)?.leftItemView?.itemView(id: AnyHashable("gift"))
let suggestPostItemView = (self.panel.view as? GlassControlPanelComponent.View)?.leftItemView?.itemView(id: AnyHashable("suggestPost"))
if giftCount < 2, let giftItemView {
let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start()
Queue.mainQueue().after(0.4, {
let absoluteFrame = self.giftButton.convert(self.giftButton.bounds, to: parentController.view)
Queue.mainQueue().after(0.4, { [weak giftItemView] in
guard let giftItemView else {
return
}
let absoluteFrame = giftItemView.convert(giftItemView.bounds, to: parentController.view)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY), size: CGSize())
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -357,11 +371,14 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
)
self.interfaceInteraction?.presentControllerInCurrent(tooltipController, nil)
})
} else if suggestCount < 2 && !self.suggestedPostButton.isHidden {
} else if suggestCount < 2, let suggestPostItemView {
let _ = ApplicationSpecificNotice.incrementChannelSuggestTooltip(accountManager: context.sharedContext.accountManager).start()
Queue.mainQueue().after(0.4, {
let absoluteFrame = self.suggestedPostButton.convert(self.suggestedPostButton.bounds, to: parentController.view)
Queue.mainQueue().after(0.4, { [weak suggestPostItemView] in
guard let suggestPostItemView else {
return
}
let absoluteFrame = suggestPostItemView.convert(suggestPostItemView.bounds, to: parentController.view)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY), size: CGSize())
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -394,7 +411,133 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
let isFirstTime = self.layoutData == nil
self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics)
if self.presentationInterfaceState != interfaceState || force {
var transition = transition
if !isFirstTime && !transition.isAnimated {
transition = .animated(duration: 0.4, curve: .spring)
}
self.presentationInterfaceState = interfaceState
var centerAction: (title: String, isAccent: Bool)?
if let context = self.context, let peer = interfaceState.renderedPeer?.peer, let action = actionForPeer(context: context, peer: peer, interfaceState: interfaceState, isJoining: self.isJoining, isMuted: interfaceState.peerIsMuted) {
self.action = action
let (title, _) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings)
var isAccent = false
if case .join = self.action {
isAccent = true
}
centerAction = (title, isAccent)
}
var displayGift = false
var displaySuggestPost = false
var displayHelp = false
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
if case .broadcast = peer.info, interfaceState.starGiftsAvailable {
displayGift = true
}
if case let .broadcast(broadcastInfo) = peer.info, broadcastInfo.flags.contains(.hasMonoforum) {
displaySuggestPost = true
}
if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications {
displayHelp = true
}
}
var leftInset = leftInset + 8.0
var rightInset = rightInset + 8.0
if bottomInset <= 32.0 {
leftInset += 18.0
rightInset += 18.0
}
var leftPanelItems: [GlassControlGroupComponent.Item] = []
if displaySuggestPost {
leftPanelItems.append(GlassControlGroupComponent.Item(
id: "suggestPost",
content: .icon("Chat/Input/Accessory Panels/SuggestPost"),
action: { [weak self] in
self?.suggestedPostPressed()
}
))
}
if displayGift {
leftPanelItems.append(GlassControlGroupComponent.Item(
id: "gift",
content: .icon("Chat/Input/Accessory Panels/Gift"),
action: { [weak self] in
self?.giftPressed()
}
))
}
if displayHelp {
leftPanelItems.append(GlassControlGroupComponent.Item(
id: "help",
content: .icon("Chat/Input/Accessory Panels/Help"),
action: { [weak self] in
self?.helpPressed()
}
))
}
var centerPanelItem: GlassControlPanelComponent.Item?
if let centerAction {
centerPanelItem = GlassControlPanelComponent.Item(
items: [GlassControlGroupComponent.Item(
id: 0,
content: .text(centerAction.title),
action: { [weak self] in
self?.buttonPressed()
}
)],
background: centerAction.isAccent ? .activeTint : .panel
)
}
var rightPanelItems: [GlassControlGroupComponent.Item] = []
rightPanelItems.append(GlassControlGroupComponent.Item(
id: "search",
content: .icon("Chat List/SearchIcon"),
action: { [weak self] in
guard let self else {
return
}
self.interfaceInteraction?.beginMessageSearch(.everything, "")
}
))
let panelHeight = defaultHeight(metrics: metrics)
let _ = isFirstTime
let panelFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight))
let _ = self.panel.update(
transition: ComponentTransition(transition),
component: AnyComponent(GlassControlPanelComponent(
theme: interfaceState.theme,
leftItem: leftPanelItems.isEmpty ? nil : GlassControlPanelComponent.Item(
items: leftPanelItems,
background: .panel
),
centralItem: centerPanelItem,
rightItem: rightPanelItems.isEmpty ? nil : GlassControlPanelComponent.Item(
items: rightPanelItems,
background: .panel
)
)),
environment: {},
containerSize: panelFrame.size
)
if let panelView = self.panel.view {
if panelView.superview == nil {
self.panelContainer.addSubview(panelView)
}
transition.updateFrame(view: self.panelContainer, frame: panelFrame)
transition.updateFrame(view: panelView, frame: CGRect(origin: CGPoint(), size: panelFrame.size))
}
/*if self.presentationInterfaceState != interfaceState || force {
let previousState = self.presentationInterfaceState
self.presentationInterfaceState = interfaceState
@ -504,7 +647,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
transition.updateFrame(view: self.suggestedPostButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size)))
}
transition.updateFrame(view: self.suggestedPostButton, frame: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size))
self.suggestedPostButtonBackgroundView.update(size: suggestedPostButtonFrame.size, cornerRadius: suggestedPostButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: ComponentTransition(transition))
self.suggestedPostButtonBackgroundView.update(size: suggestedPostButtonFrame.size, cornerRadius: suggestedPostButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), isInteractive: true, transition: ComponentTransition(transition))*/
return panelHeight
}

View file

@ -2094,6 +2094,8 @@ private final class ChatSendStarsScreenComponent: Component {
let separatorSpacing: CGFloat = 10.0
transition.setFrame(layer: topPeersLeftSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: separatorY), size: CGSize(width: max(0.0, topPeersBackgroundFrame.minX - separatorSpacing - sideInset), height: UIScreenPixel)))
transition.setFrame(layer: topPeersRightSeparator, frame: CGRect(origin: CGPoint(x: topPeersBackgroundFrame.maxX + separatorSpacing, y: separatorY), size: CGSize(width: max(0.0, availableSize.width - sideInset - (topPeersBackgroundFrame.maxX + separatorSpacing)), height: UIScreenPixel)))
contentHeight += 60.0
}
var mappedTopPeers = reactData.topPeers
@ -2261,7 +2263,7 @@ private final class ChatSendStarsScreenComponent: Component {
itemComponentView.alpha = 0.0
}
let itemFrame = CGRect(origin: CGPoint(x: itemX, y: contentHeight + 60.0), size: itemSize)
let itemFrame = CGRect(origin: CGPoint(x: itemX, y: contentHeight), size: itemSize)
if animateItem {
itemPositionTransition.setPosition(view: itemComponentView, position: itemFrame.center)
@ -2277,7 +2279,7 @@ private final class ChatSendStarsScreenComponent: Component {
itemX += itemSize.width + itemSpacing
}
contentHeight += 164.0
contentHeight += 104.0
}
if !reactData.topPeers.isEmpty {

View file

@ -2956,7 +2956,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
transition.updateFrame(node: self.textPlaceholderNode, frame: textPlaceholderFrame)
let sendAsButtonFrame = CGRect(origin: CGPoint(x: 3.0, y: textInputContainerBackgroundFrame.height - 3.0 - 34.0), size: CGSize(width: 34.0, height: 34.0))
transition.updateFrame(node: self.sendAsAvatarButtonNode, frame: sendAsButtonFrame)
transition.updatePosition(node: self.sendAsAvatarButtonNode, position: sendAsButtonFrame.center)
transition.updateBounds(node: self.sendAsAvatarButtonNode, bounds: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size))
transition.updateAlpha(layer: self.sendAsAvatarButtonNode.layer, alpha: audioRecordingItemsAlpha)
transition.updateTransformScale(layer: self.sendAsAvatarButtonNode.layer, scale: audioRecordingItemsAlpha == 0.0 ? 0.001 : 1.0)
transition.updateFrame(node: self.sendAsAvatarContainerNode, frame: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size))
transition.updateFrame(node: self.sendAsAvatarReferenceNode, frame: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size))
transition.updatePosition(node: self.sendAsAvatarNode, position: CGRect(origin: CGPoint(), size: sendAsButtonFrame.size).center)
@ -3000,7 +3003,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
var mediaActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX + 6.0, y: textInputContainerBackgroundFrame.maxY - mediaActionButtonsSize.height), size: mediaActionButtonsSize)
if inputHasText || self.extendedSearchLayout || hasMediaDraft {
if inputHasText || self.extendedSearchLayout || hasMediaDraft || interfaceState.interfaceState.forwardMessageIds != nil {
mediaActionButtonsFrame.origin.x = width + 8.0
}
transition.updateFrame(node: self.mediaActionButtons, frame: mediaActionButtonsFrame)

View file

@ -0,0 +1,24 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "GlassControls",
module_name = "GlassControls",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/Components/MultilineTextComponent",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,236 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import GlassBackgroundComponent
import PlainButtonComponent
import BundleIconComponent
import MultilineTextComponent
public final class GlassControlGroupComponent: Component {
public final class Item: Equatable {
public enum Content: Hashable {
case icon(String)
case text(String)
}
public let id: AnyHashable
public let content: Content
public let action: (() -> Void)?
public init(id: AnyHashable, content: Content, action: (() -> Void)?) {
self.id = id
self.content = content
self.action = action
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.content != rhs.content {
return false
}
if (lhs.action == nil) != (rhs.action == nil) {
return false
}
return true
}
}
public enum Background {
case panel
case activeTint
}
public let theme: PresentationTheme
public let background: Background
public let items: [Item]
public let minWidth: CGFloat
public init(
theme: PresentationTheme,
background: Background,
items: [Item],
minWidth: CGFloat
) {
self.theme = theme
self.background = background
self.items = items
self.minWidth = minWidth
}
public static func ==(lhs: GlassControlGroupComponent, rhs: GlassControlGroupComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.background != rhs.background {
return false
}
if lhs.items != rhs.items {
return false
}
if lhs.minWidth != rhs.minWidth {
return false
}
return true
}
public final class View: UIView {
private let backgroundView: GlassBackgroundView
private var itemViews: [AnyHashable: ComponentView<Empty>] = [:]
private var component: GlassControlGroupComponent?
private weak var state: EmptyComponentState?
override public init(frame: CGRect) {
self.backgroundView = GlassBackgroundView()
super.init(frame: frame)
self.addSubview(self.backgroundView)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func itemView(id: AnyHashable) -> UIView? {
return self.itemViews[id]?.view
}
func update(component: GlassControlGroupComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
self.component = component
self.state = state
struct ItemId: Hashable {
var id: AnyHashable
var contentId: AnyHashable
init(id: AnyHashable, contentId: AnyHashable) {
self.id = id
self.contentId = contentId
}
}
var contentsWidth: CGFloat = 0.0
var validIds: [AnyHashable] = []
var isInteractive = false
for item in component.items {
let itemId = ItemId(id: item.id, contentId: item.content)
validIds.append(itemId)
let itemView: ComponentView<Empty>
var itemTransition = transition
if let current = self.itemViews[itemId] {
itemView = current
} else {
itemView = ComponentView()
self.itemViews[itemId] = itemView
itemTransition = itemTransition.withAnimation(.none)
}
if item.action != nil {
isInteractive = true
}
let content: AnyComponent<Empty>
var itemInsets = UIEdgeInsets()
switch item.content {
case let .icon(name):
content = AnyComponent(BundleIconComponent(
name: name,
tintColor: component.background == .activeTint ? component.theme.list.itemCheckColors.foregroundColor : component.theme.chat.inputPanel.panelControlColor
))
case let .text(string):
content = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: string, font: Font.semibold(15.0), textColor: component.background == .activeTint ? component.theme.list.itemCheckColors.foregroundColor : component.theme.chat.inputPanel.panelControlColor))
))
itemInsets.left = 10.0
itemInsets.right = itemInsets.left
}
var minItemWidth: CGFloat = 40.0
if component.items.count == 1 {
minItemWidth = max(minItemWidth, component.minWidth)
}
let itemSize = itemView.update(
transition: itemTransition,
component: AnyComponent(PlainButtonComponent(
content: content,
minSize: CGSize(width: minItemWidth, height: 40.0),
contentInsets: itemInsets,
action: {
item.action?()
},
isEnabled: item.action != nil,
animateAlpha: false,
animateScale: false,
animateContents: false
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
)
let itemFrame = CGRect(origin: CGPoint(x: contentsWidth, y: 0.0), size: itemSize)
if let itemComponentView = itemView.view {
var animateIn = false
if itemComponentView.superview == nil {
animateIn = true
self.backgroundView.contentView.addSubview(itemComponentView)
itemComponentView.alpha = 0.0
}
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
if animateIn {
alphaTransition.setAlpha(view: itemComponentView, alpha: 1.0)
alphaTransition.animateBlur(layer: itemComponentView.layer, fromRadius: 8.0, toRadius: 0.0)
}
}
contentsWidth += itemSize.width
}
var removeIds: [AnyHashable] = []
for (id, itemView) in self.itemViews {
if !validIds.contains(id) {
removeIds.append(id)
if let itemComponentView = itemView.view {
alphaTransition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in
itemComponentView?.removeFromSuperview()
})
alphaTransition.animateBlur(layer: itemComponentView.layer, fromRadius: 0.0, toRadius: 8.0, removeOnCompletion: false)
}
}
}
for id in removeIds {
self.itemViews.removeValue(forKey: id)
}
let size = CGSize(width: contentsWidth, height: availableSize.height)
let tintColor: GlassBackgroundView.TintColor
switch component.background {
case .panel:
tintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
case .activeTint:
tintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7), innerColor: component.theme.list.itemCheckColors.fillColor)
}
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, isInteractive: isInteractive, transition: transition)
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -0,0 +1,273 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import GlassBackgroundComponent
public final class GlassControlPanelComponent: Component {
public final class Item: Equatable {
public let items: [GlassControlGroupComponent.Item]
public let background: GlassControlGroupComponent.Background
public init(items: [GlassControlGroupComponent.Item], background: GlassControlGroupComponent.Background) {
self.items = items
self.background = background
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
if lhs.items != rhs.items {
return false
}
if lhs.background != rhs.background {
return false
}
return true
}
}
public let theme: PresentationTheme
public let leftItem: Item?
public let rightItem: Item?
public let centralItem: Item?
public init(
theme: PresentationTheme,
leftItem: Item?,
centralItem: Item?,
rightItem: Item?
) {
self.theme = theme
self.leftItem = leftItem
self.centralItem = centralItem
self.rightItem = rightItem
}
public static func ==(lhs: GlassControlPanelComponent, rhs: GlassControlPanelComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.leftItem != rhs.leftItem {
return false
}
if lhs.centralItem != rhs.centralItem {
return false
}
if lhs.rightItem != rhs.rightItem {
return false
}
return true
}
public final class View: UIView {
private let glassContainerView: GlassBackgroundContainerView
private var leftItemComponent: ComponentView<Empty>?
private var centralItemComponent: ComponentView<Empty>?
private var rightItemComponent: ComponentView<Empty>?
private var component: GlassControlPanelComponent?
private weak var state: EmptyComponentState?
public var leftItemView: GlassControlGroupComponent.View? {
return self.leftItemComponent?.view as? GlassControlGroupComponent.View
}
public var centerItemView: GlassControlGroupComponent.View? {
return self.centralItemComponent?.view as? GlassControlGroupComponent.View
}
public var rightItemView: GlassControlGroupComponent.View? {
return self.rightItemComponent?.view as? GlassControlGroupComponent.View
}
override public init(frame: CGRect) {
self.glassContainerView = GlassBackgroundContainerView()
super.init(frame: frame)
self.addSubview(self.glassContainerView)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: GlassControlPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
let minSpacing: CGFloat = 8.0
var leftItemFrame: CGRect?
if let leftItem = component.leftItem {
let leftItemComponent: ComponentView<Empty>
var leftItemTransition = transition
if let current = self.leftItemComponent {
leftItemComponent = current
} else {
leftItemComponent = ComponentView()
self.leftItemComponent = leftItemComponent
leftItemTransition = transition.withAnimation(.none)
}
let leftItemSize = leftItemComponent.update(
transition: leftItemTransition,
component: AnyComponent(GlassControlGroupComponent(
theme: component.theme,
background: leftItem.background,
items: leftItem.items,
minWidth: 40.0
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
)
let leftItemFrameValue = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: leftItemSize)
leftItemFrame = leftItemFrameValue
if let leftItemComponentView = leftItemComponent.view {
var animateIn = false
if leftItemComponentView.superview == nil {
animateIn = true
self.glassContainerView.contentView.addSubview(leftItemComponentView)
ComponentTransition.immediate.setScale(view: leftItemComponentView, scale: 0.001)
}
leftItemTransition.setPosition(view: leftItemComponentView, position: leftItemFrameValue.center)
leftItemTransition.setBounds(view: leftItemComponentView, bounds: CGRect(origin: CGPoint(), size: leftItemFrameValue.size))
if animateIn {
alphaTransition.animateAlpha(view: leftItemComponentView, from: 0.0, to: 1.0)
transition.setScale(view: leftItemComponentView, scale: 1.0)
}
}
} else if let leftItemComponent = self.leftItemComponent {
self.leftItemComponent = nil
if let leftItemComponentView = leftItemComponent.view {
transition.setScale(view: leftItemComponentView, scale: 0.001)
alphaTransition.setAlpha(view: leftItemComponentView, alpha: 0.0, completion: { [weak leftItemComponentView] _ in
leftItemComponentView?.removeFromSuperview()
})
}
}
var rightItemFrame: CGRect?
if let rightItem = component.rightItem {
let rightItemComponent: ComponentView<Empty>
var rightItemTransition = transition
if let current = self.rightItemComponent {
rightItemComponent = current
} else {
rightItemComponent = ComponentView()
self.rightItemComponent = rightItemComponent
rightItemTransition = transition.withAnimation(.none)
}
let rightItemSize = rightItemComponent.update(
transition: rightItemTransition,
component: AnyComponent(GlassControlGroupComponent(
theme: component.theme,
background: rightItem.background,
items: rightItem.items,
minWidth: 40.0
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
)
let rightItemFrameValue = CGRect(origin: CGPoint(x: availableSize.width - rightItemSize.width, y: 0.0), size: rightItemSize)
rightItemFrame = rightItemFrameValue
if let rightItemComponentView = rightItemComponent.view {
var animateIn = false
if rightItemComponentView.superview == nil {
animateIn = true
self.glassContainerView.contentView.addSubview(rightItemComponentView)
ComponentTransition.immediate.setScale(view: rightItemComponentView, scale: 0.001)
}
rightItemTransition.setPosition(view: rightItemComponentView, position: rightItemFrameValue.center)
rightItemTransition.setBounds(view: rightItemComponentView, bounds: CGRect(origin: CGPoint(), size: rightItemFrameValue.size))
if animateIn {
alphaTransition.animateAlpha(view: rightItemComponentView, from: 0.0, to: 1.0)
transition.setScale(view: rightItemComponentView, scale: 1.0)
}
}
} else if let rightItemComponent = self.rightItemComponent {
self.rightItemComponent = nil
if let rightItemComponentView = rightItemComponent.view {
transition.setScale(view: rightItemComponentView, scale: 0.001)
alphaTransition.setAlpha(view: rightItemComponentView, alpha: 0.0, completion: { [weak rightItemComponentView] _ in
rightItemComponentView?.removeFromSuperview()
})
}
}
if let centralItem = component.centralItem {
let centralItemComponent: ComponentView<Empty>
var centralItemTransition = transition
if let current = self.centralItemComponent {
centralItemComponent = current
} else {
centralItemComponent = ComponentView()
self.centralItemComponent = centralItemComponent
centralItemTransition = transition.withAnimation(.none)
}
var maxCentralItemSize = CGSize(width: availableSize.width, height: availableSize.height)
var centralRightInset: CGFloat = 0.0
if let rightItemFrame {
centralRightInset = availableSize.width - rightItemFrame.minX + minSpacing
}
var centralLeftInset: CGFloat = 0.0
if let leftItemFrame {
centralLeftInset = leftItemFrame.maxX + minSpacing
}
maxCentralItemSize.width = max(1.0, availableSize.width - centralLeftInset - centralRightInset)
let centralItemSize = centralItemComponent.update(
transition: centralItemTransition,
component: AnyComponent(GlassControlGroupComponent(
theme: component.theme,
background: centralItem.background,
items: centralItem.items,
minWidth: 165.0
)),
environment: {},
containerSize: maxCentralItemSize
)
let centralItemFrameValue = CGRect(origin: CGPoint(x: centralLeftInset + floor((availableSize.width - centralLeftInset - centralRightInset - centralItemSize.width) * 0.5), y: 0.0), size: centralItemSize)
if let centralItemComponentView = centralItemComponent.view {
var animateIn = false
if centralItemComponentView.superview == nil {
animateIn = true
self.glassContainerView.contentView.addSubview(centralItemComponentView)
ComponentTransition.immediate.setScale(view: centralItemComponentView, scale: 0.001)
}
centralItemTransition.setPosition(view: centralItemComponentView, position: centralItemFrameValue.center)
centralItemTransition.setBounds(view: centralItemComponentView, bounds: CGRect(origin: CGPoint(), size: centralItemFrameValue.size))
if animateIn {
alphaTransition.animateAlpha(view: centralItemComponentView, from: 0.0, to: 1.0)
transition.setScale(view: centralItemComponentView, scale: 1.0)
}
}
} else if let centralItemComponent = self.centralItemComponent {
self.centralItemComponent = nil
if let centralItemComponentView = centralItemComponent.view {
transition.setScale(view: centralItemComponentView, scale: 0.001)
alphaTransition.setAlpha(view: centralItemComponentView, alpha: 0.0, completion: { [weak centralItemComponentView] _ in
centralItemComponentView?.removeFromSuperview()
})
}
}
transition.setFrame(view: self.glassContainerView, frame: CGRect(origin: CGPoint(), size: availableSize))
self.glassContainerView.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
return availableSize
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -331,9 +331,15 @@ public final class ListSectionComponent: Component {
case legacy
}
public enum BackgroundColor {
case base
case modal
}
public let theme: PresentationTheme
public let style: Style
public let background: Background
public let backgroundColor: BackgroundColor
public let header: AnyComponent<Empty>?
public let footer: AnyComponent<Empty>?
public let items: [AnyComponentWithIdentity<Empty>]
@ -345,6 +351,7 @@ public final class ListSectionComponent: Component {
theme: PresentationTheme,
style: Style = .legacy,
background: Background = .all,
backgroundColor: BackgroundColor = .base,
header: AnyComponent<Empty>?,
footer: AnyComponent<Empty>?,
items: [AnyComponentWithIdentity<Empty>],
@ -355,6 +362,7 @@ public final class ListSectionComponent: Component {
self.theme = theme
self.style = style
self.background = background
self.backgroundColor = backgroundColor
self.header = header
self.footer = footer
self.items = items
@ -373,6 +381,9 @@ public final class ListSectionComponent: Component {
if lhs.background != rhs.background {
return false
}
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.header != rhs.header {
return false
}

View file

@ -109,6 +109,7 @@ swift_library(
"//submodules/TelegramUI/Components/Stories/LiveChat/StoryLiveChatMessageComponent",
"//submodules/TelegramUI/Components/StarsParticleEffect",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/AdminUserActionsSheet",
],
visibility = [
"//visibility:public",

View file

@ -17,6 +17,7 @@ import MultilineTextComponent
import ContextUI
import StarsParticleEffect
import StoryLiveChatMessageComponent
import AdminUserActionsSheet
private final class PinnedBarMessageComponent: Component {
let context: AccountContext
@ -374,6 +375,7 @@ final class StoryContentLiveChatComponent: Component {
let call: PresentationGroupCall
let storyPeerId: EnginePeer.Id
let insets: UIEdgeInsets
let controller: () -> ViewController?
init(
external: External,
@ -382,7 +384,8 @@ final class StoryContentLiveChatComponent: Component {
theme: PresentationTheme,
call: PresentationGroupCall,
storyPeerId: EnginePeer.Id,
insets: UIEdgeInsets
insets: UIEdgeInsets,
controller: @escaping () -> ViewController?
) {
self.external = external
self.context = context
@ -391,6 +394,7 @@ final class StoryContentLiveChatComponent: Component {
self.call = call
self.storyPeerId = storyPeerId
self.insets = insets
self.controller = controller
}
static func ==(lhs: StoryContentLiveChatComponent, rhs: StoryContentLiveChatComponent) -> Bool {
@ -437,6 +441,7 @@ final class StoryContentLiveChatComponent: Component {
private var stateDisposable: Disposable?
private var currentListIsEmpty: Bool = true
private var isMessageContextMenuOpen: Bool = false
public var isChatEmpty: Bool {
guard let messagesState = self.messagesState else {
@ -551,6 +556,60 @@ final class StoryContentLiveChatComponent: Component {
self.state?.updated(transition: .spring(duration: 0.4))
}
private func displayDeleteMessageAndBan(id: GroupCallMessagesContext.Message.Id) {
Task { @MainActor [weak self] in
guard let self, let component = self.component else {
return
}
guard let chatPeer = await component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: component.storyPeerId)
).get() else {
return
}
guard let messagesState = self.messagesState, let message = messagesState.messages.first(where: { $0.id == id }) else {
return
}
guard let author = message.author else {
return
}
var totalCount = 0
for message in messagesState.messages {
if message.author?.id == author.id {
totalCount += 1
}
}
guard let controller = component.controller() else {
return
}
controller.push(AdminUserActionsSheet(
context: component.context,
chatPeer: chatPeer,
peers: [RenderedChannelParticipant(
participant: .member(
id: author.id,
invitedAt: 0,
adminInfo: nil,
banInfo: nil,
rank: nil,
subscriptionUntilDate: nil
),
peer: author._asPeer()
)],
mode: .liveStream(
messageCount: 1,
deleteAllMessageCount: totalCount,
completion: { [weak self] result in
guard let self else {
return
}
let _ = self
}
),
customTheme: defaultDarkColorPresentationTheme
))
}
}
private func openMessageContextMenu(id: GroupCallMessagesContext.Message.Id, gesture: ContextGesture, sourceNode: ContextExtractedContentContainingNode) {
Task { @MainActor [weak self] in
guard let self else {
@ -583,7 +642,52 @@ final class StoryContentLiveChatComponent: Component {
})))
let state = await (component.call.state |> take(1)).get()
if state.canManageCall || component.storyPeerId == component.context.account.peerId {
var isAdmin = state.canManageCall
if component.storyPeerId == component.context.account.peerId {
isAdmin = true
}
var canDelete = isAdmin
var isMyMessage = false
guard let messagesState = self.messagesState, let message = messagesState.messages.first(where: { $0.id == id }) else {
return
}
if message.author?.id == component.context.account.peerId {
isMyMessage = true
canDelete = true
}
if !isMyMessage, let author = message.author {
items.append(.action(ContextMenuActionItem(text: "Open Profile", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
guard let self else {
return
}
c?.dismiss(completion: { [weak self] in
guard let self, let component = self.component else {
return
}
guard let controller = component.controller(), let navigationController = controller.navigationController as? NavigationController else {
return
}
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
navigationController: navigationController,
context: component.context,
chatLocation: .peer(author),
keepStack: .always
))
})
})))
}
#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 {
return
@ -594,7 +698,11 @@ final class StoryContentLiveChatComponent: Component {
return
}
if let call = component.call as? PresentationGroupCallImpl {
call.deleteMessage(id: id)
if isAdmin && !isMyMessage {
self.displayDeleteMessageAndBan(id: id)
} else {
call.deleteMessage(id: id, reportSpam: false)
}
}
})
})))
@ -615,17 +723,18 @@ final class StoryContentLiveChatComponent: Component {
guard let self else {
return
}
if let listView = self.list.view {
let transition: ComponentTransition = .easeInOut(duration: 0.2)
transition.setAlpha(view: listView, alpha: 1.0)
self.isMessageContextMenuOpen = false
if !self.isUpdating {
self.state?.updated(transition: .easeInOut(duration: 0.2), isLocal: true)
}
}
if let listView = self.list.view {
let transition: ComponentTransition = .easeInOut(duration: 0.2)
transition.setAlpha(view: listView, alpha: 0.25)
self.isMessageContextMenuOpen = true
if !self.isUpdating {
self.state?.updated(transition: .easeInOut(duration: 0.2), isLocal: true)
}
component.context.sharedContext.mainWindow?.presentInGlobalOverlay(contextController)
component.controller()?.presentInGlobalOverlay(contextController)
}
}
@ -793,7 +902,14 @@ final class StoryContentLiveChatComponent: Component {
}
transition.setPosition(view: listView, position: listFrame.offsetBy(dx: 0.0, dy: self.isChatExpanded ? 0.0 : listFrame.height).center)
transition.setBounds(view: listView, bounds: CGRect(origin: CGPoint(), size: listFrame.size))
alphaTransition.setAlpha(view: listView, alpha: listItems.isEmpty ? 0.0 : 1.0)
let listAlpha: CGFloat
if self.isMessageContextMenuOpen {
listAlpha = 0.25
} else {
listAlpha = listItems.isEmpty ? 0.0 : 1.0
}
alphaTransition.setAlpha(view: listView, alpha: listAlpha)
}
transition.setFrame(view: self.listContainer, frame: CGRect(origin: CGPoint(), size: availableSize))

View file

@ -40,8 +40,9 @@ final class StoryItemContentComponent: Component {
let preferHighQuality: Bool
let isEmbeddedInCamera: Bool
let activateReaction: (UIView, MessageReaction.Reaction) -> Void
let controller: () -> ViewController?
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, preferHighQuality: Bool, isEmbeddedInCamera: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) {
init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, preferHighQuality: Bool, isEmbeddedInCamera: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void, controller: @escaping () -> ViewController?) {
self.context = context
self.strings = strings
self.peer = peer
@ -55,6 +56,7 @@ final class StoryItemContentComponent: Component {
self.preferHighQuality = preferHighQuality
self.isEmbeddedInCamera = isEmbeddedInCamera
self.activateReaction = activateReaction
self.controller = controller
}
static func ==(lhs: StoryItemContentComponent, rhs: StoryItemContentComponent) -> Bool {
@ -859,7 +861,13 @@ final class StoryItemContentComponent: Component {
theme: environment.theme,
call: mediaStreamCall,
storyPeerId: component.peer.id,
insets: environment.containerInsets
insets: environment.containerInsets,
controller: { [weak self] in
guard let self, let component = self.component else {
return nil
}
return component.controller()
}
)),
environment: {},
containerSize: availableSize

View file

@ -1628,6 +1628,12 @@ public final class StoryItemSetContainerComponent: Component {
return
}
self.sendMessageContext.activateInlineReaction(view: self, reactionView: reactionView, reaction: reaction)
},
controller: { [weak self] in
guard let self, let component = self.component else {
return nil
}
return component.controller()
}
)),
environment: {