2023-11-04 00:37:02 +04:00

857 lines
41 KiB
Swift

import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import ComponentDisplayAdapters
import AppBundle
import ViewControllerComponent
import AccountContext
import TelegramCore
import Postbox
import SwiftSignalKit
import EntityKeyboard
import MultilineTextComponent
import Markdown
import ButtonComponent
import PremiumUI
import UndoUI
final class PeerAllowedReactionsScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let peerId: EnginePeer.Id
let initialContent: PeerAllowedReactionsScreen.Content
init(
context: AccountContext,
peerId: EnginePeer.Id,
initialContent: PeerAllowedReactionsScreen.Content
) {
self.context = context
self.peerId = peerId
self.initialContent = initialContent
}
static func ==(lhs: PeerAllowedReactionsScreenComponent, rhs: PeerAllowedReactionsScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.peerId != rhs.peerId {
return false
}
return true
}
final class View: UIView, UIScrollViewDelegate {
private let scrollView: UIScrollView
private let switchItem = ComponentView<Empty>()
private let switchInfoText = ComponentView<Empty>()
private var reactionsTitleText: ComponentView<Empty>?
private var reactionsInfoText: ComponentView<Empty>?
private var reactionInput: ComponentView<Empty>?
private let actionButton = ComponentView<Empty>()
private var reactionSelectionControl: ComponentView<Empty>?
private var isUpdating: Bool = false
private var component: PeerAllowedReactionsScreenComponent?
private(set) weak var state: EmptyComponentState?
private var environment: EnvironmentType?
private var isEnabled: Bool = false
private var availableReactions: AvailableReactions?
private var enabledReactions: [EmojiComponentReactionItem]?
private var emojiContent: EmojiPagerContentComponent?
private var emojiContentDisposable: Disposable?
private var displayInput: Bool = false
private var isApplyingSettings: Bool = false
private var applyDisposable: Disposable?
override init(frame: CGRect) {
self.scrollView = UIScrollView()
self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.scrollsToTop = false
self.scrollView.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true
self.scrollView.contentInsetAdjustmentBehavior = .never
self.scrollView.alwaysBounceVertical = true
super.init(frame: frame)
self.scrollView.delegate = self
self.addSubview(self.scrollView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.emojiContentDisposable?.dispose()
self.applyDisposable?.dispose()
}
func scrollToTop() {
self.scrollView.setContentOffset(CGPoint(), animated: true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateScrolling(transition: .immediate)
}
private func updateScrolling(transition: Transition) {
let navigationAlphaDistance: CGFloat = 16.0
let navigationAlpha: CGFloat = max(0.0, min(1.0, self.scrollView.contentOffset.y / navigationAlphaDistance))
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha)
transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha)
}
}
private func applySettings() {
guard let component = self.component else {
return
}
if self.isApplyingSettings {
return
}
guard let enabledReactions = self.enabledReactions else {
return
}
guard let availableReactions = self.availableReactions else {
return
}
self.isApplyingSettings = true
self.state?.updated(transition: .immediate)
self.applyDisposable?.dispose()
let allowedReactions: PeerAllowedReactions
if self.isEnabled {
if Set(availableReactions.reactions.map(\.value)) == Set(enabledReactions.map(\.reaction)) {
allowedReactions = .all
} else {
allowedReactions = .limited(enabledReactions.map(\.reaction))
}
} else {
allowedReactions = .empty
}
self.applyDisposable = (component.context.engine.peers.updatePeerAllowedReactions(peerId: component.peerId, allowedReactions: allowedReactions)
|> deliverOnMainQueue).start(error: { [weak self] error in
guard let self, let component = self.component else {
return
}
self.isApplyingSettings = false
self.state?.updated(transition: .immediate)
switch error {
case .boostRequired:
let _ = combineLatest(
queue: Queue.mainQueue(),
component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId)),
component.context.engine.peers.getChannelBoostStatus(peerId: component.peerId)
).startStandalone(next: { [weak self] peer, status in
guard let self, let component = self.component, let peer, let status else {
return
}
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
let link = status.url
let controller = PremiumLimitScreen(context: component.context, subject: .storiesChannelBoost(peer: peer, boostSubject: .channelReactions, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: { [weak self] in
guard let self, let component = self.component else {
return true
}
UIPasteboard.general.string = link
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current)
return true
}, openStats: nil, openGift: premiumConfiguration.giveawayGiftsPurchaseAvailable ? { [weak self] in
guard let self, let component = self.component else {
return
}
let controller = createGiveawayController(context: component.context, peerId: component.peerId, subject: .generic)
self.environment?.controller()?.push(controller)
} : nil)
self.environment?.controller()?.push(controller)
HapticFeedback().impact(.light)
})
case .generic:
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "An error occurred", actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}, completed: { [weak self] in
guard let self else {
return
}
self.environment?.controller()?.dismiss()
})
}
func update(component: PeerAllowedReactionsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
let environment = environment[EnvironmentType.self].value
let themeUpdated = self.environment?.theme !== environment.theme
self.environment = environment
self.component = component
self.state = state
let topInset: CGFloat = 24.0
let bottomInset: CGFloat = 8.0
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 16.0
let enabledReactions: [EmojiComponentReactionItem]
if let current = self.enabledReactions {
enabledReactions = current
} else {
enabledReactions = component.initialContent.enabledReactions
self.enabledReactions = enabledReactions
self.availableReactions = component.initialContent.availableReactions
self.isEnabled = component.initialContent.isEnabled
}
if self.emojiContentDisposable == nil {
let emojiContent = EmojiPagerContentComponent.emojiInputData(
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
isStandalone: false,
subject: .reactionList,
hasTrending: false,
topReactionItems: [],
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,
chatPeerId: nil,
selectedItems: Set(),
backgroundIconColor: nil,
hasSearch: false,
forceHasPremium: true
)
self.emojiContentDisposable = (emojiContent
|> deliverOnMainQueue).start(next: { [weak self] emojiContent in
guard let self else {
return
}
self.emojiContent = emojiContent
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] _, item, _, _, _, _ in
guard let self, var enabledReactions = self.enabledReactions else {
return
}
if self.isApplyingSettings {
return
}
guard let itemFile = item.itemFile else {
return
}
if let index = enabledReactions.firstIndex(where: { $0.file.fileId.id == itemFile.fileId.id }) {
enabledReactions.remove(at: index)
} else {
let reaction: MessageReaction.Reaction
if let availableReactions = self.availableReactions, let reactionItem = availableReactions.reactions.first(where: { $0.selectAnimation.fileId.id == itemFile.fileId.id }) {
reaction = reactionItem.value
} else {
reaction = .custom(itemFile.fileId.id)
}
enabledReactions.append(EmojiComponentReactionItem(reaction: reaction, file: itemFile))
}
self.enabledReactions = enabledReactions
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
},
deleteBackwards: {
},
openStickerSettings: {
},
openFeatured: {
},
openSearch: {
},
addGroupAction: { _, _, _ in
},
clearGroup: { _ in
},
pushController: { c in
},
presentController: { c in
},
presentGlobalOverlayController: { c in
},
navigationController: {
return nil
},
requestUpdate: { _ in
},
updateSearchQuery: { _ in
},
updateScrollingToItemGroup: {
},
onScroll: {},
chatPeerId: nil,
peekBehavior: nil,
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
customContentView: nil,
useOpaqueTheme: true,
hideBackground: false,
stateContext: nil,
addImage: nil
)
if !self.isUpdating {
self.state?.updated(transition: .immediate)
}
})
}
if themeUpdated {
self.backgroundColor = environment.theme.list.blocksBackgroundColor
}
var contentHeight: CGFloat = 0.0
contentHeight += environment.navigationHeight
contentHeight += topInset
let switchSize = self.switchItem.update(
transition: transition,
component: AnyComponent(ListSwitchItemComponent(
theme: environment.theme,
title: environment.strings.PeerInfo_AllowedReactions_AllowAllText,
value: true,
valueUpdated: { [weak self] value in
guard let self else {
return
}
if self.isEnabled != value {
self.isEnabled = value
if self.isEnabled {
if var enabledReactions = self.enabledReactions, enabledReactions.isEmpty {
if let availableReactions = self.availableReactions {
for reactionItem in availableReactions.reactions {
enabledReactions.append(EmojiComponentReactionItem(reaction: reactionItem.value, file: reactionItem.selectAnimation))
}
}
self.enabledReactions = enabledReactions
}
} else {
self.displayInput = false
}
self.state?.updated(transition: .easeInOut(duration: 0.25))
}
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)
)
let switchFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: switchSize)
if let switchView = self.switchItem.view {
if switchView.superview == nil {
self.scrollView.addSubview(switchView)
}
transition.setFrame(view: switchView, frame: switchFrame)
}
contentHeight += switchSize.height
contentHeight += 7.0
//TODO:localize
let switchInfoTextSize = self.switchInfoText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "You can add emoji from any emoji pack as a reaction.",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - textSideInset * 2.0, height: .greatestFiniteMagnitude)
)
let switchInfoTextFrame = CGRect(origin: CGPoint(x: sideInset + textSideInset, y: contentHeight), size: switchInfoTextSize)
if let switchInfoTextView = self.switchInfoText.view {
if switchInfoTextView.superview == nil {
switchInfoTextView.layer.anchorPoint = CGPoint()
self.scrollView.addSubview(switchInfoTextView)
}
transition.setPosition(view: switchInfoTextView, position: switchInfoTextFrame.origin)
switchInfoTextView.bounds = CGRect(origin: CGPoint(), size: switchInfoTextFrame.size)
}
contentHeight += switchInfoTextSize.height
contentHeight += 37.0
if self.isEnabled {
var animateIn = false
let reactionsTitleText: ComponentView<Empty>
if let current = self.reactionsTitleText {
reactionsTitleText = current
} else {
reactionsTitleText = ComponentView()
self.reactionsTitleText = reactionsTitleText
animateIn = true
}
//TODO:localize
let reactionsTitleTextSize = reactionsTitleText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "AVAILABLE REACTIONS",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - textSideInset * 2.0, height: .greatestFiniteMagnitude)
)
let reactionsTitleTextFrame = CGRect(origin: CGPoint(x: sideInset + textSideInset, y: contentHeight), size: reactionsTitleTextSize)
if let reactionsTitleTextView = reactionsTitleText.view {
if reactionsTitleTextView.superview == nil {
reactionsTitleTextView.layer.anchorPoint = CGPoint()
self.scrollView.addSubview(reactionsTitleTextView)
}
if animateIn {
reactionsTitleTextView.frame = reactionsTitleTextFrame
if !transition.animation.isImmediate {
reactionsTitleTextView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
} else {
transition.setPosition(view: reactionsTitleTextView, position: reactionsTitleTextFrame.origin)
reactionsTitleTextView.bounds = CGRect(origin: CGPoint(), size: reactionsTitleTextFrame.size)
}
}
contentHeight += reactionsTitleTextSize.height
contentHeight += 6.0
let reactionInput: ComponentView<Empty>
if let current = self.reactionInput {
reactionInput = current
} else {
reactionInput = ComponentView()
self.reactionInput = reactionInput
}
//TOOD:localize
let reactionInputSize = reactionInput.update(
transition: animateIn ? .immediate : transition,
component: AnyComponent(EmojiListInputComponent(
context: component.context,
theme: environment.theme,
placeholder: "Add Reactions...",
reactionItems: enabledReactions,
isInputActive: self.displayInput,
activateInput: { [weak self] in
guard let self else {
return
}
if self.emojiContent != nil && !self.displayInput {
self.displayInput = true
self.state?.updated(transition: .spring(duration: 0.5))
}
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)
)
let reactionInputFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: reactionInputSize)
if let reactionInputView = reactionInput.view {
if reactionInputView.superview == nil {
self.scrollView.addSubview(reactionInputView)
}
if animateIn {
reactionInputView.frame = reactionInputFrame
if !transition.animation.isImmediate {
reactionInputView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
} else {
transition.setFrame(view: reactionInputView, frame: reactionInputFrame)
}
}
contentHeight += reactionInputSize.height
contentHeight += 7.0
let reactionsInfoText: ComponentView<Empty>
if let current = self.reactionsInfoText {
reactionsInfoText = current
} else {
reactionsInfoText = ComponentView()
self.reactionsInfoText = reactionsInfoText
}
//TODO:localize
let body = MarkdownAttributeSet(font: UIFont.systemFont(ofSize: 13.0), textColor: environment.theme.list.freeTextColor)
let link = MarkdownAttributeSet(font: UIFont.systemFont(ofSize: 13.0), textColor: environment.theme.list.itemAccentColor, additionalAttributes: ["URL": true as NSNumber])
let attributes = MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in
return nil
})
let reactionsInfoTextSize = reactionsInfoText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .markdown(text: "You can also [create your own]() emoji packs and use them.", attributes: attributes),
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - textSideInset * 2.0, height: .greatestFiniteMagnitude)
)
let reactionsInfoTextFrame = CGRect(origin: CGPoint(x: sideInset + textSideInset, y: contentHeight), size: reactionsInfoTextSize)
if let reactionsInfoTextView = reactionsInfoText.view {
if reactionsInfoTextView.superview == nil {
reactionsInfoTextView.layer.anchorPoint = CGPoint()
self.scrollView.addSubview(reactionsInfoTextView)
}
if animateIn {
reactionsInfoTextView.frame = reactionsInfoTextFrame
if !transition.animation.isImmediate {
reactionsInfoTextView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
} else {
transition.setPosition(view: reactionsInfoTextView, position: reactionsInfoTextFrame.origin)
reactionsInfoTextView.bounds = CGRect(origin: CGPoint(), size: reactionsInfoTextFrame.size)
}
}
contentHeight += reactionsInfoTextSize.height
contentHeight += 6.0
} else {
if let reactionsTitleText = self.reactionsTitleText {
self.reactionsTitleText = nil
if let reactionsTitleTextView = reactionsTitleText.view {
if !transition.animation.isImmediate {
reactionsTitleTextView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionsTitleTextView] _ in
reactionsTitleTextView?.removeFromSuperview()
})
} else {
reactionsTitleTextView.removeFromSuperview()
}
}
}
if let reactionInput = self.reactionInput {
self.reactionInput = nil
if let reactionInputView = reactionInput.view {
if !transition.animation.isImmediate {
reactionInputView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionInputView] _ in
reactionInputView?.removeFromSuperview()
})
} else {
reactionInputView.removeFromSuperview()
}
}
}
if let reactionsInfoText = self.reactionsInfoText {
self.reactionsInfoText = nil
if let reactionsInfoTextView = reactionsInfoText.view {
if !transition.animation.isImmediate {
reactionsInfoTextView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionsInfoTextView] _ in
reactionsInfoTextView?.removeFromSuperview()
})
} else {
reactionsInfoTextView.removeFromSuperview()
}
}
}
}
//TODO:localize
var buttonContents: [AnyComponentWithIdentity<Empty>] = []
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
Text(text: "Update Reactions", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
)))
/*if self.remainingTimer > 0 {
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(
AnimatedTextComponent(font: Font.with(size: 17.0, weight: .semibold, traits: .monospacedNumbers), color: environment.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.5), items: [
AnimatedTextComponent.Item(id: AnyHashable(0 as Int), content: .number(self.remainingTimer, minDigits: 0))
])
)))
}*/
let buttonSize = self.actionButton.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
),
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
HStack(buttonContents, spacing: 5.0)
)),
isEnabled: true,
tintWhenDisabled: false,
displaysProgress: self.isApplyingSettings,
action: { [weak self] in
guard let self else {
return
}
self.applySettings()
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
)
contentHeight += buttonSize.height
var inputHeight: CGFloat = 0.0
if self.displayInput, let emojiContent = self.emojiContent {
let reactionSelectionControl: ComponentView<Empty>
var animateIn = false
if let current = self.reactionSelectionControl {
reactionSelectionControl = current
} else {
animateIn = true
reactionSelectionControl = ComponentView()
self.reactionSelectionControl = reactionSelectionControl
}
let reactionSelectionControlSize = reactionSelectionControl.update(
transition: animateIn ? .immediate : transition,
component: AnyComponent(EmojiSelectionComponent(
theme: environment.theme,
strings: environment.strings,
sideInset: environment.safeInsets.left,
bottomInset: environment.safeInsets.bottom,
deviceMetrics: environment.deviceMetrics,
emojiContent: emojiContent.withSelectedItems(Set(enabledReactions.map(\.file.fileId))),
backgroundIconColor: nil,
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
separatorColor: environment.theme.list.itemBlocksSeparatorColor)
),
environment: {},
containerSize: CGSize(width: availableSize.width, height: min(340.0, max(50.0, availableSize.height - 200.0)))
)
let reactionSelectionControlFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - reactionSelectionControlSize.height), size: reactionSelectionControlSize)
if let reactionSelectionControlView = reactionSelectionControl.view {
if reactionSelectionControlView.superview == nil {
self.addSubview(reactionSelectionControlView)
}
if animateIn {
reactionSelectionControlView.frame = reactionSelectionControlFrame
transition.animatePosition(view: reactionSelectionControlView, from: CGPoint(x: 0.0, y: reactionSelectionControlFrame.height), to: CGPoint(), additive: true)
} else {
transition.setFrame(view: reactionSelectionControlView, frame: reactionSelectionControlFrame)
}
}
inputHeight = reactionSelectionControlSize.height
} else if let reactionSelectionControl = self.reactionSelectionControl {
self.reactionSelectionControl = nil
if let reactionSelectionControlView = reactionSelectionControl.view {
transition.setPosition(view: reactionSelectionControlView, position: CGPoint(x: reactionSelectionControlView.center.x, y: availableSize.height + reactionSelectionControlView.bounds.height * 0.5), completion: { [weak reactionSelectionControlView] _ in
reactionSelectionControlView?.removeFromSuperview()
})
}
}
let buttonY: CGFloat
if self.displayInput {
contentHeight += bottomInset + 8.0
contentHeight += inputHeight
buttonY = availableSize.height - bottomInset - 8.0 - inputHeight - buttonSize.height
} else {
contentHeight += bottomInset
contentHeight += environment.safeInsets.bottom
buttonY = availableSize.height - bottomInset - environment.safeInsets.bottom - buttonSize.height
}
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: buttonY), size: buttonSize)
if let buttonView = self.actionButton.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
transition.setFrame(view: buttonView, frame: buttonFrame)
}
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize)
}
if self.scrollView.contentSize != contentSize {
self.scrollView.contentSize = contentSize
}
let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0)
if self.scrollView.scrollIndicatorInsets != scrollInsets {
self.scrollView.scrollIndicatorInsets = scrollInsets
}
self.updateScrolling(transition: transition)
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public class PeerAllowedReactionsScreen: ViewControllerComponentContainer {
public final class Content: Equatable {
public let isEnabled: Bool
public let enabledReactions: [EmojiComponentReactionItem]
public let availableReactions: AvailableReactions?
init(
isEnabled: Bool,
enabledReactions: [EmojiComponentReactionItem],
availableReactions: AvailableReactions?
) {
self.isEnabled = isEnabled
self.enabledReactions = enabledReactions
self.availableReactions = availableReactions
}
public static func ==(lhs: Content, rhs: Content) -> Bool {
if lhs === rhs {
return true
}
if lhs.isEnabled != rhs.isEnabled {
return false
}
if lhs.enabledReactions != rhs.enabledReactions {
return false
}
if lhs.availableReactions != rhs.availableReactions {
return false
}
return true
}
}
private let context: AccountContext
private var isDismissed: Bool = false
public init(
context: AccountContext,
peerId: EnginePeer.Id,
initialContent: Content
) {
self.context = context
super.init(context: context, component: PeerAllowedReactionsScreenComponent(
context: context,
peerId: peerId,
initialContent: initialContent
), navigationBarAppearance: .default, theme: .default)
self.scrollToTop = { [weak self] in
guard let self, let componentView = self.node.hostView.componentView as? PeerAllowedReactionsScreenComponent.View else {
return
}
componentView.scrollToTop()
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.title = presentationData.strings.PeerInfo_AllowedReactions_Title
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
@objc private func cancelPressed() {
self.dismiss()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
}
public static func content(context: AccountContext, peerId: EnginePeer.Id) -> Signal<Content, NoError> {
return combineLatest(
context.engine.stickers.availableReactions(),
context.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)])
)
|> mapToSignal { availableReactions, combinedView -> Signal<Content, NoError> in
guard let cachedDataView = combinedView.views[.cachedPeerData(peerId: peerId)] as? CachedPeerDataView, let cachedData = cachedDataView.cachedPeerData as? CachedChannelData else {
return .complete()
}
var reactions: [MessageReaction.Reaction] = []
var isEnabled = false
if let allowedReactions = cachedData.allowedReactions.knownValue {
switch allowedReactions {
case .all:
isEnabled = true
if let availableReactions {
reactions = availableReactions.reactions.map(\.value)
}
case let .limited(list):
isEnabled = true
reactions.append(contentsOf: list)
case .empty:
isEnabled = false
}
}
var missingReactionFiles: [Int64] = []
for reaction in reactions {
if let availableReactions, let _ = availableReactions.reactions.first(where: { $0.value == reaction }) {
} else {
if case let .custom(fileId) = reaction {
if !missingReactionFiles.contains(fileId) {
missingReactionFiles.append(fileId)
}
}
}
}
return context.engine.stickers.resolveInlineStickers(fileIds: missingReactionFiles)
|> map { files -> Content in
var result: [EmojiComponentReactionItem] = []
for reaction in reactions {
if let availableReactions, let item = availableReactions.reactions.first(where: { $0.value == reaction }) {
result.append(EmojiComponentReactionItem(reaction: reaction, file: item.selectAnimation))
} else {
if case let .custom(fileId) = reaction {
if let file = files[fileId] {
result.append(EmojiComponentReactionItem(reaction: reaction, file: file))
}
}
}
}
return Content(isEnabled: isEnabled, enabledReactions: result, availableReactions: availableReactions)
}
}
|> distinctUntilChanged
}
}