mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Various improvements
This commit is contained in:
@@ -20,6 +20,8 @@ import MediaEditor
|
||||
import EntityKeyboardGifContent
|
||||
import CameraButtonComponent
|
||||
import BundleIconComponent
|
||||
import LottieComponent
|
||||
import LottieComponentResourceContent
|
||||
import UndoUI
|
||||
import GalleryUI
|
||||
|
||||
@@ -532,7 +534,10 @@ public class StickerPickerScreen: ViewController {
|
||||
self.containerView.addSubview(self.hostView)
|
||||
|
||||
if controller.hasInteractiveStickers {
|
||||
self.storyStickersContentView = StoryStickersContentView(isPremium: context.isPremium)
|
||||
self.storyStickersContentView = StoryStickersContentView(
|
||||
context: context,
|
||||
weather: controller.weather
|
||||
)
|
||||
self.storyStickersContentView?.locationAction = { [weak self] in
|
||||
self?.controller?.presentLocationPicker()
|
||||
}
|
||||
@@ -2044,15 +2049,34 @@ public class StickerPickerScreen: ViewController {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
public enum Weather {
|
||||
public struct LoadedWeather {
|
||||
public let emoji: String
|
||||
public let emojiFile: TelegramMediaFile
|
||||
public let temperature: Double
|
||||
|
||||
public init(emoji: String, emojiFile: TelegramMediaFile, temperature: Double) {
|
||||
self.emoji = emoji
|
||||
self.emojiFile = emojiFile
|
||||
self.temperature = temperature
|
||||
}
|
||||
}
|
||||
|
||||
case none
|
||||
case fetching
|
||||
case loaded(StickerPickerScreen.Weather.LoadedWeather)
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let theme: PresentationTheme
|
||||
fileprivate let forceDark: Bool
|
||||
let forceDark: Bool
|
||||
private let inputData: Signal<StickerPickerInput, NoError>
|
||||
fileprivate let defaultToEmoji: Bool
|
||||
let defaultToEmoji: Bool
|
||||
let isFullscreen: Bool
|
||||
let hasEmoji: Bool
|
||||
let hasGifs: Bool
|
||||
let hasInteractiveStickers: Bool
|
||||
let weather: Signal<StickerPickerScreen.Weather, NoError>
|
||||
|
||||
private var currentLayout: ContainerViewLayout?
|
||||
|
||||
@@ -2068,7 +2092,7 @@ public class StickerPickerScreen: ViewController {
|
||||
public var addLink: () -> Void = { }
|
||||
public var addWeather: () -> Void = { }
|
||||
|
||||
public init(context: AccountContext, inputData: Signal<StickerPickerInput, NoError>, forceDark: Bool = false, expanded: Bool = false, defaultToEmoji: Bool = false, hasEmoji: Bool = true, hasGifs: Bool = false, hasInteractiveStickers: Bool = true) {
|
||||
public init(context: AccountContext, inputData: Signal<StickerPickerInput, NoError>, forceDark: Bool = false, expanded: Bool = false, defaultToEmoji: Bool = false, hasEmoji: Bool = true, hasGifs: Bool = false, hasInteractiveStickers: Bool = true, weather: Signal<StickerPickerScreen.Weather, NoError> = .single(.none)) {
|
||||
self.context = context
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.theme = forceDark ? defaultDarkColorPresentationTheme : presentationData.theme
|
||||
@@ -2079,6 +2103,7 @@ public class StickerPickerScreen: ViewController {
|
||||
self.hasEmoji = hasEmoji
|
||||
self.hasGifs = hasGifs
|
||||
self.hasInteractiveStickers = hasInteractiveStickers
|
||||
self.weather = weather
|
||||
|
||||
super.init(navigationBarPresentationData: expanded ? NavigationBarPresentationData(presentationData: presentationData) : nil)
|
||||
|
||||
@@ -2141,22 +2166,28 @@ public class StickerPickerScreen: ViewController {
|
||||
}
|
||||
|
||||
private final class InteractiveStickerButtonContent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let iconName: String
|
||||
let title: String?
|
||||
let iconName: String?
|
||||
let iconFile: TelegramMediaFile?
|
||||
let useOpaqueTheme: Bool
|
||||
weak var tintContainerView: UIView?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
iconName: String,
|
||||
title: String?,
|
||||
iconName: String?,
|
||||
iconFile: TelegramMediaFile? = nil,
|
||||
useOpaqueTheme: Bool,
|
||||
tintContainerView: UIView
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.iconName = iconName
|
||||
self.iconFile = iconFile
|
||||
self.useOpaqueTheme = useOpaqueTheme
|
||||
self.tintContainerView = tintContainerView
|
||||
}
|
||||
@@ -2207,68 +2238,80 @@ private final class InteractiveStickerButtonContent: Component {
|
||||
|
||||
func update(component: InteractiveStickerButtonContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.backgroundLayer.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.11).cgColor
|
||||
|
||||
|
||||
let iconSize: CGSize
|
||||
if component.iconName == "Sun" {
|
||||
iconSize = self.icon.update(
|
||||
let buttonSize: CGSize
|
||||
if let title = component.title {
|
||||
if let iconFile = component.iconFile {
|
||||
iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.ResourceContent(context: component.context, file: iconFile, attemptSynchronously: true, providesPlaceholder: true),
|
||||
color: nil,
|
||||
placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.4),
|
||||
loop: !["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"].contains(component.iconName ?? "")
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 20.0, height: 20.0)
|
||||
)
|
||||
} else {
|
||||
iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(
|
||||
name: component.iconName ?? "",
|
||||
tintColor: .white,
|
||||
maxSize: CGSize(width: 20.0, height: 20.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
}
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(
|
||||
text: "☀️",
|
||||
text: title.uppercased(),
|
||||
font: Font.with(size: 23.0, design: .camera),
|
||||
color: .white
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let padding: CGFloat = 7.0
|
||||
let spacing: CGFloat = 4.0
|
||||
buttonSize = CGSize(width: padding + iconSize.width + spacing + titleSize.width + padding, height: 34.0)
|
||||
|
||||
if let view = self.icon.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding, y: floorToScreenPixels((buttonSize.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
}
|
||||
if let view = self.title.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding + iconSize.width + spacing, y: floorToScreenPixels((buttonSize.height - titleSize.height) / 2.0)), size: titleSize))
|
||||
}
|
||||
} else {
|
||||
iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(
|
||||
name: component.iconName,
|
||||
tintColor: .white,
|
||||
maxSize: CGSize(width: 20.0, height: 20.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
}
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(
|
||||
text: component.title.uppercased(),
|
||||
font: Font.with(size: 23.0, design: .camera),
|
||||
color: .white
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let padding: CGFloat = 7.0
|
||||
let spacing: CGFloat = 4.0
|
||||
let buttonSize = CGSize(width: padding + iconSize.width + spacing + titleSize.width + padding, height: 34.0)
|
||||
|
||||
if let view = self.icon.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding, y: floorToScreenPixels((buttonSize.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
}
|
||||
if let view = self.title.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: padding + iconSize.width + spacing, y: floorToScreenPixels((buttonSize.height - titleSize.height) / 2.0)), size: titleSize))
|
||||
buttonSize = CGSize(width: 87.0, height: 34.0)
|
||||
}
|
||||
|
||||
self.backgroundLayer.cornerRadius = 6.0
|
||||
self.tintBackgroundLayer.cornerRadius = 6.0
|
||||
|
||||
self.backgroundLayer.frame = CGRect(origin: .zero, size: buttonSize)
|
||||
var transition = transition
|
||||
if self.backgroundLayer.frame.width.isZero {
|
||||
transition = .immediate
|
||||
}
|
||||
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: buttonSize))
|
||||
|
||||
if self.tintBackgroundLayer.superlayer == nil, let tintContainerView = component.tintContainerView {
|
||||
Queue.mainQueue().justDispatch {
|
||||
let mappedFrame = self.convert(self.bounds, to: tintContainerView)
|
||||
self.tintBackgroundLayer.frame = mappedFrame
|
||||
transition.setFrame(layer: self.tintBackgroundLayer, frame: mappedFrame)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2441,12 +2484,14 @@ final class ItemStack<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
private let padding: CGFloat
|
||||
private let minSpacing: CGFloat
|
||||
private let verticalSpacing: CGFloat
|
||||
private let maxHorizontalItems: Int
|
||||
|
||||
init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], padding: CGFloat, minSpacing: CGFloat, verticalSpacing: CGFloat) {
|
||||
init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], padding: CGFloat, minSpacing: CGFloat, verticalSpacing: CGFloat, maxHorizontalItems: Int) {
|
||||
self.items = items
|
||||
self.padding = padding
|
||||
self.minSpacing = minSpacing
|
||||
self.verticalSpacing = verticalSpacing
|
||||
self.maxHorizontalItems = maxHorizontalItems
|
||||
}
|
||||
|
||||
static func ==(lhs: ItemStack<ChildEnvironment>, rhs: ItemStack<ChildEnvironment>) -> Bool {
|
||||
@@ -2462,6 +2507,9 @@ final class ItemStack<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
if lhs.verticalSpacing != rhs.verticalSpacing {
|
||||
return false
|
||||
}
|
||||
if lhs.maxHorizontalItems != rhs.maxHorizontalItems {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2491,7 +2539,7 @@ final class ItemStack<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
|
||||
let remainingWidth = context.availableSize.width - itemsWidth - context.component.padding * 2.0
|
||||
let spacing = remainingWidth / CGFloat(rowItemsCount - 1)
|
||||
if spacing < context.component.minSpacing || currentGroup.count == 3 {
|
||||
if spacing < context.component.minSpacing || currentGroup.count == context.component.maxHorizontalItems {
|
||||
groups.append(currentGroup)
|
||||
currentGroup = []
|
||||
}
|
||||
@@ -2545,148 +2593,233 @@ final class ItemStack<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
}
|
||||
|
||||
final class StoryStickersContentView: UIView, EmojiCustomContentView {
|
||||
private let context: AccountContext
|
||||
private let weatherFormatter: MeasurementFormatter
|
||||
|
||||
let tintContainerView = UIView()
|
||||
|
||||
private let container = ComponentView<Empty>()
|
||||
|
||||
private let isPremium: Bool
|
||||
private var weatherDisposable: Disposable?
|
||||
private var weather: StickerPickerScreen.Weather = .none
|
||||
|
||||
var locationAction: () -> Void = {}
|
||||
var audioAction: () -> Void = {}
|
||||
var reactionAction: () -> Void = {}
|
||||
var linkAction: () -> Void = {}
|
||||
var weatherAction: () -> Void = {}
|
||||
|
||||
private var params: (theme: PresentationTheme, strings: PresentationStrings, useOpaqueTheme: Bool, availableSize: CGSize)?
|
||||
|
||||
init(isPremium: Bool) {
|
||||
self.isPremium = isPremium
|
||||
init(context: AccountContext, weather: Signal<StickerPickerScreen.Weather, NoError>) {
|
||||
self.context = context
|
||||
self.weatherFormatter = MeasurementFormatter()
|
||||
self.weatherFormatter.locale = Locale.current
|
||||
self.weatherFormatter.unitStyle = .short
|
||||
self.weatherFormatter.numberFormatter.maximumFractionDigits = 0
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.weatherDisposable = (weather
|
||||
|> deliverOnMainQueue).start(next: { [weak self] weather in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.weather = weather
|
||||
if let (theme, strings, useOpaqueTheme, availableSize) = self.params {
|
||||
let _ = self.update(theme: theme, strings: strings, useOpaqueTheme: useOpaqueTheme, availableSize: availableSize, transition: .easeInOut(duration: 0.25))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.weatherDisposable?.dispose()
|
||||
}
|
||||
|
||||
func update(theme: PresentationTheme, strings: PresentationStrings, useOpaqueTheme: Bool, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||
self.params = (theme, strings, useOpaqueTheme, availableSize)
|
||||
|
||||
let padding: CGFloat = 22.0
|
||||
var maxHorizontalItems = 2
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "link",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "content",
|
||||
component: AnyComponent(
|
||||
InteractiveStickerButtonContent(
|
||||
context: self.context,
|
||||
theme: theme,
|
||||
title: strings.MediaEditor_AddLink,
|
||||
iconName: self.context.isPremium ? "Media Editor/Link" : "Media Editor/LinkLocked",
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
tintContainerView: self.tintContainerView
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.linkAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "location",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "content",
|
||||
component: AnyComponent(
|
||||
InteractiveStickerButtonContent(
|
||||
context: self.context,
|
||||
theme: theme,
|
||||
title: strings.MediaEditor_AddLocationShort,
|
||||
iconName: "Chat/Attach Menu/Location",
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
tintContainerView: self.tintContainerView
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.locationAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if case .none = self.weather {
|
||||
|
||||
} else {
|
||||
maxHorizontalItems = 3
|
||||
|
||||
switch self.weather {
|
||||
case let .loaded(weather):
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "weather",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "weather",
|
||||
component: AnyComponent(
|
||||
InteractiveStickerButtonContent(
|
||||
context: self.context,
|
||||
theme: theme,
|
||||
title: self.weatherFormatter.string(from: Measurement(value: weather.temperature, unit: UnitTemperature.celsius)),
|
||||
iconName: weather.emoji,
|
||||
iconFile: weather.emojiFile,
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
tintContainerView: self.tintContainerView
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.weatherAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
case .fetching:
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "weather",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "weather",
|
||||
component: AnyComponent(
|
||||
InteractiveStickerButtonContent(
|
||||
context: self.context,
|
||||
theme: theme,
|
||||
title: nil,
|
||||
iconName: nil,
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
tintContainerView: self.tintContainerView
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.weatherAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "audio",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "audio",
|
||||
component: AnyComponent(
|
||||
InteractiveStickerButtonContent(
|
||||
context: self.context,
|
||||
theme: theme,
|
||||
title: strings.MediaEditor_AddAudio,
|
||||
iconName: "Media Editor/Audio",
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
tintContainerView: self.tintContainerView
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.audioAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "reaction",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "reaction",
|
||||
component: AnyComponent(
|
||||
InteractiveReactionButtonContent(theme: theme)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.reactionAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
let size = self.container.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
ItemStack(
|
||||
[
|
||||
AnyComponentWithIdentity(
|
||||
id: "link",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "content",
|
||||
component: AnyComponent(
|
||||
InteractiveStickerButtonContent(
|
||||
theme: theme,
|
||||
title: strings.MediaEditor_AddLink,
|
||||
iconName: self.isPremium ? "Media Editor/Link" : "Media Editor/LinkLocked",
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
tintContainerView: self.tintContainerView
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.linkAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "location",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "content",
|
||||
component: AnyComponent(
|
||||
InteractiveStickerButtonContent(
|
||||
theme: theme,
|
||||
title: strings.MediaEditor_AddLocationShort,
|
||||
iconName: "Chat/Attach Menu/Location",
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
tintContainerView: self.tintContainerView
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.locationAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "weather",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "weather",
|
||||
component: AnyComponent(
|
||||
InteractiveStickerButtonContent(
|
||||
theme: theme,
|
||||
title: "35°C",
|
||||
iconName: "Sun",
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
tintContainerView: self.tintContainerView
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.weatherAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "audio",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "audio",
|
||||
component: AnyComponent(
|
||||
InteractiveStickerButtonContent(
|
||||
theme: theme,
|
||||
title: strings.MediaEditor_AddAudio,
|
||||
iconName: "Media Editor/Audio",
|
||||
useOpaqueTheme: useOpaqueTheme,
|
||||
tintContainerView: self.tintContainerView
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.audioAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "reaction",
|
||||
component: AnyComponent(
|
||||
CameraButton(
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "reaction",
|
||||
component: AnyComponent(
|
||||
InteractiveReactionButtonContent(theme: theme)
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.reactionAction()
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
],
|
||||
items,
|
||||
padding: 18.0,
|
||||
minSpacing: 8.0,
|
||||
verticalSpacing: 12.0
|
||||
verticalSpacing: 12.0,
|
||||
maxHorizontalItems: maxHorizontalItems
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
|
||||
Reference in New Issue
Block a user