mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Updates
This commit is contained in:
parent
5633833085
commit
fcdfd8e00f
16 changed files with 915 additions and 42 deletions
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
24
submodules/TelegramUI/Components/GlassControls/BUILD
Normal file
24
submodules/TelegramUI/Components/GlassControls/BUILD
Normal 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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue