mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-20 10:11:10 +00:00
Merge commit '40780242fe8ef168b115e9b2b47914faecf3dea5'
This commit is contained in:
commit
23e52bc1f7
@ -850,7 +850,7 @@ final class ComposePollScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if self.pollOptions.count < 10, let lastOption = self.pollOptions.last {
|
||||
if self.pollOptions.count < component.initialData.maxPollAnswersCount, let lastOption = self.pollOptions.last {
|
||||
if lastOption.textInputState.text.length != 0 {
|
||||
self.pollOptions.append(PollOption(id: self.nextPollOptionId))
|
||||
self.nextPollOptionId += 1
|
||||
@ -921,7 +921,7 @@ final class ComposePollScreenComponent: Component {
|
||||
|
||||
contentHeight += 7.0
|
||||
|
||||
let pollOptionsLimitReached = self.pollOptions.count >= 10
|
||||
let pollOptionsLimitReached = self.pollOptions.count >= component.initialData.maxPollAnswersCount
|
||||
var animatePollOptionsFooterIn = false
|
||||
var pollOptionsFooterTransition = transition
|
||||
if self.currentPollOptionsLimitReached != pollOptionsLimitReached {
|
||||
@ -944,7 +944,7 @@ final class ComposePollScreenComponent: Component {
|
||||
maximumNumberOfLines: 0
|
||||
))
|
||||
} else {
|
||||
let remainingCount = 10 - self.pollOptions.count
|
||||
let remainingCount = component.initialData.maxPollAnswersCount - self.pollOptions.count
|
||||
let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount))
|
||||
|
||||
var pollOptionsFooterItems: [AnimatedTextComponent.Item] = []
|
||||
@ -1476,13 +1476,16 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
|
||||
public final class InitialData {
|
||||
fileprivate let maxPollTextLength: Int
|
||||
fileprivate let maxPollOptionLength: Int
|
||||
fileprivate let maxPollAnswersCount: Int
|
||||
|
||||
fileprivate init(
|
||||
maxPollTextLength: Int,
|
||||
maxPollOptionLength: Int
|
||||
maxPollOptionLength: Int,
|
||||
maxPollAnwsersCount: Int
|
||||
) {
|
||||
self.maxPollTextLength = maxPollTextLength
|
||||
self.maxPollOptionLength = maxPollOptionLength
|
||||
self.maxPollAnswersCount = maxPollAnwsersCount
|
||||
}
|
||||
}
|
||||
|
||||
@ -1577,9 +1580,14 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont
|
||||
}
|
||||
|
||||
public static func initialData(context: AccountContext) -> InitialData {
|
||||
var maxPollAnwsersCount: Int = 10
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["poll_answers_max"] as? Double {
|
||||
maxPollAnwsersCount = Int(value)
|
||||
}
|
||||
return InitialData(
|
||||
maxPollTextLength: Int(200),
|
||||
maxPollOptionLength: 100
|
||||
maxPollOptionLength: 100,
|
||||
maxPollAnwsersCount: maxPollAnwsersCount
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -426,7 +426,9 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
let text = strings.Contacts_PermissionsText
|
||||
switch authorizationStatus {
|
||||
case .limited:
|
||||
if displaySortOptions {
|
||||
entries.append(.permissionLimited(theme, strings))
|
||||
}
|
||||
case .denied:
|
||||
entries.append(.permissionInfo(theme, title, text, suppressed))
|
||||
entries.append(.permissionEnable(theme, strings.Permissions_ContactsAllowInSettings_v0))
|
||||
|
@ -116,6 +116,10 @@ public final class PresentationData: Equatable {
|
||||
return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
|
||||
}
|
||||
|
||||
public func withUpdate(listsFontSize: PresentationFontSize) -> PresentationData {
|
||||
return PresentationData(strings: self.strings, theme: self.theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, reduceMotion: self.reduceMotion, largeEmoji: self.largeEmoji)
|
||||
}
|
||||
|
||||
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool {
|
||||
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.autoNightModeTriggered == rhs.autoNightModeTriggered && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatFontSize == rhs.chatFontSize && lhs.chatBubbleCorners == rhs.chatBubbleCorners && lhs.listsFontSize == rhs.listsFontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.reduceMotion == rhs.reduceMotion && lhs.largeEmoji == rhs.largeEmoji
|
||||
}
|
||||
|
@ -2,11 +2,13 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import PlainButtonComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
import LottieComponent
|
||||
|
||||
public final class FilterSelectorComponent: Component {
|
||||
public struct Colors: Equatable {
|
||||
@ -24,39 +26,45 @@ public final class FilterSelectorComponent: Component {
|
||||
|
||||
public struct Item: Equatable {
|
||||
public var id: AnyHashable
|
||||
public var index: Int
|
||||
public var iconName: String?
|
||||
public var title: String
|
||||
public var action: (UIView) -> Void
|
||||
|
||||
public init(
|
||||
id: AnyHashable,
|
||||
index: Int = 0,
|
||||
iconName: String? = nil,
|
||||
title: String,
|
||||
action: @escaping (UIView) -> Void
|
||||
) {
|
||||
self.id = id
|
||||
self.index = index
|
||||
self.iconName = iconName
|
||||
self.title = title
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||
return lhs.id == rhs.id && lhs.iconName == rhs.iconName && lhs.title == rhs.title
|
||||
return lhs.id == rhs.id && lhs.index == rhs.index && lhs.iconName == rhs.iconName && lhs.title == rhs.title
|
||||
}
|
||||
}
|
||||
|
||||
public let context: AccountContext?
|
||||
public let colors: Colors
|
||||
public let items: [Item]
|
||||
public let selectedItemId: AnyHashable?
|
||||
|
||||
public init(
|
||||
context: AccountContext? = nil,
|
||||
colors: Colors,
|
||||
items: [Item]
|
||||
items: [Item],
|
||||
selectedItemId: AnyHashable?
|
||||
) {
|
||||
self.context = context
|
||||
self.colors = colors
|
||||
self.items = items
|
||||
self.selectedItemId = selectedItemId
|
||||
}
|
||||
|
||||
public static func ==(lhs: FilterSelectorComponent, rhs: FilterSelectorComponent) -> Bool {
|
||||
@ -69,6 +77,9 @@ public final class FilterSelectorComponent: Component {
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedItemId != rhs.selectedItemId {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -150,15 +161,17 @@ public final class FilterSelectorComponent: Component {
|
||||
validIds.append(itemId)
|
||||
|
||||
let itemSize = itemView.title.update(
|
||||
transition: .immediate,
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(ItemComponent(
|
||||
context: component.context,
|
||||
index: item.index,
|
||||
iconName: item.iconName,
|
||||
text: item.title,
|
||||
font: itemFont,
|
||||
color: component.colors.foreground,
|
||||
backgroundColor: component.colors.background
|
||||
backgroundColor: component.colors.background,
|
||||
isSelected: itemId == component.selectedItemId
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
minSize: nil,
|
||||
@ -242,34 +255,43 @@ extension CGRect {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ItemComponent: CombinedComponent {
|
||||
private final class ItemComponent: Component {
|
||||
let context: AccountContext?
|
||||
let index: Int
|
||||
let iconName: String?
|
||||
let text: String
|
||||
let font: UIFont
|
||||
let color: UIColor
|
||||
let backgroundColor: UIColor
|
||||
let isSelected: Bool
|
||||
|
||||
init(
|
||||
context: AccountContext?,
|
||||
index: Int,
|
||||
iconName: String?,
|
||||
text: String,
|
||||
font: UIFont,
|
||||
color: UIColor,
|
||||
backgroundColor: UIColor
|
||||
backgroundColor: UIColor,
|
||||
isSelected: Bool
|
||||
) {
|
||||
self.context = context
|
||||
self.index = index
|
||||
self.iconName = iconName
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.backgroundColor = backgroundColor
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
|
||||
static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.index != rhs.index {
|
||||
return false
|
||||
}
|
||||
if lhs.iconName != rhs.iconName {
|
||||
return false
|
||||
}
|
||||
@ -285,80 +307,166 @@ private final class ItemComponent: CombinedComponent {
|
||||
if lhs.backgroundColor != rhs.backgroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.isSelected != rhs.isSelected {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let background = Child(RoundedRectangle.self)
|
||||
let title = Child(MultilineTextWithEntitiesComponent.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
public final class View: UIView {
|
||||
private var component: ItemComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
return { context in
|
||||
let component = context.component
|
||||
private let background = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
private let icon = ComponentView<Empty>()
|
||||
|
||||
let attributedTitle = NSMutableAttributedString(string: component.text, font: component.font, textColor: component.color)
|
||||
let range = (attributedTitle.string as NSString).range(of: "⭐️")
|
||||
if range.location != NSNotFound {
|
||||
attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
|
||||
private var isSelected = false
|
||||
private var iconName: String?
|
||||
|
||||
private let playOnce = ActionSlot<Void>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context?.animationCache,
|
||||
animationRenderer: component.context?.animationRenderer,
|
||||
placeholderColor: .white,
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var animateTitleInDirection: CGFloat?
|
||||
if let previousComponent, previousComponent.text != component.text, !transition.animation.isImmediate, let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = titleView.frame
|
||||
self.addSubview(snapshotView)
|
||||
|
||||
var direction: CGFloat = 1.0
|
||||
if previousComponent.index < component.index {
|
||||
direction = -1.0
|
||||
}
|
||||
|
||||
snapshotView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 6.0 * direction), duration: 0.2, removeOnCompletion: false, additive: true)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
snapshotView.removeFromSuperview()
|
||||
})
|
||||
|
||||
animateTitleInDirection = direction
|
||||
}
|
||||
|
||||
let attributedTitle = NSAttributedString(string: component.text, font: component.font, textColor: component.color)
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(attributedTitle)
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: component.iconName ?? "Item List/ExpandableSelectorArrows",
|
||||
tintColor: component.color,
|
||||
maxSize: component.iconName != nil ? CGSize(width: 22.0, height: 22.0) : nil
|
||||
),
|
||||
availableSize: CGSize(width: 100, height: 100),
|
||||
transition: .immediate
|
||||
let animationName = component.iconName ?? (component.isSelected ? "GiftFilterMenuOpen" : "GiftFilterMenuClose")
|
||||
let animationSize = component.iconName != nil ? CGSize(width: 22.0, height: 22.0) : CGSize(width: 10.0, height: 22.0)
|
||||
|
||||
let iconSize = self.icon.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: animationName),
|
||||
color: component.color,
|
||||
playOnce: self.playOnce
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 22.0, height: 22.0)
|
||||
)
|
||||
|
||||
var playAnimation = false
|
||||
if self.isSelected != component.isSelected || self.iconName != component.iconName {
|
||||
if let iconName = component.iconName {
|
||||
if component.isSelected {
|
||||
playAnimation = true
|
||||
} else if self.iconName != iconName {
|
||||
playAnimation = true
|
||||
}
|
||||
self.iconName = iconName
|
||||
} else {
|
||||
playAnimation = true
|
||||
}
|
||||
self.isSelected = component.isSelected
|
||||
}
|
||||
if playAnimation {
|
||||
self.playOnce.invoke(Void())
|
||||
}
|
||||
|
||||
let padding: CGFloat = 12.0
|
||||
var leftPadding = padding
|
||||
if let _ = component.iconName {
|
||||
leftPadding -= 4.0
|
||||
}
|
||||
let spacing: CGFloat = 4.0
|
||||
let totalWidth = title.size.width + icon.size.width + spacing
|
||||
let totalWidth = titleSize.width + animationSize.width + spacing
|
||||
let size = CGSize(width: totalWidth + leftPadding + padding, height: 28.0)
|
||||
let background = background.update(
|
||||
component: RoundedRectangle(
|
||||
|
||||
let backgroundSize = self.background.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(RoundedRectangle(
|
||||
color: component.backgroundColor,
|
||||
cornerRadius: 14.0
|
||||
),
|
||||
availableSize: size,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(background
|
||||
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||
)
|
||||
if let _ = component.iconName {
|
||||
context.add(title
|
||||
.position(CGPoint(x: size.width - padding - title.size.width / 2.0, y: size.height / 2.0))
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: leftPadding + icon.size.width / 2.0, y: size.height / 2.0))
|
||||
)
|
||||
} else {
|
||||
context.add(title
|
||||
.position(CGPoint(x: padding + title.size.width / 2.0, y: size.height / 2.0))
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: size.width - padding - icon.size.width / 2.0, y: size.height / 2.0))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
)
|
||||
|
||||
if let backgroundView = self.background.view {
|
||||
if backgroundView.superview == nil {
|
||||
self.addSubview(backgroundView)
|
||||
}
|
||||
transition.setPosition(view: backgroundView, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||
transition.setBounds(view: backgroundView, bounds: CGRect(origin: CGPoint(), size: backgroundSize))
|
||||
}
|
||||
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
let titlePosition: CGPoint
|
||||
if let _ = component.iconName {
|
||||
titlePosition = CGPoint(x: size.width - padding - titleSize.width / 2.0, y: size.height / 2.0)
|
||||
} else {
|
||||
titlePosition = CGPoint(x: padding + titleSize.width / 2.0, y: size.height / 2.0)
|
||||
}
|
||||
if let animateTitleInDirection {
|
||||
titleView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
titleView.center = CGPoint(x: titlePosition.x, y: titlePosition.y - 6.0 * animateTitleInDirection)
|
||||
}
|
||||
transition.setPosition(view: titleView, position: titlePosition)
|
||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleSize)
|
||||
}
|
||||
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
self.addSubview(iconView)
|
||||
}
|
||||
let iconPosition: CGPoint
|
||||
if let _ = component.iconName {
|
||||
iconPosition = CGPoint(x: leftPadding + iconSize.width / 2.0, y: size.height / 2.0)
|
||||
} else {
|
||||
iconPosition = CGPoint(x: size.width - padding - animationSize.width / 2.0, y: size.height / 2.0)
|
||||
}
|
||||
transition.setPosition(view: iconView, position: iconPosition)
|
||||
transition.setBounds(view: iconView, bounds: CGRect(origin: CGPoint(), size: iconSize))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -201,70 +201,27 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu
|
||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
private let actionNodes: [ContextControllerActionsListActionItemNode]
|
||||
private let separatorNodes: [ASDisplayNode]
|
||||
private var actionNodes: [AnyHashable: ContextControllerActionsListActionItemNode] = [:]
|
||||
private var separatorNodes: [AnyHashable: ASDisplayNode] = [:]
|
||||
|
||||
private var searchDisposable: Disposable?
|
||||
private var searchQuery = ""
|
||||
|
||||
private var itemHeights: [AnyHashable: CGFloat] = [:]
|
||||
private var totalContentHeight: CGFloat = 0
|
||||
private var itemFrames: [AnyHashable: CGRect] = [:]
|
||||
|
||||
init(presentationData: PresentationData, item: GiftAttributeListContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||
self.item = item
|
||||
self.presentationData = presentationData
|
||||
self.presentationData = presentationData.withUpdate(listsFontSize: .regular)
|
||||
self.getController = getController
|
||||
self.actionSelected = actionSelected
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
var actionNodes: [ContextControllerActionsListActionItemNode] = []
|
||||
var separatorNodes: [ASDisplayNode] = []
|
||||
|
||||
let selectedAttributes = Set(item.selectedAttributes)
|
||||
|
||||
let selectAllAction = ContextMenuActionItem(text: presentationData.strings.Gift_Store_SelectAll, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
||||
}, iconPosition: .left, action: { _, f in
|
||||
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||
|
||||
item.selectAll()
|
||||
})
|
||||
|
||||
let selectAllActionNode = ContextControllerActionsListActionItemNode(context: item.context, getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: selectAllAction)
|
||||
actionNodes.append(selectAllActionNode)
|
||||
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
separatorNodes.append(separatorNode)
|
||||
|
||||
for attribute in item.attributes {
|
||||
guard let action = actionForAttribute(attribute: attribute, presentationData: presentationData, selectedAttributes: selectedAttributes, searchQuery: self.searchQuery, item: item, getController: getController) else {
|
||||
continue
|
||||
}
|
||||
let actionNode = ContextControllerActionsListActionItemNode(context: item.context, getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: action)
|
||||
actionNodes.append(actionNode)
|
||||
if actionNodes.count != item.attributes.count {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
separatorNodes.append(separatorNode)
|
||||
}
|
||||
}
|
||||
|
||||
let nopAction: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)? = nil
|
||||
let emptyResultsAction = ContextMenuActionItem(text: presentationData.strings.Gift_Store_NoResults, textFont: .small, icon: { _ in return nil }, action: nopAction)
|
||||
let emptyResultsActionNode = ContextControllerActionsListActionItemNode(context: item.context, getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: emptyResultsAction)
|
||||
actionNodes.append(emptyResultsActionNode)
|
||||
|
||||
self.actionNodes = actionNodes
|
||||
self.separatorNodes = separatorNodes
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.scrollNode)
|
||||
for separatorNode in self.separatorNodes {
|
||||
self.scrollNode.addSubnode(separatorNode)
|
||||
}
|
||||
for actionNode in self.actionNodes {
|
||||
self.scrollNode.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
self.searchDisposable = (item.searchQuery
|
||||
|> deliverOnMainQueue).start(next: { [weak self] searchQuery in
|
||||
@ -272,15 +229,7 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu
|
||||
return
|
||||
}
|
||||
self.searchQuery = searchQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
var i = 1
|
||||
for attribute in item.attributes {
|
||||
guard let action = actionForAttribute(attribute: attribute, presentationData: presentationData, selectedAttributes: selectedAttributes, searchQuery: self.searchQuery, item: item, getController: getController) else {
|
||||
continue
|
||||
}
|
||||
self.actionNodes[i].setItem(item: action)
|
||||
i += 1
|
||||
}
|
||||
self.invalidateLayout()
|
||||
self.getController()?.requestLayout(transition: .immediate)
|
||||
})
|
||||
}
|
||||
@ -298,95 +247,245 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu
|
||||
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let minActionsWidth: CGFloat = 250.0
|
||||
let maxActionsWidth: CGFloat = 300.0
|
||||
let constrainedWidth = min(constrainedWidth, maxActionsWidth)
|
||||
var maxWidth: CGFloat = 0.0
|
||||
var contentHeight: CGFloat = 0.0
|
||||
var heightsAndCompletions: [(Int, CGFloat, (CGSize, ContainedViewLayoutTransition) -> Void)] = []
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let maxWidth = self.maxWidth {
|
||||
self.updateScrolling(maxWidth: maxWidth)
|
||||
}
|
||||
}
|
||||
|
||||
enum ItemType {
|
||||
case selectAll
|
||||
case attribute(StarGift.UniqueGift.Attribute)
|
||||
case noResults
|
||||
case separator
|
||||
}
|
||||
|
||||
private func getVisibleItems(in scrollView: UIScrollView, constrainedWidth: CGFloat) -> [(itemId: AnyHashable, itemType: ItemType, frame: CGRect)] {
|
||||
let effectiveAttributes: [StarGift.UniqueGift.Attribute]
|
||||
if self.searchQuery.isEmpty {
|
||||
effectiveAttributes = self.item.attributes
|
||||
} else {
|
||||
effectiveAttributes = filteredAttributes(attributes: self.item.attributes, query: self.searchQuery)
|
||||
}
|
||||
let visibleAttributes = Set(effectiveAttributes.map { attribute -> AnyHashable in
|
||||
|
||||
var items: [(itemId: AnyHashable, itemType: ItemType, frame: CGRect)] = []
|
||||
var yOffset: CGFloat = 0
|
||||
|
||||
let defaultHeight: CGFloat = 42.0
|
||||
if self.searchQuery.isEmpty {
|
||||
let selectAllId = AnyHashable("selectAll")
|
||||
let height = self.itemHeights[selectAllId] ?? defaultHeight
|
||||
let frame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: height)
|
||||
items.append((selectAllId, .selectAll, frame))
|
||||
yOffset += height
|
||||
|
||||
let separatorId = AnyHashable("separator_selectAll")
|
||||
let separatorFrame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: UIScreenPixel)
|
||||
items.append((separatorId, .separator, separatorFrame))
|
||||
yOffset += UIScreenPixel
|
||||
}
|
||||
|
||||
for (index, attribute) in effectiveAttributes.enumerated() {
|
||||
let attributeId = self.getAttributeId(from: attribute)
|
||||
let height = self.itemHeights[attributeId] ?? defaultHeight
|
||||
let frame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: height)
|
||||
items.append((attributeId, .attribute(attribute), frame))
|
||||
yOffset += height
|
||||
|
||||
if index < effectiveAttributes.count - 1 {
|
||||
let separatorId = AnyHashable("separator_\(attributeId)")
|
||||
let separatorFrame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: UIScreenPixel)
|
||||
items.append((separatorId, .separator, separatorFrame))
|
||||
yOffset += UIScreenPixel
|
||||
}
|
||||
}
|
||||
|
||||
if !self.searchQuery.isEmpty && effectiveAttributes.isEmpty {
|
||||
let noResultsId = AnyHashable("noResults")
|
||||
let height = self.itemHeights[noResultsId] ?? defaultHeight
|
||||
let frame = CGRect(x: 0, y: yOffset, width: constrainedWidth, height: height)
|
||||
items.append((noResultsId, .noResults, frame))
|
||||
yOffset += height
|
||||
}
|
||||
|
||||
self.totalContentHeight = yOffset
|
||||
|
||||
for (itemId, _, frame) in items {
|
||||
self.itemFrames[itemId] = frame
|
||||
}
|
||||
|
||||
let visibleBounds = scrollView.bounds.insetBy(dx: 0.0, dy: -100.0)
|
||||
return items.filter { visibleBounds.intersects($0.frame) }
|
||||
}
|
||||
|
||||
private func getAttributeId(from attribute: StarGift.UniqueGift.Attribute) -> AnyHashable {
|
||||
switch attribute {
|
||||
case let .model(_, file, _):
|
||||
return file.fileId.id
|
||||
return AnyHashable("model_\(file.fileId.id)")
|
||||
case let .pattern(_, file, _):
|
||||
return file.fileId.id
|
||||
return AnyHashable("pattern_\(file.fileId.id)")
|
||||
case let .backdrop(_, id, _, _, _, _, _):
|
||||
return id
|
||||
return AnyHashable("backdrop_\(id)")
|
||||
default:
|
||||
fatalError()
|
||||
return AnyHashable("unknown")
|
||||
}
|
||||
}
|
||||
|
||||
private var maxWidth: CGFloat?
|
||||
private func updateScrolling(maxWidth: CGFloat) {
|
||||
let scrollView = self.scrollNode.view
|
||||
|
||||
let constrainedWidth = scrollView.bounds.width
|
||||
let visibleItems = self.getVisibleItems(in: scrollView, constrainedWidth: constrainedWidth)
|
||||
|
||||
var validNodeIds: Set<AnyHashable> = []
|
||||
|
||||
for (itemId, itemType, frame) in visibleItems {
|
||||
validNodeIds.insert(itemId)
|
||||
|
||||
switch itemType {
|
||||
case .selectAll:
|
||||
if self.actionNodes[itemId] == nil {
|
||||
let selectAllAction = ContextMenuActionItem(text: presentationData.strings.Gift_Store_SelectAll, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
||||
}, iconPosition: .left, action: { _, f in
|
||||
self.getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||
self.item.selectAll()
|
||||
})
|
||||
|
||||
for i in 0 ..< self.actionNodes.count {
|
||||
let itemNode = self.actionNodes[i]
|
||||
if !self.searchQuery.isEmpty && i == 0 {
|
||||
itemNode.isHidden = true
|
||||
continue
|
||||
let actionNode = ContextControllerActionsListActionItemNode(
|
||||
context: self.item.context,
|
||||
getController: self.getController,
|
||||
requestDismiss: self.actionSelected,
|
||||
requestUpdateAction: { _, _ in },
|
||||
item: selectAllAction
|
||||
)
|
||||
self.actionNodes[itemId] = actionNode
|
||||
self.scrollNode.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
if i > 0 && i < self.actionNodes.count - 1 {
|
||||
let attribute = self.item.attributes[i - 1]
|
||||
let attributeId: AnyHashable
|
||||
switch attribute {
|
||||
case let .model(_, file, _):
|
||||
attributeId = AnyHashable(file.fileId.id)
|
||||
case let .pattern(_, file, _):
|
||||
attributeId = AnyHashable(file.fileId.id)
|
||||
case let .backdrop(_, id, _, _, _, _, _):
|
||||
attributeId = AnyHashable(id)
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
if !visibleAttributes.contains(attributeId) {
|
||||
itemNode.isHidden = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if i == self.actionNodes.count - 1 {
|
||||
if !visibleAttributes.isEmpty {
|
||||
itemNode.isHidden = true
|
||||
continue
|
||||
case .attribute(let attribute):
|
||||
if self.actionNodes[itemId] == nil {
|
||||
let selectedAttributes = Set(self.item.selectedAttributes)
|
||||
guard let action = actionForAttribute(
|
||||
attribute: attribute,
|
||||
presentationData: self.presentationData,
|
||||
selectedAttributes: selectedAttributes,
|
||||
searchQuery: self.searchQuery,
|
||||
item: self.item,
|
||||
getController: self.getController
|
||||
) else { continue }
|
||||
|
||||
let actionNode = ContextControllerActionsListActionItemNode(
|
||||
context: self.item.context,
|
||||
getController: self.getController,
|
||||
requestDismiss: self.actionSelected,
|
||||
requestUpdateAction: { _, _ in },
|
||||
item: action
|
||||
)
|
||||
self.actionNodes[itemId] = actionNode
|
||||
self.scrollNode.addSubnode(actionNode)
|
||||
} else {
|
||||
let selectedAttributes = Set(self.item.selectedAttributes)
|
||||
if let action = actionForAttribute(
|
||||
attribute: attribute,
|
||||
presentationData: self.presentationData,
|
||||
selectedAttributes: selectedAttributes,
|
||||
searchQuery: self.searchQuery,
|
||||
item: self.item,
|
||||
getController: self.getController
|
||||
) {
|
||||
self.actionNodes[itemId]?.setItem(item: action)
|
||||
}
|
||||
}
|
||||
itemNode.isHidden = false
|
||||
|
||||
let (minSize, complete) = itemNode.update(presentationData: self.presentationData, constrainedSize: CGSize(width: constrainedWidth, height: constrainedHeight))
|
||||
maxWidth = max(maxWidth, minSize.width)
|
||||
heightsAndCompletions.append((i, minSize.height, complete))
|
||||
contentHeight += minSize.height
|
||||
}
|
||||
|
||||
maxWidth = max(maxWidth, minActionsWidth)
|
||||
case .noResults:
|
||||
if self.actionNodes[itemId] == nil {
|
||||
let nopAction: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)? = nil
|
||||
let emptyResultsAction = ContextMenuActionItem(
|
||||
text: presentationData.strings.Gift_Store_NoResults,
|
||||
textFont: .small,
|
||||
icon: { _ in return nil },
|
||||
action: nopAction
|
||||
)
|
||||
let actionNode = ContextControllerActionsListActionItemNode(
|
||||
context: self.item.context,
|
||||
getController: self.getController,
|
||||
requestDismiss: self.actionSelected,
|
||||
requestUpdateAction: { _, _ in },
|
||||
item: emptyResultsAction
|
||||
)
|
||||
self.actionNodes[itemId] = actionNode
|
||||
self.scrollNode.addSubnode(actionNode)
|
||||
}
|
||||
case .separator:
|
||||
if self.separatorNodes[itemId] == nil {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
self.separatorNodes[itemId] = separatorNode
|
||||
self.scrollNode.addSubnode(separatorNode)
|
||||
}
|
||||
}
|
||||
|
||||
if let actionNode = self.actionNodes[itemId] {
|
||||
actionNode.frame = frame
|
||||
|
||||
let (minSize, complete) = actionNode.update(presentationData: self.presentationData, constrainedSize: frame.size)
|
||||
self.itemHeights[itemId] = minSize.height
|
||||
complete(CGSize(width: maxWidth, height: minSize.height), .immediate)
|
||||
} else if let separatorNode = self.separatorNodes[itemId] {
|
||||
separatorNode.frame = frame
|
||||
}
|
||||
}
|
||||
|
||||
var nodesToRemove: [AnyHashable] = []
|
||||
for (nodeId, node) in self.actionNodes {
|
||||
if !validNodeIds.contains(nodeId) {
|
||||
nodesToRemove.append(nodeId)
|
||||
node.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
for nodeId in nodesToRemove {
|
||||
self.actionNodes.removeValue(forKey: nodeId)
|
||||
}
|
||||
|
||||
var separatorsToRemove: [AnyHashable] = []
|
||||
for (separatorId, separatorNode) in self.separatorNodes {
|
||||
if !validNodeIds.contains(separatorId) {
|
||||
separatorsToRemove.append(separatorId)
|
||||
separatorNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
for separatorId in separatorsToRemove {
|
||||
self.separatorNodes.removeValue(forKey: separatorId)
|
||||
}
|
||||
}
|
||||
|
||||
private func invalidateLayout() {
|
||||
self.itemHeights.removeAll()
|
||||
self.itemFrames.removeAll()
|
||||
self.totalContentHeight = 0.0
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let minActionsWidth: CGFloat = 250.0
|
||||
let maxActionsWidth: CGFloat = 300.0
|
||||
let constrainedWidth = min(constrainedWidth, maxActionsWidth)
|
||||
let maxWidth = max(constrainedWidth, minActionsWidth)
|
||||
|
||||
let maxHeight: CGFloat = min(360.0, constrainedHeight - 108.0)
|
||||
|
||||
return (CGSize(width: maxWidth, height: min(maxHeight, contentHeight)), { size, transition in
|
||||
var verticalOffset: CGFloat = 0.0
|
||||
for (i, itemHeight, itemCompletion) in heightsAndCompletions {
|
||||
let itemNode = self.actionNodes[i]
|
||||
|
||||
let itemSize = CGSize(width: maxWidth, height: itemHeight)
|
||||
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: itemSize))
|
||||
itemCompletion(itemSize, transition)
|
||||
verticalOffset += itemHeight
|
||||
|
||||
if i < self.actionNodes.count - 2 {
|
||||
let separatorNode = self.separatorNodes[i]
|
||||
separatorNode.frame = CGRect(x: 0, y: verticalOffset, width: size.width, height: UIScreenPixel)
|
||||
}
|
||||
if self.totalContentHeight == 0 {
|
||||
let _ = self.getVisibleItems(in: UIScrollView(), constrainedWidth: constrainedWidth)
|
||||
}
|
||||
|
||||
return (CGSize(width: maxWidth, height: min(maxHeight, self.totalContentHeight)), { size, transition in
|
||||
self.maxWidth = maxWidth
|
||||
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||
self.scrollNode.view.contentSize = CGSize(width: size.width, height: self.totalContentHeight)
|
||||
|
||||
self.updateScrolling(maxWidth: maxWidth)
|
||||
})
|
||||
}
|
||||
|
||||
@ -417,7 +516,7 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
for actionNode in self.actionNodes {
|
||||
for (_, actionNode) in self.actionNodes {
|
||||
actionNode.updateIsHighlighted(isHighlighted: false)
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +98,8 @@ final class GiftStoreScreenComponent: Component {
|
||||
private var initialCount: Int32?
|
||||
private var showLoading = true
|
||||
|
||||
private var selectedFilterId: AnyHashable?
|
||||
|
||||
private var component: GiftStoreScreenComponent?
|
||||
private(set) weak var state: State?
|
||||
private var environment: EnvironmentType?
|
||||
@ -502,6 +504,13 @@ final class GiftStoreScreenComponent: Component {
|
||||
})))
|
||||
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
||||
contextController.dismissed = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.selectedFilterId = nil
|
||||
self.state?.updated()
|
||||
}
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
@ -603,6 +612,13 @@ final class GiftStoreScreenComponent: Component {
|
||||
items: .single(ContextController.Items(content: .list(items))),
|
||||
gesture: nil
|
||||
)
|
||||
contextController.dismissed = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.selectedFilterId = nil
|
||||
self.state?.updated()
|
||||
}
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
@ -704,6 +720,13 @@ final class GiftStoreScreenComponent: Component {
|
||||
items: .single(ContextController.Items(content: .list(items))),
|
||||
gesture: nil
|
||||
)
|
||||
contextController.dismissed = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.selectedFilterId = nil
|
||||
self.state?.updated()
|
||||
}
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
@ -805,6 +828,13 @@ final class GiftStoreScreenComponent: Component {
|
||||
items: .single(ContextController.Items(content: .list(items))),
|
||||
gesture: nil
|
||||
)
|
||||
contextController.dismissed = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.selectedFilterId = nil
|
||||
self.state?.updated()
|
||||
}
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
@ -996,29 +1026,43 @@ final class GiftStoreScreenComponent: Component {
|
||||
let optionWidth = (availableSize.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
||||
|
||||
var sortingTitle = environment.strings.Gift_Store_Sort_Date
|
||||
var sortingIcon: String = "Peer Info/SortDate"
|
||||
var sortingIcon: String = "GiftFilterDate"
|
||||
var sortingIndex: Int = 0
|
||||
if let sorting = self.state?.starGiftsState?.sorting {
|
||||
switch sorting {
|
||||
case .date:
|
||||
sortingTitle = environment.strings.Gift_Store_Sort_Date
|
||||
sortingIcon = "Peer Info/SortDate"
|
||||
case .value:
|
||||
sortingTitle = environment.strings.Gift_Store_Sort_Price
|
||||
sortingIcon = "Peer Info/SortValue"
|
||||
sortingIcon = "GiftFilterPrice"
|
||||
sortingIndex = 0
|
||||
case .date:
|
||||
sortingTitle = environment.strings.Gift_Store_Sort_Date
|
||||
sortingIcon = "GiftFilterDate"
|
||||
sortingIndex = 1
|
||||
case .number:
|
||||
sortingTitle = environment.strings.Gift_Store_Sort_Number
|
||||
sortingIcon = "Peer Info/SortNumber"
|
||||
sortingIcon = "GiftFilterNumber"
|
||||
sortingIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
enum FilterItemId: Int32 {
|
||||
case sort
|
||||
case model
|
||||
case backdrop
|
||||
case symbol
|
||||
}
|
||||
|
||||
var filterItems: [FilterSelectorComponent.Item] = []
|
||||
filterItems.append(FilterSelectorComponent.Item(
|
||||
id: AnyHashable(0),
|
||||
id: AnyHashable(FilterItemId.sort),
|
||||
index: sortingIndex,
|
||||
iconName: sortingIcon,
|
||||
title: sortingTitle,
|
||||
action: { [weak self] view in
|
||||
if let self {
|
||||
self.selectedFilterId = AnyHashable(FilterItemId.sort)
|
||||
self.openSortContextMenu(sourceView: view)
|
||||
self.state?.updated()
|
||||
}
|
||||
}
|
||||
))
|
||||
@ -1035,10 +1079,10 @@ final class GiftStoreScreenComponent: Component {
|
||||
switch attribute {
|
||||
case .model:
|
||||
modelCount += 1
|
||||
case .pattern:
|
||||
symbolCount += 1
|
||||
case .backdrop:
|
||||
backdropCount += 1
|
||||
case .pattern:
|
||||
symbolCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -1054,29 +1098,35 @@ final class GiftStoreScreenComponent: Component {
|
||||
}
|
||||
|
||||
filterItems.append(FilterSelectorComponent.Item(
|
||||
id: AnyHashable(1),
|
||||
id: AnyHashable(FilterItemId.model),
|
||||
title: modelTitle,
|
||||
action: { [weak self] view in
|
||||
if let self {
|
||||
self.selectedFilterId = AnyHashable(FilterItemId.model)
|
||||
self.openModelContextMenu(sourceView: view)
|
||||
self.state?.updated()
|
||||
}
|
||||
}
|
||||
))
|
||||
filterItems.append(FilterSelectorComponent.Item(
|
||||
id: AnyHashable(2),
|
||||
id: AnyHashable(FilterItemId.backdrop),
|
||||
title: backdropTitle,
|
||||
action: { [weak self] view in
|
||||
if let self {
|
||||
self.selectedFilterId = AnyHashable(FilterItemId.backdrop)
|
||||
self.openBackdropContextMenu(sourceView: view)
|
||||
self.state?.updated()
|
||||
}
|
||||
}
|
||||
))
|
||||
filterItems.append(FilterSelectorComponent.Item(
|
||||
id: AnyHashable(3),
|
||||
id: AnyHashable(FilterItemId.symbol),
|
||||
title: symbolTitle,
|
||||
action: { [weak self] view in
|
||||
if let self {
|
||||
self.selectedFilterId = AnyHashable(FilterItemId.symbol)
|
||||
self.openSymbolContextMenu(sourceView: view)
|
||||
self.state?.updated()
|
||||
}
|
||||
}
|
||||
))
|
||||
@ -1092,7 +1142,8 @@ final class GiftStoreScreenComponent: Component {
|
||||
foreground: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.65),
|
||||
background: theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85)
|
||||
),
|
||||
items: filterItems
|
||||
items: filterItems,
|
||||
selectedItemId: self.selectedFilterId
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 10.0 * 2.0, height: 50.0)
|
||||
@ -1193,8 +1244,17 @@ final class GiftStoreScreenComponent: Component {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let previousFilterAttributes = self.starGiftsState?.filterAttributes
|
||||
let previousSorting = self.starGiftsState?.sorting
|
||||
self.starGiftsState = state
|
||||
self.updated()
|
||||
|
||||
var transition: ComponentTransition = .immediate
|
||||
if let previousFilterAttributes, previousFilterAttributes != state.filterAttributes {
|
||||
transition = .easeInOut(duration: 0.25)
|
||||
} else if let previousSorting, previousSorting != state.sorting {
|
||||
transition = .easeInOut(duration: 0.25)
|
||||
}
|
||||
self.updated(transition: transition)
|
||||
})
|
||||
}
|
||||
|
||||
|
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterDate.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterDate.tgs
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterNumber.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterNumber.tgs
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterPrice.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/GiftFilterPrice.tgs
Normal file
Binary file not shown.
@ -61,8 +61,10 @@ extension ChatControllerImpl {
|
||||
|
||||
var canSendPolls = true
|
||||
if let peer = self.presentationInterfaceState.renderedPeer?.peer {
|
||||
if let peer = peer as? TelegramUser, peer.botInfo == nil {
|
||||
if let peer = peer as? TelegramUser {
|
||||
if peer.botInfo == nil && peer.id != self.context.account.peerId {
|
||||
canSendPolls = false
|
||||
}
|
||||
} else if peer is TelegramSecretChat {
|
||||
canSendPolls = false
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
|
@ -528,9 +528,22 @@ public final class WebAppMessagePreviewScreen: ViewControllerComponentContainer
|
||||
}
|
||||
|
||||
fileprivate func proceed() {
|
||||
let requestPeerType = self.preparedMessage.peerTypes.requestPeerTypes
|
||||
let peerTypes = self.preparedMessage.peerTypes
|
||||
var types: [ReplyMarkupButtonRequestPeerType] = []
|
||||
if peerTypes.contains(.users) {
|
||||
types.append(.user(.init(isBot: false, isPremium: nil)))
|
||||
}
|
||||
if peerTypes.contains(.bots) {
|
||||
types.append(.user(.init(isBot: true, isPremium: nil)))
|
||||
}
|
||||
if peerTypes.contains(.channels) {
|
||||
types.append(.channel(.init(isCreator: false, hasUsername: nil, userAdminRights: TelegramChatAdminRights(rights: [.canPostMessages]), botAdminRights: nil)))
|
||||
}
|
||||
if peerTypes.contains(.groups) {
|
||||
types.append(.group(.init(isCreator: false, hasUsername: nil, isForum: nil, botParticipant: false, userAdminRights: nil, botAdminRights: nil)))
|
||||
}
|
||||
|
||||
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: requestPeerType, hasContactSelector: false, multipleSelection: true, selectForumThreads: true, immediatelyActivateMultipleSelection: true))
|
||||
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: types, hasContactSelector: false, multipleSelection: true, selectForumThreads: true, immediatelyActivateMultipleSelection: true))
|
||||
|
||||
controller.multiplePeersSelected = { [weak self, weak controller] peers, _, _, _, _, _ in
|
||||
guard let self else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user