mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'gift-resale' of gitlab.com:peter-iakovlev/telegram-ios into gift-resale
# Conflicts: # submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift
This commit is contained in:
commit
037890cfa5
@ -1178,7 +1178,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||||||
func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController
|
func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController
|
||||||
func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController
|
func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController
|
||||||
func makeStarsWithdrawalScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController
|
func makeStarsWithdrawalScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController
|
||||||
func makeStarGiftResellScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController
|
func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController
|
||||||
func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController
|
func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController
|
||||||
func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController
|
func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController
|
||||||
func makeStarsIntroScreen(context: AccountContext) -> ViewController
|
func makeStarsIntroScreen(context: AccountContext) -> ViewController
|
||||||
|
@ -41,6 +41,7 @@ public struct AttachmentMainButtonState {
|
|||||||
public let progress: Progress
|
public let progress: Progress
|
||||||
public let isEnabled: Bool
|
public let isEnabled: Bool
|
||||||
public let hasShimmer: Bool
|
public let hasShimmer: Bool
|
||||||
|
public let iconName: String?
|
||||||
public let position: Position?
|
public let position: Position?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@ -53,6 +54,7 @@ public struct AttachmentMainButtonState {
|
|||||||
progress: Progress,
|
progress: Progress,
|
||||||
isEnabled: Bool,
|
isEnabled: Bool,
|
||||||
hasShimmer: Bool,
|
hasShimmer: Bool,
|
||||||
|
iconName: String? = nil,
|
||||||
position: Position? = nil
|
position: Position? = nil
|
||||||
) {
|
) {
|
||||||
self.text = text
|
self.text = text
|
||||||
@ -64,6 +66,7 @@ public struct AttachmentMainButtonState {
|
|||||||
self.progress = progress
|
self.progress = progress
|
||||||
self.isEnabled = isEnabled
|
self.isEnabled = isEnabled
|
||||||
self.hasShimmer = hasShimmer
|
self.hasShimmer = hasShimmer
|
||||||
|
self.iconName = iconName
|
||||||
self.position = position
|
self.position = position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,6 +476,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
|
|||||||
private var size: CGSize?
|
private var size: CGSize?
|
||||||
|
|
||||||
private let backgroundAnimationNode: ASImageNode
|
private let backgroundAnimationNode: ASImageNode
|
||||||
|
private var iconNode: ASImageNode?
|
||||||
fileprivate let textNode: ImmediateTextNode
|
fileprivate let textNode: ImmediateTextNode
|
||||||
private var badgeNode: BadgeNode?
|
private var badgeNode: BadgeNode?
|
||||||
private let statusNode: SemanticStatusNode
|
private let statusNode: SemanticStatusNode
|
||||||
@ -781,6 +782,25 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
|
|||||||
badgeNode.removeFromSupernode()
|
badgeNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let iconName = state.iconName {
|
||||||
|
let iconNode: ASImageNode
|
||||||
|
if let current = self.iconNode {
|
||||||
|
iconNode = current
|
||||||
|
} else {
|
||||||
|
iconNode = ASImageNode()
|
||||||
|
iconNode.displaysAsynchronously = false
|
||||||
|
iconNode.image = generateTintedImage(image: UIImage(bundleImageName: iconName), color: state.textColor)
|
||||||
|
self.addSubnode(iconNode)
|
||||||
|
}
|
||||||
|
if let iconSize = iconNode.image?.size {
|
||||||
|
textFrame.origin.x += (iconSize.width + 6.0) / 2.0
|
||||||
|
iconNode.frame = CGRect(origin: CGPoint(x: textFrame.minX - iconSize.width - 6.0, y: textFrame.minY + floorToScreenPixels((textFrame.height - iconSize.height) * 0.5)), size: iconSize)
|
||||||
|
}
|
||||||
|
} else if let iconNode = self.iconNode {
|
||||||
|
self.iconNode = nil
|
||||||
|
iconNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
if self.textNode.frame.width.isZero {
|
if self.textNode.frame.width.isZero {
|
||||||
self.textNode.frame = textFrame
|
self.textNode.frame = textFrame
|
||||||
} else {
|
} else {
|
||||||
@ -795,7 +815,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
|
|||||||
self.transitionFromProgress()
|
self.transitionFromProgress()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let shimmerView = self.shimmerView, let borderView = self.borderView, let borderMaskView = self.borderMaskView, let borderShimmerView = self.borderShimmerView {
|
if let shimmerView = self.shimmerView, let borderView = self.borderView, let borderMaskView = self.borderMaskView, let borderShimmerView = self.borderShimmerView {
|
||||||
let buttonFrame = CGRect(origin: .zero, size: size)
|
let buttonFrame = CGRect(origin: .zero, size: size)
|
||||||
let buttonWidth = size.width
|
let buttonWidth = size.width
|
||||||
|
@ -2145,6 +2145,8 @@ public protocol ContextReferenceContentSource: AnyObject {
|
|||||||
|
|
||||||
var shouldBeDismissed: Signal<Bool, NoError> { get }
|
var shouldBeDismissed: Signal<Bool, NoError> { get }
|
||||||
|
|
||||||
|
var forceDisplayBelowKeyboard: Bool { get }
|
||||||
|
|
||||||
func transitionInfo() -> ContextControllerReferenceViewInfo?
|
func transitionInfo() -> ContextControllerReferenceViewInfo?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2153,6 +2155,10 @@ public extension ContextReferenceContentSource {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var forceDisplayBelowKeyboard: Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var shouldBeDismissed: Signal<Bool, NoError> {
|
var shouldBeDismissed: Signal<Bool, NoError> {
|
||||||
return .single(false)
|
return .single(false)
|
||||||
}
|
}
|
||||||
@ -2744,7 +2750,9 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?) {
|
public func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?) {
|
||||||
if viewTreeContainsFirstResponder(view: self.view) {
|
if let mainSource = self.configuration.sources.first(where: { $0.id == self.configuration.initialId }), case let .reference(source) = mainSource.source, source.forceDisplayBelowKeyboard {
|
||||||
|
|
||||||
|
} else if viewTreeContainsFirstResponder(view: self.view) {
|
||||||
self.dismissOnInputClose = (result, completion)
|
self.dismissOnInputClose = (result, completion)
|
||||||
self.view.endEditing(true)
|
self.view.endEditing(true)
|
||||||
return
|
return
|
||||||
|
@ -273,7 +273,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||||||
return super.hitTest(point, with: event)
|
return super.hitTest(point, with: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setItem(item: ContextMenuActionItem) {
|
public func setItem(item: ContextMenuActionItem) {
|
||||||
self.item = item
|
self.item = item
|
||||||
self.accessibilityLabel = item.text
|
self.accessibilityLabel = item.text
|
||||||
}
|
}
|
||||||
@ -363,6 +363,8 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||||||
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId, enableAnimation: true), range: entity.range)
|
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId, enableAnimation: true), range: entity.range)
|
||||||
} else if case .Bold = entity.type {
|
} else if case .Bold = entity.type {
|
||||||
return ChatTextInputStateTextAttribute(type: .bold, range: entity.range)
|
return ChatTextInputStateTextAttribute(type: .bold, range: entity.range)
|
||||||
|
} else if case .Italic = entity.type {
|
||||||
|
return ChatTextInputStateTextAttribute(type: .italic, range: entity.range)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -373,6 +375,8 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||||||
], range: NSRange(location: 0, length: result.length))
|
], range: NSRange(location: 0, length: result.length))
|
||||||
for attribute in inputStateText.attributes {
|
for attribute in inputStateText.attributes {
|
||||||
if case .bold = attribute.type {
|
if case .bold = attribute.type {
|
||||||
|
result.addAttribute(NSAttributedString.Key.font, value: Font.semibold(presentationData.listsFontSize.baseDisplaySize), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||||
|
} else if case .italic = attribute.type {
|
||||||
result.addAttribute(NSAttributedString.Key.font, value: Font.semibold(15.0), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
result.addAttribute(NSAttributedString.Key.font, value: Font.semibold(15.0), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -500,6 +500,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||||||
func wantsDisplayBelowKeyboard() -> Bool {
|
func wantsDisplayBelowKeyboard() -> Bool {
|
||||||
if let reactionContextNode = self.reactionContextNode {
|
if let reactionContextNode = self.reactionContextNode {
|
||||||
return reactionContextNode.wantsDisplayBelowKeyboard()
|
return reactionContextNode.wantsDisplayBelowKeyboard()
|
||||||
|
} else if case let .reference(source) = self.source {
|
||||||
|
return source.forceDisplayBelowKeyboard
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1823,6 +1823,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
private var isDismissing = false
|
private var isDismissing = false
|
||||||
|
|
||||||
fileprivate let mainButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
fileprivate let mainButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
||||||
|
fileprivate let secondaryButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
||||||
|
|
||||||
private let mainButtonAction: (() -> Void)?
|
private let mainButtonAction: (() -> Void)?
|
||||||
|
|
||||||
@ -2380,9 +2381,23 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0)
|
transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0)
|
||||||
transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1)
|
transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1)
|
||||||
|
|
||||||
//if self. {
|
if self.selectionCount > 0 {
|
||||||
//self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: "Add", badge: "\(count)", font: .bold, background: .color(self.presentationData.theme.actionSheet.controlAccentColor), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, isVisible: count > 0, progress: .none, isEnabled: true, hasShimmer: false)))
|
//TODO:localize
|
||||||
//}
|
var text = "Create 1 Story"
|
||||||
|
if self.selectionCount > 1 {
|
||||||
|
text = "Create \(self.selectionCount) Stories"
|
||||||
|
}
|
||||||
|
self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: text, badge: nil, font: .bold, background: .color(self.presentationData.theme.actionSheet.controlAccentColor), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false, position: .top)))
|
||||||
|
|
||||||
|
if self.selectionCount > 1 && self.selectionCount <= 6 {
|
||||||
|
self.secondaryButtonStatePromise.set(.single(AttachmentMainButtonState(text: "Combine into Collage", badge: nil, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false, iconName: "Media Editor/Collage", position: .bottom)))
|
||||||
|
} else {
|
||||||
|
self.secondaryButtonStatePromise.set(.single(nil))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.mainButtonStatePromise.set(.single(nil))
|
||||||
|
self.secondaryButtonStatePromise.set(.single(nil))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateThemeAndStrings() {
|
private func updateThemeAndStrings() {
|
||||||
@ -2933,6 +2948,10 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
|||||||
return self.controller?.mainButtonStatePromise.get() ?? .single(nil)
|
return self.controller?.mainButtonStatePromise.get() ?? .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var secondaryButtonState: Signal<AttachmentMainButtonState?, NoError> {
|
||||||
|
return self.controller?.secondaryButtonStatePromise.get() ?? .single(nil)
|
||||||
|
}
|
||||||
|
|
||||||
init(controller: MediaPickerScreenImpl) {
|
init(controller: MediaPickerScreenImpl) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
}
|
}
|
||||||
@ -2952,6 +2971,10 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
|||||||
func mainButtonAction() {
|
func mainButtonAction() {
|
||||||
self.controller?.mainButtonPressed()
|
self.controller?.mainButtonPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func secondaryButtonAction() {
|
||||||
|
self.controller?.mainButtonPressed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class MediaPickerContextReferenceContentSource: ContextReferenceContentSource {
|
private final class MediaPickerContextReferenceContentSource: ContextReferenceContentSource {
|
||||||
|
@ -1459,23 +1459,26 @@ private final class ProfileGiftsContextImpl {
|
|||||||
return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId)
|
return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buyStarGift(gift inputGift: TelegramCore.StarGift, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
||||||
var gift = self.gifts.first(where: { $0.gift == inputGift })
|
|
||||||
if gift == nil {
|
|
||||||
gift = self.filteredGifts.first(where: { $0.gift == inputGift })
|
|
||||||
}
|
|
||||||
guard case let .unique(uniqueGift) = gift?.gift else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let count = self.count {
|
if let count = self.count {
|
||||||
self.count = max(0, count - 1)
|
self.count = max(0, count - 1)
|
||||||
}
|
}
|
||||||
self.gifts.removeAll(where: { $0.gift == inputGift })
|
self.gifts.removeAll(where: { gift in
|
||||||
self.filteredGifts.removeAll(where: { $0.gift == inputGift })
|
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
self.filteredGifts.removeAll(where: { gift in
|
||||||
|
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
self.pushState()
|
self.pushState()
|
||||||
|
|
||||||
return _internal_buyStarGift(account: self.account, slug: uniqueGift.slug, peerId: peerId)
|
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeStarGift(gift: TelegramCore.StarGift) {
|
func removeStarGift(gift: TelegramCore.StarGift) {
|
||||||
@ -1899,11 +1902,11 @@ public final class ProfileGiftsContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func buyStarGift(gift: TelegramCore.StarGift, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
public func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
self.impl.with { impl in
|
self.impl.with { impl in
|
||||||
disposable.set(impl.buyStarGift(gift: gift, peerId: peerId).start(error: { error in
|
disposable.set(impl.buyStarGift(slug: slug, peerId: peerId).start(error: { error in
|
||||||
subscriber.putError(error)
|
subscriber.putError(error)
|
||||||
}, completed: {
|
}, completed: {
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
@ -2308,7 +2311,7 @@ private final class ResaleGiftsContextImpl {
|
|||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
|
|
||||||
private var sorting: ResaleGiftsContext.Sorting = .date
|
private var sorting: ResaleGiftsContext.Sorting = .value
|
||||||
private var filterAttributes: [ResaleGiftsContext.Attribute] = []
|
private var filterAttributes: [ResaleGiftsContext.Attribute] = []
|
||||||
|
|
||||||
private var gifts: [StarGift] = []
|
private var gifts: [StarGift] = []
|
||||||
@ -2431,7 +2434,15 @@ private final class ResaleGiftsContextImpl {
|
|||||||
|
|
||||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||||
return (gifts.compactMap { StarGift(apiStarGift: $0) }, resultAttributes, attributeCount, count, nextOffset)
|
|
||||||
|
var mappedGifts: [StarGift] = []
|
||||||
|
for gift in gifts {
|
||||||
|
if let mappedGift = StarGift(apiStarGift: gift), case let .unique(uniqueGift) = mappedGift, let resellStars = uniqueGift.resellStars, resellStars > 0 {
|
||||||
|
mappedGifts.append(mappedGift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mappedGifts, resultAttributes, attributeCount, count, nextOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2444,9 +2455,7 @@ private final class ResaleGiftsContextImpl {
|
|||||||
if initialNextOffset == nil || reload {
|
if initialNextOffset == nil || reload {
|
||||||
self.gifts = gifts
|
self.gifts = gifts
|
||||||
} else {
|
} else {
|
||||||
for gift in gifts {
|
self.gifts.append(contentsOf: gifts)
|
||||||
self.gifts.append(gift)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let updatedCount = max(Int32(self.gifts.count), count)
|
let updatedCount = max(Int32(self.gifts.count), count)
|
||||||
@ -2473,6 +2482,11 @@ private final class ResaleGiftsContextImpl {
|
|||||||
self.loadMore()
|
self.loadMore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeStarGift(gift: TelegramCore.StarGift) {
|
||||||
|
self.gifts.removeAll(where: { $0 == gift })
|
||||||
|
self.pushState()
|
||||||
|
}
|
||||||
|
|
||||||
func updateSorting(_ sorting: ResaleGiftsContext.Sorting) {
|
func updateSorting(_ sorting: ResaleGiftsContext.Sorting) {
|
||||||
guard self.sorting != sorting else {
|
guard self.sorting != sorting else {
|
||||||
return
|
return
|
||||||
@ -2571,6 +2585,12 @@ public final class ResaleGiftsContext {
|
|||||||
impl.updateFilterAttributes(attributes)
|
impl.updateFilterAttributes(attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func removeStarGift(gift: TelegramCore.StarGift) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.removeStarGift(gift: gift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var currentState: ResaleGiftsContext.State? {
|
public var currentState: ResaleGiftsContext.State? {
|
||||||
var state: ResaleGiftsContext.State?
|
var state: ResaleGiftsContext.State?
|
||||||
|
@ -125,7 +125,7 @@ public extension TelegramEngine {
|
|||||||
return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId)
|
return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func buyStarGift(prepaid: Bool, slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
public func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
||||||
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId)
|
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2726,9 +2726,13 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
self.additionalPreviewView.isEnabled = false
|
self.additionalPreviewView.isEnabled = false
|
||||||
self.collageView?.isEnabled = false
|
self.collageView?.isEnabled = false
|
||||||
|
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
|
||||||
|
#else
|
||||||
Queue.mainQueue().after(0.3) {
|
Queue.mainQueue().after(0.3) {
|
||||||
self.previewBlurPromise.set(true)
|
self.previewBlurPromise.set(true)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
self.camera?.stopCapture()
|
self.camera?.stopCapture()
|
||||||
|
|
||||||
self.cameraIsActive = false
|
self.cameraIsActive = false
|
||||||
@ -3627,12 +3631,17 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
if self.cameraState.isCollageEnabled, let collage = self.node.collage {
|
if self.cameraState.isCollageEnabled, let collage = self.node.collage {
|
||||||
selectionLimit = collage.grid.count - collage.results.count
|
selectionLimit = collage.grid.count - collage.results.count
|
||||||
} else {
|
} else {
|
||||||
selectionLimit = 6
|
if self.cameraState.isCollageEnabled {
|
||||||
|
selectionLimit = 6
|
||||||
|
} else {
|
||||||
|
selectionLimit = 10
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
//TODO:unmock
|
||||||
controller = self.context.sharedContext.makeStoryMediaPickerScreen(
|
controller = self.context.sharedContext.makeStoryMediaPickerScreen(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
isDark: true,
|
isDark: true,
|
||||||
forCollage: self.cameraState.isCollageEnabled,
|
forCollage: self.cameraState.isCollageEnabled || "".isEmpty,
|
||||||
selectionLimit: selectionLimit,
|
selectionLimit: selectionLimit,
|
||||||
getSourceRect: { [weak self] in
|
getSourceRect: { [weak self] in
|
||||||
if let self {
|
if let self {
|
||||||
|
@ -44,6 +44,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Gifts/GiftSetupScreen",
|
"//submodules/TelegramUI/Components/Gifts/GiftSetupScreen",
|
||||||
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
|
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
|
||||||
"//submodules/TelegramUI/Components/LottieComponent",
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -24,21 +24,24 @@ public final class FilterSelectorComponent: Component {
|
|||||||
|
|
||||||
public struct Item: Equatable {
|
public struct Item: Equatable {
|
||||||
public var id: AnyHashable
|
public var id: AnyHashable
|
||||||
|
public var iconName: String?
|
||||||
public var title: String
|
public var title: String
|
||||||
public var action: (UIView) -> Void
|
public var action: (UIView) -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: AnyHashable,
|
id: AnyHashable,
|
||||||
|
iconName: String? = nil,
|
||||||
title: String,
|
title: String,
|
||||||
action: @escaping (UIView) -> Void
|
action: @escaping (UIView) -> Void
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
|
self.iconName = iconName
|
||||||
self.title = title
|
self.title = title
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||||
return lhs.id == rhs.id && lhs.title == rhs.title
|
return lhs.id == rhs.id && lhs.iconName == rhs.iconName && lhs.title == rhs.title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +145,7 @@ public final class FilterSelectorComponent: Component {
|
|||||||
component: AnyComponent(PlainButtonComponent(
|
component: AnyComponent(PlainButtonComponent(
|
||||||
content: AnyComponent(ItemComponent(
|
content: AnyComponent(ItemComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
|
iconName: item.iconName,
|
||||||
text: item.title,
|
text: item.title,
|
||||||
font: itemFont,
|
font: itemFont,
|
||||||
color: component.colors.foreground,
|
color: component.colors.foreground,
|
||||||
@ -231,6 +235,7 @@ extension CGRect {
|
|||||||
|
|
||||||
private final class ItemComponent: CombinedComponent {
|
private final class ItemComponent: CombinedComponent {
|
||||||
let context: AccountContext?
|
let context: AccountContext?
|
||||||
|
let iconName: String?
|
||||||
let text: String
|
let text: String
|
||||||
let font: UIFont
|
let font: UIFont
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
@ -238,12 +243,14 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext?,
|
context: AccountContext?,
|
||||||
|
iconName: String?,
|
||||||
text: String,
|
text: String,
|
||||||
font: UIFont,
|
font: UIFont,
|
||||||
color: UIColor,
|
color: UIColor,
|
||||||
backgroundColor: UIColor
|
backgroundColor: UIColor
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.iconName = iconName
|
||||||
self.text = text
|
self.text = text
|
||||||
self.font = font
|
self.font = font
|
||||||
self.color = color
|
self.color = color
|
||||||
@ -254,6 +261,9 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
if lhs.context !== rhs.context {
|
if lhs.context !== rhs.context {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.iconName != rhs.iconName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -297,17 +307,22 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
|
|
||||||
let icon = icon.update(
|
let icon = icon.update(
|
||||||
component: BundleIconComponent(
|
component: BundleIconComponent(
|
||||||
name: "Item List/ExpandableSelectorArrows",
|
name: component.iconName ?? "Item List/ExpandableSelectorArrows",
|
||||||
tintColor: component.color
|
tintColor: component.color,
|
||||||
|
maxSize: component.iconName != nil ? CGSize(width: 22.0, height: 22.0) : nil
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 100, height: 100),
|
availableSize: CGSize(width: 100, height: 100),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
|
|
||||||
let padding: CGFloat = 12.0
|
let padding: CGFloat = 12.0
|
||||||
|
var leftPadding = padding
|
||||||
|
if let _ = component.iconName {
|
||||||
|
leftPadding -= 4.0
|
||||||
|
}
|
||||||
let spacing: CGFloat = 4.0
|
let spacing: CGFloat = 4.0
|
||||||
let totalWidth = title.size.width + icon.size.width + spacing
|
let totalWidth = title.size.width + icon.size.width + spacing
|
||||||
let size = CGSize(width: totalWidth + padding * 2.0, height: 28.0)
|
let size = CGSize(width: totalWidth + leftPadding + padding, height: 28.0)
|
||||||
let background = background.update(
|
let background = background.update(
|
||||||
component: RoundedRectangle(
|
component: RoundedRectangle(
|
||||||
color: component.backgroundColor,
|
color: component.backgroundColor,
|
||||||
@ -319,12 +334,21 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
context.add(background
|
context.add(background
|
||||||
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||||
)
|
)
|
||||||
context.add(title
|
if let _ = component.iconName {
|
||||||
.position(CGPoint(x: padding + title.size.width / 2.0, y: size.height / 2.0))
|
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: size.width - padding - icon.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))
|
||||||
|
)
|
||||||
|
}
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,509 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import AccountContext
|
||||||
|
import TelegramPresentationData
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
|
final class GiftAttributeListContextItem: ContextMenuCustomItem {
|
||||||
|
let context: AccountContext
|
||||||
|
let attributes: [StarGift.UniqueGift.Attribute]
|
||||||
|
let selectedAttributes: [ResaleGiftsContext.Attribute]
|
||||||
|
let attributeCount: [ResaleGiftsContext.Attribute: Int32]
|
||||||
|
let searchQuery: Signal<String, NoError>
|
||||||
|
let attributeSelected: (ResaleGiftsContext.Attribute, Bool) -> Void
|
||||||
|
let selectAll: () -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
context: AccountContext,
|
||||||
|
attributes: [StarGift.UniqueGift.Attribute],
|
||||||
|
selectedAttributes: [ResaleGiftsContext.Attribute],
|
||||||
|
attributeCount: [ResaleGiftsContext.Attribute: Int32],
|
||||||
|
searchQuery: Signal<String, NoError>,
|
||||||
|
attributeSelected: @escaping (ResaleGiftsContext.Attribute, Bool) -> Void,
|
||||||
|
selectAll: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.attributes = attributes
|
||||||
|
self.selectedAttributes = selectedAttributes
|
||||||
|
self.attributeCount = attributeCount
|
||||||
|
self.searchQuery = searchQuery
|
||||||
|
self.attributeSelected = attributeSelected
|
||||||
|
self.selectAll = selectAll
|
||||||
|
}
|
||||||
|
|
||||||
|
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||||
|
return GiftAttributeListContextItemNode(
|
||||||
|
presentationData: presentationData,
|
||||||
|
item: self,
|
||||||
|
getController: getController,
|
||||||
|
actionSelected: actionSelected
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func actionForAttribute(attribute: StarGift.UniqueGift.Attribute, presentationData: PresentationData, selectedAttributes: Set<ResaleGiftsContext.Attribute>, searchQuery: String, item: GiftAttributeListContextItem, getController: @escaping () -> ContextControllerProtocol?) -> ContextMenuActionItem? {
|
||||||
|
let searchComponents = searchQuery.lowercased().components(separatedBy: .whitespaces).filter { !$0.isEmpty }
|
||||||
|
|
||||||
|
switch attribute {
|
||||||
|
case let .model(name, file, _), let .pattern(name, file, _):
|
||||||
|
let attributeId: ResaleGiftsContext.Attribute
|
||||||
|
if case .model = attribute {
|
||||||
|
attributeId = .model(file.fileId.id)
|
||||||
|
} else {
|
||||||
|
attributeId = .pattern(file.fileId.id)
|
||||||
|
}
|
||||||
|
let isSelected = selectedAttributes.isEmpty || selectedAttributes.contains(attributeId)
|
||||||
|
|
||||||
|
var entities: [MessageTextEntity] = []
|
||||||
|
var entityFiles: [Int64: TelegramMediaFile] = [:]
|
||||||
|
entities = [
|
||||||
|
MessageTextEntity(
|
||||||
|
range: 0..<1,
|
||||||
|
type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
entityFiles[file.fileId.id] = file
|
||||||
|
|
||||||
|
var title = "# \(name)"
|
||||||
|
var count = ""
|
||||||
|
if let counter = item.attributeCount[.model(file.fileId.id)] {
|
||||||
|
count = " \(presentationStringsFormattedNumber(counter, presentationData.dateTimeFormat.groupingSeparator))"
|
||||||
|
entities.append(
|
||||||
|
MessageTextEntity(
|
||||||
|
range: title.count ..< title.count + count.count,
|
||||||
|
type: .Italic
|
||||||
|
)
|
||||||
|
)
|
||||||
|
title += count
|
||||||
|
}
|
||||||
|
|
||||||
|
let words = title.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty }
|
||||||
|
var wordStartIndices: [String.Index] = []
|
||||||
|
var currentIndex = title.startIndex
|
||||||
|
|
||||||
|
for word in words {
|
||||||
|
while currentIndex < title.endIndex {
|
||||||
|
let range = title.range(of: word, range: currentIndex..<title.endIndex)
|
||||||
|
if let range = range {
|
||||||
|
wordStartIndices.append(range.lowerBound)
|
||||||
|
currentIndex = range.upperBound
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentIndex = title.index(after: currentIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (wordIndex, word) in words.enumerated() {
|
||||||
|
let lowercaseWord = word.lowercased()
|
||||||
|
for component in searchComponents {
|
||||||
|
if lowercaseWord.hasPrefix(component) {
|
||||||
|
let startIndex = wordStartIndices[wordIndex]
|
||||||
|
let prefixRange = startIndex..<title.index(startIndex, offsetBy: min(component.count, word.count))
|
||||||
|
|
||||||
|
entities.append(
|
||||||
|
MessageTextEntity(
|
||||||
|
range: title.distance(from: title.startIndex, to: prefixRange.lowerBound)..<title.distance(from: title.startIndex, to: prefixRange.upperBound),
|
||||||
|
type: .Bold
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContextMenuActionItem(text: title, entities: entities, entityFiles: entityFiles, enableEntityAnimations: false, parseMarkdown: true, icon: { theme in
|
||||||
|
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||||
|
}, action: { _, f in
|
||||||
|
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||||
|
|
||||||
|
item.attributeSelected(attributeId, false)
|
||||||
|
}, longPressAction: { _, f in
|
||||||
|
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||||
|
|
||||||
|
item.attributeSelected(attributeId, true)
|
||||||
|
})
|
||||||
|
case let .backdrop(name, id, innerColor, outerColor, _, _, _):
|
||||||
|
let attributeId: ResaleGiftsContext.Attribute = .backdrop(id)
|
||||||
|
let isSelected = selectedAttributes.isEmpty || selectedAttributes.contains(attributeId)
|
||||||
|
|
||||||
|
var entities: [MessageTextEntity] = []
|
||||||
|
var title = " \(name)"
|
||||||
|
var count = ""
|
||||||
|
if let counter = item.attributeCount[attributeId] {
|
||||||
|
count = " \(presentationStringsFormattedNumber(counter, presentationData.dateTimeFormat.groupingSeparator))"
|
||||||
|
entities.append(
|
||||||
|
MessageTextEntity(range: title.count ..< title.count + count.count, type: .Italic)
|
||||||
|
)
|
||||||
|
title += count
|
||||||
|
}
|
||||||
|
|
||||||
|
let words = title.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty }
|
||||||
|
var wordStartIndices: [String.Index] = []
|
||||||
|
var currentIndex = title.startIndex
|
||||||
|
|
||||||
|
for word in words {
|
||||||
|
while currentIndex < title.endIndex {
|
||||||
|
let range = title.range(of: word, range: currentIndex..<title.endIndex)
|
||||||
|
if let range = range {
|
||||||
|
wordStartIndices.append(range.lowerBound)
|
||||||
|
currentIndex = range.upperBound
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentIndex = title.index(after: currentIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (wordIndex, word) in words.enumerated() {
|
||||||
|
let lowercaseWord = word.lowercased()
|
||||||
|
for component in searchComponents {
|
||||||
|
if lowercaseWord.hasPrefix(component) {
|
||||||
|
let startIndex = wordStartIndices[wordIndex]
|
||||||
|
let prefixRange = startIndex..<title.index(startIndex, offsetBy: min(component.count, word.count))
|
||||||
|
|
||||||
|
entities.append(
|
||||||
|
MessageTextEntity(
|
||||||
|
range: title.distance(from: title.startIndex, to: prefixRange.lowerBound)..<title.distance(from: title.startIndex, to: prefixRange.upperBound),
|
||||||
|
type: .Bold
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContextMenuActionItem(text: title, entities: entities, icon: { theme in
|
||||||
|
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||||
|
}, additionalLeftIcon: { _ in
|
||||||
|
return generateGradientFilledCircleImage(diameter: 24.0, colors: [UIColor(rgb: UInt32(bitPattern: innerColor)).cgColor, UIColor(rgb: UInt32(bitPattern: outerColor)).cgColor])
|
||||||
|
}, action: { _, f in
|
||||||
|
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||||
|
|
||||||
|
item.attributeSelected(attributeId, false)
|
||||||
|
}, longPressAction: { _, f in
|
||||||
|
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||||
|
|
||||||
|
item.attributeSelected(attributeId, true)
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol, ASScrollViewDelegate {
|
||||||
|
private let item: GiftAttributeListContextItem
|
||||||
|
private let presentationData: PresentationData
|
||||||
|
private let getController: () -> ContextControllerProtocol?
|
||||||
|
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||||
|
|
||||||
|
private let scrollNode: ASScrollNode
|
||||||
|
private let actionNodes: [ContextControllerActionsListActionItemNode]
|
||||||
|
private let separatorNodes: [ASDisplayNode]
|
||||||
|
|
||||||
|
private var searchDisposable: Disposable?
|
||||||
|
private var searchQuery = ""
|
||||||
|
|
||||||
|
init(presentationData: PresentationData, item: GiftAttributeListContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||||
|
self.item = item
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.getController = getController
|
||||||
|
self.actionSelected = actionSelected
|
||||||
|
|
||||||
|
self.scrollNode = ASScrollNode()
|
||||||
|
|
||||||
|
var actionNodes: [ContextControllerActionsListActionItemNode] = []
|
||||||
|
var separatorNodes: [ASDisplayNode] = []
|
||||||
|
|
||||||
|
let selectedAttributes = Set(item.selectedAttributes)
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let selectAllAction = ContextMenuActionItem(text: "Select All", 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: "No Results", 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
|
||||||
|
guard let self, self.searchQuery != searchQuery else {
|
||||||
|
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.getController()?.requestLayout(transition: .immediate)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.searchDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
|
||||||
|
self.scrollNode.view.alwaysBounceVertical = false
|
||||||
|
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||||
|
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)] = []
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
switch attribute {
|
||||||
|
case let .model(_, file, _):
|
||||||
|
return file.fileId.id
|
||||||
|
case let .pattern(_, file, _):
|
||||||
|
return file.fileId.id
|
||||||
|
case let .backdrop(_, id, _, _, _, _, _):
|
||||||
|
return id
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for i in 0 ..< self.actionNodes.count {
|
||||||
|
let itemNode = self.actionNodes[i]
|
||||||
|
if !self.searchQuery.isEmpty && i == 0 {
|
||||||
|
itemNode.isHidden = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTheme(presentationData: PresentationData) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var isActionEnabled: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func performAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIsHighlighted(_ value: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func canBeHighlighted() -> Bool {
|
||||||
|
return self.isActionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateIsHighlighted(isHighlighted: Bool) {
|
||||||
|
self.setIsHighlighted(isHighlighted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||||
|
// for actionNode in self.actionNodes {
|
||||||
|
// let frame = actionNode.convert(actionNode.bounds, to: self)
|
||||||
|
// if frame.contains(point) {
|
||||||
|
// return actionNode
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
actionNode.updateIsHighlighted(isHighlighted: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func stringTokens(_ string: String) -> [ValueBoxKey] {
|
||||||
|
let nsString = string.folding(options: .diacriticInsensitive, locale: .current).lowercased() as NSString
|
||||||
|
|
||||||
|
let flag = UInt(kCFStringTokenizerUnitWord)
|
||||||
|
let tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, nsString, CFRangeMake(0, nsString.length), flag, CFLocaleCopyCurrent())
|
||||||
|
var tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)
|
||||||
|
var tokens: [ValueBoxKey] = []
|
||||||
|
|
||||||
|
var addedTokens = Set<ValueBoxKey>()
|
||||||
|
while tokenType != [] {
|
||||||
|
let currentTokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer)
|
||||||
|
|
||||||
|
if currentTokenRange.location >= 0 && currentTokenRange.length != 0 {
|
||||||
|
let token = ValueBoxKey(length: currentTokenRange.length * 2)
|
||||||
|
nsString.getCharacters(token.memory.assumingMemoryBound(to: unichar.self), range: NSMakeRange(currentTokenRange.location, currentTokenRange.length))
|
||||||
|
if !addedTokens.contains(token) {
|
||||||
|
tokens.append(token)
|
||||||
|
addedTokens.insert(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
private func matchStringTokens(_ tokens: [ValueBoxKey], with other: [ValueBoxKey]) -> Bool {
|
||||||
|
if other.isEmpty {
|
||||||
|
return false
|
||||||
|
} else if other.count == 1 {
|
||||||
|
let otherToken = other[0]
|
||||||
|
for token in tokens {
|
||||||
|
if otherToken.isPrefix(to: token) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for otherToken in other {
|
||||||
|
var found = false
|
||||||
|
for token in tokens {
|
||||||
|
if otherToken.isPrefix(to: token) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func filteredAttributes(attributes: [StarGift.UniqueGift.Attribute], query: String) -> [StarGift.UniqueGift.Attribute] {
|
||||||
|
let queryTokens = stringTokens(query.lowercased())
|
||||||
|
|
||||||
|
var result: [StarGift.UniqueGift.Attribute] = []
|
||||||
|
for attribute in attributes {
|
||||||
|
let string: String
|
||||||
|
switch attribute {
|
||||||
|
case let .model(name, _, _):
|
||||||
|
string = name
|
||||||
|
case let .pattern(name, _, _):
|
||||||
|
string = name
|
||||||
|
case let .backdrop(name, _, _, _, _, _, _):
|
||||||
|
string = name
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let tokens = stringTokens(string)
|
||||||
|
if matchStringTokens(tokens, with: queryTokens) {
|
||||||
|
result.append(attribute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
@ -88,7 +88,6 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
|
|
||||||
private var starsItems: [AnyHashable: ComponentView<Empty>] = [:]
|
private var starsItems: [AnyHashable: ComponentView<Empty>] = [:]
|
||||||
private let filterSelector = ComponentView<Empty>()
|
private let filterSelector = ComponentView<Empty>()
|
||||||
private var isLoading = false
|
|
||||||
|
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
|
|
||||||
@ -144,48 +143,13 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
private var effectiveGifts: [StarGift]? {
|
private var effectiveGifts: [StarGift]? {
|
||||||
if let gifts = self.state?.starGiftsState?.gifts {
|
if let gifts = self.state?.starGiftsState?.gifts {
|
||||||
return gifts
|
return gifts
|
||||||
// if self.selectedModels.isEmpty && self.selectedBackdrops.isEmpty && self.selectedSymbols.isEmpty {
|
|
||||||
// return gifts
|
|
||||||
// } else if let (currentGifts, currentModels, currentBackdrops, currentSymbols) = self.currentGifts, currentModels == self.selectedModels && currentBackdrops == self.selectedBackdrops && currentSymbols == self.selectedSymbols {
|
|
||||||
// return currentGifts
|
|
||||||
// } else {
|
|
||||||
// var filteredGifts: [StarGift] = []
|
|
||||||
// for gift in gifts {
|
|
||||||
// guard case let .unique(uniqueGift) = gift else {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// var match = true
|
|
||||||
// for attribute in uniqueGift.attributes {
|
|
||||||
// if case let .model(name, _, _) = attribute {
|
|
||||||
// if !self.selectedModels.isEmpty && !self.selectedModels.contains(name) {
|
|
||||||
// match = false
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if case let .backdrop(name, _, _, _, _, _, _) = attribute {
|
|
||||||
// if !self.selectedBackdrops.isEmpty && !self.selectedBackdrops.contains(name) {
|
|
||||||
// match = false
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if case let .pattern(name, _, _) = attribute {
|
|
||||||
// if !self.selectedSymbols.isEmpty && !self.selectedSymbols.contains(name) {
|
|
||||||
// match = false
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if match {
|
|
||||||
// filteredGifts.append(gift)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// self.currentGifts = (filteredGifts, self.selectedModels, self.selectedBackdrops, self.selectedSymbols)
|
|
||||||
// return filteredGifts
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateScrolling(interactive: Bool = false, transition: ComponentTransition) {
|
private func updateScrolling(interactive: Bool = false, transition: ComponentTransition) {
|
||||||
guard let environment = self.environment, let component = self.component, !self.isLoading else {
|
guard let environment = self.environment, let component = self.component, self.state?.starGiftsState?.dataState != .loading else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +231,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
),
|
),
|
||||||
effectAlignment: .center,
|
effectAlignment: .center,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
if let self, let component = self.component {
|
if let self, let component = self.component, let state = self.state {
|
||||||
if let controller = controller() as? GiftStoreScreen {
|
if let controller = controller() as? GiftStoreScreen {
|
||||||
let mainController: ViewController
|
let mainController: ViewController
|
||||||
if let parentController = controller.parentController() {
|
if let parentController = controller.parentController() {
|
||||||
@ -277,7 +241,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
let giftController = GiftViewScreen(
|
let giftController = GiftViewScreen(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
subject: .uniqueGift(uniqueGift)
|
subject: .uniqueGift(uniqueGift, state.peerId)
|
||||||
)
|
)
|
||||||
mainController.push(giftController)
|
mainController.push(giftController)
|
||||||
}
|
}
|
||||||
@ -330,77 +294,6 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedModels = Set<String>()
|
|
||||||
var selectedBackdrops = Set<String>()
|
|
||||||
var selectedSymbols = Set<String>()
|
|
||||||
|
|
||||||
private func simulateLoading() {
|
|
||||||
self.isLoading = true
|
|
||||||
self.state?.updated(transition: .immediate)
|
|
||||||
|
|
||||||
Queue.mainQueue().after(1.0, {
|
|
||||||
self.isLoading = false
|
|
||||||
self.state?.updated(transition: .immediate)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func openContextMenu(sourceView: UIView) {
|
|
||||||
guard let component = self.component, let controller = self.environment?.controller() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Sort by Price", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SortValue"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { [weak self] _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
self?.state?.starGiftsContext.updateSorting(.value)
|
|
||||||
})))
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Sort by Date", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SortDate"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { [weak self] _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
self?.state?.starGiftsContext.updateSorting(.date)
|
|
||||||
})))
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Sort by Number", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SortNumber"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { [weak self] _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
self?.state?.starGiftsContext.updateSorting(.number)
|
|
||||||
})))
|
|
||||||
|
|
||||||
items.append(.separator)
|
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Model", textLayout: .secondLineWithValue("all models"), icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SortNumber"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
})))
|
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Backdrop", textLayout: .secondLineWithValue("all backdrops"), icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SortNumber"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
})))
|
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Symbol", textLayout: .secondLineWithValue("all symbols"), icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SortNumber"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
})))
|
|
||||||
|
|
||||||
let contextController = ContextController(context: component.context, presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
|
||||||
controller.presentInGlobalOverlay(contextController)
|
|
||||||
}
|
|
||||||
|
|
||||||
func openSortContextMenu(sourceView: UIView) {
|
func openSortContextMenu(sourceView: UIView) {
|
||||||
guard let component = self.component, let controller = self.environment?.controller() else {
|
guard let component = self.component, let controller = self.environment?.controller() else {
|
||||||
return
|
return
|
||||||
@ -441,28 +334,70 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let searchQueryPromise = ValuePromise<String>("")
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
let attributes = self.state?.starGiftsState?.attributes ?? []
|
||||||
var allSelected = true
|
let modelAttributes = attributes.filter { attribute in
|
||||||
|
if case .model = attribute {
|
||||||
var currentFilterAttributes: [ResaleGiftsContext.Attribute] = []
|
return true
|
||||||
var selectedIds = Set<Int64>()
|
} else {
|
||||||
|
return false
|
||||||
if let filterAttributes = self.state?.starGiftsState?.filterAttributes {
|
|
||||||
currentFilterAttributes = filterAttributes
|
|
||||||
for attribute in filterAttributes {
|
|
||||||
if case let .model(id) = attribute {
|
|
||||||
allSelected = false
|
|
||||||
selectedIds.insert(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.append(.action(ContextMenuActionItem(text: "Select All", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
let currentFilterAttributes = self.state?.starGiftsState?.filterAttributes ?? []
|
||||||
}, iconPosition: .left, action: { [weak self] _, f in
|
let selectedModelAttributes = currentFilterAttributes.filter { attribute in
|
||||||
f(.default)
|
if case .model = attribute {
|
||||||
|
return true
|
||||||
if let self {
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
items.append(.custom(SearchContextItem(
|
||||||
|
context: component.context,
|
||||||
|
placeholder: "Search",
|
||||||
|
value: "",
|
||||||
|
valueChanged: { value in
|
||||||
|
searchQueryPromise.set(value)
|
||||||
|
}
|
||||||
|
), false))
|
||||||
|
items.append(.separator)
|
||||||
|
items.append(.custom(GiftAttributeListContextItem(
|
||||||
|
context: component.context,
|
||||||
|
attributes: modelAttributes,
|
||||||
|
selectedAttributes: selectedModelAttributes,
|
||||||
|
attributeCount: self.state?.starGiftsState?.attributeCount ?? [:],
|
||||||
|
searchQuery: searchQueryPromise.get(),
|
||||||
|
attributeSelected: { [weak self] attribute, exclusive in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var updatedFilterAttributes: [ResaleGiftsContext.Attribute]
|
||||||
|
if exclusive {
|
||||||
|
updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
||||||
|
if case .model = attribute {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
updatedFilterAttributes.append(attribute)
|
||||||
|
} else {
|
||||||
|
updatedFilterAttributes = currentFilterAttributes
|
||||||
|
if selectedModelAttributes.contains(attribute) {
|
||||||
|
updatedFilterAttributes.removeAll(where: { $0 == attribute })
|
||||||
|
} else {
|
||||||
|
updatedFilterAttributes.append(attribute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
||||||
|
},
|
||||||
|
selectAll: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
let updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
||||||
if case .model = attribute {
|
if case .model = attribute {
|
||||||
return false
|
return false
|
||||||
@ -471,65 +406,15 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
||||||
}
|
}
|
||||||
})))
|
), false))
|
||||||
|
|
||||||
if let attributes = self.state?.starGiftsState?.attributes {
|
let contextController = ContextController(
|
||||||
for attribute in attributes {
|
context: component.context,
|
||||||
if case let .model(name, file, _) = attribute {
|
presentationData: presentationData,
|
||||||
let isSelected = allSelected || selectedIds.contains(file.fileId.id)
|
source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)),
|
||||||
|
items: .single(ContextController.Items(content: .list(items))),
|
||||||
var entities: [MessageTextEntity] = []
|
gesture: nil
|
||||||
var entityFiles: [Int64: TelegramMediaFile] = [:]
|
)
|
||||||
entities = [
|
|
||||||
MessageTextEntity(
|
|
||||||
range: 0..<1,
|
|
||||||
type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
entityFiles[file.fileId.id] = file
|
|
||||||
|
|
||||||
var title = "# \(name)"
|
|
||||||
var count = ""
|
|
||||||
if let counter = self.state?.starGiftsState?.attributeCount[.model(file.fileId.id)] {
|
|
||||||
count = " \(presentationStringsFormattedNumber(counter, presentationData.dateTimeFormat.groupingSeparator))"
|
|
||||||
entities.append(
|
|
||||||
MessageTextEntity(range: title.count ..< title.count + count.count, type: .Bold)
|
|
||||||
)
|
|
||||||
title += count
|
|
||||||
}
|
|
||||||
items.append(.action(ContextMenuActionItem(text: title, entities: entities, entityFiles: entityFiles, enableEntityAnimations: false, parseMarkdown: true, icon: { theme in
|
|
||||||
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
|
||||||
}, action: { [weak self] _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
if let self {
|
|
||||||
var updatedFilterAttributes = currentFilterAttributes
|
|
||||||
if selectedIds.contains(file.fileId.id) {
|
|
||||||
updatedFilterAttributes.removeAll(where: { $0 == .model(file.fileId.id) })
|
|
||||||
} else {
|
|
||||||
updatedFilterAttributes.append(.model(file.fileId.id))
|
|
||||||
}
|
|
||||||
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
|
||||||
}
|
|
||||||
}, longPressAction: { [weak self] _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
if let self {
|
|
||||||
var updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
|
||||||
if case .model = attribute {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
updatedFilterAttributes.append(.model(file.fileId.id))
|
|
||||||
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let contextController = ContextController(context: component.context, presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
|
||||||
controller.presentInGlobalOverlay(contextController)
|
controller.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,28 +424,70 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let searchQueryPromise = ValuePromise<String>("")
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
let attributes = self.state?.starGiftsState?.attributes ?? []
|
||||||
var allSelected = true
|
let backdropAttributes = attributes.filter { attribute in
|
||||||
|
if case .backdrop = attribute {
|
||||||
var currentFilterAttributes: [ResaleGiftsContext.Attribute] = []
|
return true
|
||||||
var selectedIds = Set<Int32>()
|
} else {
|
||||||
|
return false
|
||||||
if let filterAttributes = self.state?.starGiftsState?.filterAttributes {
|
|
||||||
currentFilterAttributes = filterAttributes
|
|
||||||
for attribute in filterAttributes {
|
|
||||||
if case let .backdrop(id) = attribute {
|
|
||||||
allSelected = false
|
|
||||||
selectedIds.insert(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.append(.action(ContextMenuActionItem(text: "Select All", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
let currentFilterAttributes = self.state?.starGiftsState?.filterAttributes ?? []
|
||||||
}, iconPosition: .left, action: { [weak self] _, f in
|
let selectedBackdropAttributes = currentFilterAttributes.filter { attribute in
|
||||||
f(.default)
|
if case .backdrop = attribute {
|
||||||
|
return true
|
||||||
if let self {
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
items.append(.custom(SearchContextItem(
|
||||||
|
context: component.context,
|
||||||
|
placeholder: "Search",
|
||||||
|
value: "",
|
||||||
|
valueChanged: { value in
|
||||||
|
searchQueryPromise.set(value)
|
||||||
|
}
|
||||||
|
), false))
|
||||||
|
items.append(.separator)
|
||||||
|
items.append(.custom(GiftAttributeListContextItem(
|
||||||
|
context: component.context,
|
||||||
|
attributes: backdropAttributes,
|
||||||
|
selectedAttributes: selectedBackdropAttributes,
|
||||||
|
attributeCount: self.state?.starGiftsState?.attributeCount ?? [:],
|
||||||
|
searchQuery: searchQueryPromise.get(),
|
||||||
|
attributeSelected: { [weak self] attribute, exclusive in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var updatedFilterAttributes: [ResaleGiftsContext.Attribute]
|
||||||
|
if exclusive {
|
||||||
|
updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
||||||
|
if case .backdrop = attribute {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
updatedFilterAttributes.append(attribute)
|
||||||
|
} else {
|
||||||
|
updatedFilterAttributes = currentFilterAttributes
|
||||||
|
if selectedBackdropAttributes.contains(attribute) {
|
||||||
|
updatedFilterAttributes.removeAll(where: { $0 == attribute })
|
||||||
|
} else {
|
||||||
|
updatedFilterAttributes.append(attribute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
||||||
|
},
|
||||||
|
selectAll: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
let updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
||||||
if case .backdrop = attribute {
|
if case .backdrop = attribute {
|
||||||
return false
|
return false
|
||||||
@ -569,58 +496,15 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
||||||
}
|
}
|
||||||
})))
|
), false))
|
||||||
|
|
||||||
if let attributes = self.state?.starGiftsState?.attributes {
|
let contextController = ContextController(
|
||||||
for attribute in attributes {
|
context: component.context,
|
||||||
if case let .backdrop(name, id, innerColor, outerColor, _, _, _) = attribute {
|
presentationData: presentationData,
|
||||||
let isSelected = allSelected || selectedIds.contains(id)
|
source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)),
|
||||||
|
items: .single(ContextController.Items(content: .list(items))),
|
||||||
var entities: [MessageTextEntity] = []
|
gesture: nil
|
||||||
var title = "\(name)"
|
)
|
||||||
var count = ""
|
|
||||||
if let counter = self.state?.starGiftsState?.attributeCount[.backdrop(id)] {
|
|
||||||
count = " \(presentationStringsFormattedNumber(counter, presentationData.dateTimeFormat.groupingSeparator))"
|
|
||||||
entities.append(
|
|
||||||
MessageTextEntity(range: title.count ..< title.count + count.count, type: .Bold)
|
|
||||||
)
|
|
||||||
title += count
|
|
||||||
}
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "\(name)\(count)", entities: entities, icon: { theme in
|
|
||||||
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
|
||||||
}, additionalLeftIcon: { _ in
|
|
||||||
return generateGradientFilledCircleImage(diameter: 24.0, colors: [UIColor(rgb: UInt32(bitPattern: innerColor)).cgColor, UIColor(rgb: UInt32(bitPattern: outerColor)).cgColor])
|
|
||||||
}, action: { [weak self] _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
if let self {
|
|
||||||
var updatedFilterAttributes = currentFilterAttributes
|
|
||||||
if selectedIds.contains(id) {
|
|
||||||
updatedFilterAttributes.removeAll(where: { $0 == .backdrop(id) })
|
|
||||||
} else {
|
|
||||||
updatedFilterAttributes.append(.backdrop(id))
|
|
||||||
}
|
|
||||||
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
|
||||||
}
|
|
||||||
}, longPressAction: { [weak self] _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
if let self {
|
|
||||||
var updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
|
||||||
if case .backdrop = attribute {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
updatedFilterAttributes.append(.backdrop(id))
|
|
||||||
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let contextController = ContextController(context: component.context, presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
|
||||||
controller.presentInGlobalOverlay(contextController)
|
controller.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -630,28 +514,70 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let searchQueryPromise = ValuePromise<String>("")
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
let attributes = self.state?.starGiftsState?.attributes ?? []
|
||||||
var allSelected = true
|
let patternAttributes = attributes.filter { attribute in
|
||||||
|
if case .pattern = attribute {
|
||||||
var currentFilterAttributes: [ResaleGiftsContext.Attribute] = []
|
return true
|
||||||
var selectedIds = Set<Int64>()
|
} else {
|
||||||
|
return false
|
||||||
if let filterAttributes = self.state?.starGiftsState?.filterAttributes {
|
|
||||||
currentFilterAttributes = filterAttributes
|
|
||||||
for attribute in filterAttributes {
|
|
||||||
if case let .pattern(id) = attribute {
|
|
||||||
allSelected = false
|
|
||||||
selectedIds.insert(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.append(.action(ContextMenuActionItem(text: "Select All", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
let currentFilterAttributes = self.state?.starGiftsState?.filterAttributes ?? []
|
||||||
}, iconPosition: .left, action: { [weak self] _, f in
|
let selectedPatternAttributes = currentFilterAttributes.filter { attribute in
|
||||||
f(.default)
|
if case .pattern = attribute {
|
||||||
|
return true
|
||||||
if let self {
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
items.append(.custom(SearchContextItem(
|
||||||
|
context: component.context,
|
||||||
|
placeholder: "Search",
|
||||||
|
value: "",
|
||||||
|
valueChanged: { value in
|
||||||
|
searchQueryPromise.set(value)
|
||||||
|
}
|
||||||
|
), false))
|
||||||
|
items.append(.separator)
|
||||||
|
items.append(.custom(GiftAttributeListContextItem(
|
||||||
|
context: component.context,
|
||||||
|
attributes: patternAttributes,
|
||||||
|
selectedAttributes: selectedPatternAttributes,
|
||||||
|
attributeCount: self.state?.starGiftsState?.attributeCount ?? [:],
|
||||||
|
searchQuery: searchQueryPromise.get(),
|
||||||
|
attributeSelected: { [weak self] attribute, exclusive in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var updatedFilterAttributes: [ResaleGiftsContext.Attribute]
|
||||||
|
if exclusive {
|
||||||
|
updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
||||||
|
if case .pattern = attribute {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
updatedFilterAttributes.append(attribute)
|
||||||
|
} else {
|
||||||
|
updatedFilterAttributes = currentFilterAttributes
|
||||||
|
if selectedPatternAttributes.contains(attribute) {
|
||||||
|
updatedFilterAttributes.removeAll(where: { $0 == attribute })
|
||||||
|
} else {
|
||||||
|
updatedFilterAttributes.append(attribute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
||||||
|
},
|
||||||
|
selectAll: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
let updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
||||||
if case .pattern = attribute {
|
if case .pattern = attribute {
|
||||||
return false
|
return false
|
||||||
@ -660,65 +586,15 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
||||||
}
|
}
|
||||||
})))
|
), false))
|
||||||
|
|
||||||
if let attributes = self.state?.starGiftsState?.attributes {
|
let contextController = ContextController(
|
||||||
for attribute in attributes {
|
context: component.context,
|
||||||
if case let .pattern(name, file, _) = attribute {
|
presentationData: presentationData,
|
||||||
let isSelected = allSelected || selectedIds.contains(file.fileId.id)
|
source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)),
|
||||||
|
items: .single(ContextController.Items(content: .list(items))),
|
||||||
var entities: [MessageTextEntity] = []
|
gesture: nil
|
||||||
var entityFiles: [Int64: TelegramMediaFile] = [:]
|
)
|
||||||
entities = [
|
|
||||||
MessageTextEntity(
|
|
||||||
range: 0..<1,
|
|
||||||
type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
entityFiles[file.fileId.id] = file
|
|
||||||
|
|
||||||
var title = "# \(name)"
|
|
||||||
var count = ""
|
|
||||||
if let counter = self.state?.starGiftsState?.attributeCount[.pattern(file.fileId.id)] {
|
|
||||||
count = " \(presentationStringsFormattedNumber(counter, presentationData.dateTimeFormat.groupingSeparator))"
|
|
||||||
entities.append(
|
|
||||||
MessageTextEntity(range: title.count ..< title.count + count.count, type: .Bold)
|
|
||||||
)
|
|
||||||
title += count
|
|
||||||
}
|
|
||||||
items.append(.action(ContextMenuActionItem(text: title, entities: entities, entityFiles: entityFiles, enableEntityAnimations: false, icon: { theme in
|
|
||||||
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
|
||||||
}, action: { [weak self] _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
if let self {
|
|
||||||
var updatedFilterAttributes = currentFilterAttributes
|
|
||||||
if selectedIds.contains(file.fileId.id) {
|
|
||||||
updatedFilterAttributes.removeAll(where: { $0 == .pattern(file.fileId.id) })
|
|
||||||
} else {
|
|
||||||
updatedFilterAttributes.append(.pattern(file.fileId.id))
|
|
||||||
}
|
|
||||||
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
|
||||||
}
|
|
||||||
}, longPressAction: { [weak self] _, f in
|
|
||||||
f(.default)
|
|
||||||
|
|
||||||
if let self {
|
|
||||||
var updatedFilterAttributes = currentFilterAttributes.filter { attribute in
|
|
||||||
if case .pattern = attribute {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
updatedFilterAttributes.append(.pattern(file.fileId.id))
|
|
||||||
self.state?.starGiftsContext.updateFilterAttributes(updatedFilterAttributes)
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let contextController = ContextController(context: component.context, presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
|
||||||
controller.presentInGlobalOverlay(contextController)
|
controller.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -729,7 +605,6 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let environment = environment[EnvironmentType.self].value
|
let environment = environment[EnvironmentType.self].value
|
||||||
let controller = environment.controller
|
|
||||||
let themeUpdated = self.environment?.theme !== environment.theme
|
let themeUpdated = self.environment?.theme !== environment.theme
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
self.state = state
|
self.state = state
|
||||||
@ -785,6 +660,9 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height))
|
let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height))
|
||||||
if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view {
|
if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view {
|
||||||
if topPanelView.superview == nil {
|
if topPanelView.superview == nil {
|
||||||
|
topPanelView.alpha = 0.0
|
||||||
|
topSeparatorView.alpha = 0.0
|
||||||
|
|
||||||
self.addSubview(topPanelView)
|
self.addSubview(topPanelView)
|
||||||
self.addSubview(topSeparatorView)
|
self.addSubview(topSeparatorView)
|
||||||
}
|
}
|
||||||
@ -792,51 +670,19 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
transition.setFrame(view: topSeparatorView, frame: topSeparatorFrame)
|
transition.setFrame(view: topSeparatorView, frame: topSeparatorFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancelButtonSize = self.cancelButton.update(
|
// let cancelButtonSize = self.cancelButton.update(
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(
|
|
||||||
PlainButtonComponent(
|
|
||||||
content: AnyComponent(
|
|
||||||
MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(string: strings.Common_Cancel, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)),
|
|
||||||
horizontalAlignment: .center
|
|
||||||
)
|
|
||||||
),
|
|
||||||
effectAlignment: .center,
|
|
||||||
action: {
|
|
||||||
controller()?.dismiss()
|
|
||||||
},
|
|
||||||
animateScale: false
|
|
||||||
)
|
|
||||||
),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
|
||||||
)
|
|
||||||
let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0 - cancelButtonSize.height / 2.0), size: cancelButtonSize)
|
|
||||||
if let cancelButtonView = self.cancelButton.view {
|
|
||||||
if cancelButtonView.superview == nil {
|
|
||||||
self.addSubview(cancelButtonView)
|
|
||||||
}
|
|
||||||
transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame)
|
|
||||||
}
|
|
||||||
|
|
||||||
// let showFilters = !"".isEmpty
|
|
||||||
|
|
||||||
// let sortButtonSize = self.sortButton.update(
|
|
||||||
// transition: transition,
|
// transition: transition,
|
||||||
// component: AnyComponent(
|
// component: AnyComponent(
|
||||||
// PlainButtonComponent(
|
// PlainButtonComponent(
|
||||||
// content: AnyComponent(
|
// content: AnyComponent(
|
||||||
// BundleIconComponent(
|
// MultilineTextComponent(
|
||||||
// name: "Peer Info/SortIcon",
|
// text: .plain(NSAttributedString(string: strings.Common_Cancel, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)),
|
||||||
// tintColor: theme.rootController.navigationBar.accentTextColor
|
// horizontalAlignment: .center
|
||||||
// )
|
// )
|
||||||
// ),
|
// ),
|
||||||
// effectAlignment: .center,
|
// effectAlignment: .center,
|
||||||
// action: { [weak self] in
|
// action: {
|
||||||
// if let sourceView = self?.sortButton.view {
|
// controller()?.dismiss()
|
||||||
// self?.openContextMenu(sourceView: sourceView)
|
|
||||||
// }
|
|
||||||
// },
|
// },
|
||||||
// animateScale: false
|
// animateScale: false
|
||||||
// )
|
// )
|
||||||
@ -844,15 +690,14 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
// environment: {},
|
// environment: {},
|
||||||
// containerSize: CGSize(width: availableSize.width, height: 100.0)
|
// containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||||
// )
|
// )
|
||||||
// let sortButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - sortButtonSize.width - 10.0, y: environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0 - sortButtonSize.height / 2.0), size: sortButtonSize)
|
// let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0 - cancelButtonSize.height / 2.0), size: cancelButtonSize)
|
||||||
// if let sortButtonView = self.sortButton.view {
|
// if let cancelButtonView = self.cancelButton.view {
|
||||||
// if sortButtonView.superview == nil {
|
// if cancelButtonView.superview == nil {
|
||||||
// self.addSubview(sortButtonView)
|
// self.addSubview(cancelButtonView)
|
||||||
// }
|
// }
|
||||||
// transition.setFrame(view: sortButtonView, frame: sortButtonFrame)
|
// transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame)
|
||||||
// transition.setAlpha(view: sortButtonView, alpha: showFilters ? 0.0 : 1.0)
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let balanceTitleSize = self.balanceTitle.update(
|
let balanceTitleSize = self.balanceTitle.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
@ -902,16 +747,12 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
balanceValueView.bounds = CGRect(origin: .zero, size: balanceValueSize)
|
balanceValueView.bounds = CGRect(origin: .zero, size: balanceValueSize)
|
||||||
balanceIconView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel)
|
balanceIconView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel)
|
||||||
balanceIconView.bounds = CGRect(origin: .zero, size: balanceIconSize)
|
balanceIconView.bounds = CGRect(origin: .zero, size: balanceIconSize)
|
||||||
|
|
||||||
// transition.setAlpha(view: balanceTitleView, alpha: showFilters ? 1.0 : 0.0)
|
|
||||||
// transition.setAlpha(view: balanceValueView, alpha: showFilters ? 1.0 : 0.0)
|
|
||||||
// transition.setAlpha(view: balanceIconView, alpha: showFilters ? 1.0 : 0.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(string: "Gift Name", font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)),
|
text: .plain(NSAttributedString(string: component.gift.title ?? "Gift", font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)),
|
||||||
horizontalAlignment: .center
|
horizontalAlignment: .center
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -924,10 +765,19 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: 10.0), size: titleSize))
|
transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: 10.0), size: titleSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let effectiveCount: Int32
|
||||||
|
if let count = self.effectiveGifts?.count {
|
||||||
|
effectiveCount = Int32(count)
|
||||||
|
} else if let resale = component.gift.availability?.resale {
|
||||||
|
effectiveCount = Int32(resale)
|
||||||
|
} else {
|
||||||
|
effectiveCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
let subtitleSize = self.subtitle.update(
|
let subtitleSize = self.subtitle.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(BalancedTextComponent(
|
component: AnyComponent(BalancedTextComponent(
|
||||||
text: .plain(NSAttributedString(string: "\(self.effectiveGifts?.count ?? 0) for resale", font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)),
|
text: .plain(NSAttributedString(string: "\(effectiveCount) for resale", font: Font.regular(13.0), textColor: theme.rootController.navigationBar.secondaryTextColor)),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
)),
|
)),
|
||||||
@ -946,20 +796,25 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
let optionWidth = (availableSize.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
let optionWidth = (availableSize.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
||||||
|
|
||||||
var sortingTitle = "Date"
|
var sortingTitle = "Date"
|
||||||
|
var sortingIcon: String = "Peer Info/SortDate"
|
||||||
if let sorting = self.state?.starGiftsState?.sorting {
|
if let sorting = self.state?.starGiftsState?.sorting {
|
||||||
switch sorting {
|
switch sorting {
|
||||||
case .date:
|
case .date:
|
||||||
sortingTitle = "Date"
|
sortingTitle = "Date"
|
||||||
|
sortingIcon = "Peer Info/SortDate"
|
||||||
case .value:
|
case .value:
|
||||||
sortingTitle = "Price"
|
sortingTitle = "Price"
|
||||||
|
sortingIcon = "Peer Info/SortValue"
|
||||||
case .number:
|
case .number:
|
||||||
sortingTitle = "Number"
|
sortingTitle = "Number"
|
||||||
|
sortingIcon = "Peer Info/SortNumber"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var filterItems: [FilterSelectorComponent.Item] = []
|
var filterItems: [FilterSelectorComponent.Item] = []
|
||||||
filterItems.append(FilterSelectorComponent.Item(
|
filterItems.append(FilterSelectorComponent.Item(
|
||||||
id: AnyHashable(0),
|
id: AnyHashable(0),
|
||||||
|
iconName: sortingIcon,
|
||||||
title: sortingTitle,
|
title: sortingTitle,
|
||||||
action: { [weak self] view in
|
action: { [weak self] view in
|
||||||
if let self {
|
if let self {
|
||||||
@ -1043,7 +898,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
component: AnyComponent(FilterSelectorComponent(
|
component: AnyComponent(FilterSelectorComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
colors: FilterSelectorComponent.Colors(
|
colors: FilterSelectorComponent.Colors(
|
||||||
foreground: theme.list.itemSecondaryTextColor,
|
foreground: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.65),
|
||||||
background: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15)
|
background: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15)
|
||||||
),
|
),
|
||||||
items: filterItems
|
items: filterItems
|
||||||
@ -1081,7 +936,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
self.scrollView.contentSize = contentSize
|
self.scrollView.contentSize = contentSize
|
||||||
self.nextScrollTransition = nil
|
self.nextScrollTransition = nil
|
||||||
}
|
}
|
||||||
let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
|
let scrollInsets = UIEdgeInsets(top: topPanelHeight, left: 0.0, bottom: 0.0, right: 0.0)
|
||||||
if self.scrollView.scrollIndicatorInsets != scrollInsets {
|
if self.scrollView.scrollIndicatorInsets != scrollInsets {
|
||||||
self.scrollView.scrollIndicatorInsets = scrollInsets
|
self.scrollView.scrollIndicatorInsets = scrollInsets
|
||||||
}
|
}
|
||||||
@ -1113,7 +968,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
transition.setFrame(view: self.loadingNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight + 39.0 + 7.0), size: availableSize))
|
transition.setFrame(view: self.loadingNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight + 39.0 + 7.0), size: availableSize))
|
||||||
|
|
||||||
let fadeTransition = ComponentTransition.easeInOut(duration: 0.25)
|
let fadeTransition = ComponentTransition.easeInOut(duration: 0.25)
|
||||||
if let effectiveGifts = self.effectiveGifts, effectiveGifts.isEmpty && !self.isLoading {
|
if let effectiveGifts = self.effectiveGifts, effectiveGifts.isEmpty && self.state?.starGiftsState?.dataState != .loading {
|
||||||
let sideInset: CGFloat = 44.0
|
let sideInset: CGFloat = 44.0
|
||||||
let emptyAnimationHeight = 148.0
|
let emptyAnimationHeight = 148.0
|
||||||
let topInset: CGFloat = environment.navigationHeight + 39.0
|
let topInset: CGFloat = environment.navigationHeight + 39.0
|
||||||
@ -1149,10 +1004,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.selectedModels.removeAll()
|
self.state?.starGiftsContext.updateFilterAttributes([])
|
||||||
self.selectedBackdrops.removeAll()
|
|
||||||
self.selectedSymbols.removeAll()
|
|
||||||
self.simulateLoading()
|
|
||||||
},
|
},
|
||||||
animateScale: false
|
animateScale: false
|
||||||
)
|
)
|
||||||
@ -1205,6 +1057,8 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
view.bounds = CGRect(origin: .zero, size: emptyResultsActionFrame.size)
|
view.bounds = CGRect(origin: .zero, size: emptyResultsActionFrame.size)
|
||||||
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsActionFrame.center)
|
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsActionFrame.center)
|
||||||
|
|
||||||
|
view.alpha = self.state?.starGiftsState?.attributes.isEmpty == true ? 0.0 : 1.0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let view = self.emptyResultsAnimation.view {
|
if let view = self.emptyResultsAnimation.view {
|
||||||
@ -1234,6 +1088,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
|
|
||||||
final class State: ComponentState {
|
final class State: ComponentState {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
var peerId: EnginePeer.Id
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
|
|
||||||
fileprivate let starGiftsContext: ResaleGiftsContext
|
fileprivate let starGiftsContext: ResaleGiftsContext
|
||||||
@ -1241,9 +1096,11 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
|
peerId: EnginePeer.Id,
|
||||||
giftId: Int64
|
giftId: Int64
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.peerId = peerId
|
||||||
self.starGiftsContext = ResaleGiftsContext(account: context.account, giftId: giftId)
|
self.starGiftsContext = ResaleGiftsContext(account: context.account, giftId: giftId)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
@ -1264,7 +1121,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeState() -> State {
|
func makeState() -> State {
|
||||||
return State(context: self.context, giftId: self.gift.id)
|
return State(context: self.context, peerId: self.peerId, giftId: self.gift.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
@ -1292,11 +1149,10 @@ public class GiftStoreScreen: ViewControllerComponentContainer {
|
|||||||
starsContext: starsContext,
|
starsContext: starsContext,
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
gift: gift
|
gift: gift
|
||||||
), navigationBarAppearance: .none, theme: .default, updatedPresentationData: nil)
|
), navigationBarAppearance: .transparent, theme: .default, updatedPresentationData: nil)
|
||||||
|
|
||||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.context.sharedContext.currentPresentationData.with { $0 }.strings.Common_Back, style: .plain, target: nil, action: nil)
|
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.context.sharedContext.currentPresentationData.with { $0 }.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||||
|
|
||||||
|
|
||||||
self.scrollToTop = { [weak self] in
|
self.scrollToTop = { [weak self] in
|
||||||
guard let self, let componentView = self.node.hostView.componentView as? GiftStoreScreenComponent.View else {
|
guard let self, let componentView = self.node.hostView.componentView as? GiftStoreScreenComponent.View else {
|
||||||
return
|
return
|
||||||
@ -1332,6 +1188,8 @@ private final class GiftStoreReferenceContentSource: ContextReferenceContentSour
|
|||||||
private let controller: ViewController
|
private let controller: ViewController
|
||||||
private let sourceView: UIView
|
private let sourceView: UIView
|
||||||
|
|
||||||
|
let forceDisplayBelowKeyboard = true
|
||||||
|
|
||||||
init(controller: ViewController, sourceView: UIView) {
|
init(controller: ViewController, sourceView: UIView) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.sourceView = sourceView
|
self.sourceView = sourceView
|
||||||
|
@ -134,6 +134,7 @@ final class LoadingShimmerNode: ASDisplayNode {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.allowsGroupOpacity = true
|
||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.addSubnode(self.backgroundColorNode)
|
self.addSubnode(self.backgroundColorNode)
|
||||||
|
@ -0,0 +1,223 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import AccountContext
|
||||||
|
import TelegramPresentationData
|
||||||
|
import ContextUI
|
||||||
|
import TextFieldComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
|
||||||
|
final class SearchContextItem: ContextMenuCustomItem {
|
||||||
|
let context: AccountContext
|
||||||
|
let placeholder: String
|
||||||
|
let value: String
|
||||||
|
let valueChanged: (String) -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
context: AccountContext,
|
||||||
|
placeholder: String,
|
||||||
|
value: String,
|
||||||
|
valueChanged: @escaping (String) -> Void
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.placeholder = placeholder
|
||||||
|
self.value = value
|
||||||
|
self.valueChanged = valueChanged
|
||||||
|
}
|
||||||
|
|
||||||
|
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||||
|
return SearchContextItemNode(
|
||||||
|
presentationData: presentationData,
|
||||||
|
item: self,
|
||||||
|
getController: getController,
|
||||||
|
actionSelected: actionSelected
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class SearchContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol, ASScrollViewDelegate {
|
||||||
|
private let item: SearchContextItem
|
||||||
|
private let presentationData: PresentationData
|
||||||
|
private let getController: () -> ContextControllerProtocol?
|
||||||
|
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||||
|
|
||||||
|
private let state = EmptyComponentState()
|
||||||
|
private let icon = ComponentView<Empty>()
|
||||||
|
private let inputField = ComponentView<Empty>()
|
||||||
|
private let inputFieldExternalState = TextFieldComponent.ExternalState()
|
||||||
|
private let inputPlaceholderView = ComponentView<Empty>()
|
||||||
|
private let inputClear = ComponentView<Empty>()
|
||||||
|
private var inputText = ""
|
||||||
|
|
||||||
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
|
init(presentationData: PresentationData, item: SearchContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||||
|
self.item = item
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.getController = getController
|
||||||
|
self.actionSelected = actionSelected
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.state._updated = { [weak self] transition, _ in
|
||||||
|
guard let self, let size = self.validLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.internalUpdateLayout(size: size, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func internalUpdateLayout(size: CGSize, transition: ComponentTransition) {
|
||||||
|
let iconSize = self.icon.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Search", tintColor: self.presentationData.theme.contextMenu.primaryColor)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: size
|
||||||
|
)
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: 17.0, y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize)
|
||||||
|
if let iconView = self.icon.view {
|
||||||
|
if iconView.superview == nil {
|
||||||
|
self.view.addSubview(iconView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: iconView, frame: iconFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputInset: CGFloat = 42.0
|
||||||
|
|
||||||
|
self.inputField.parentState = self.state
|
||||||
|
let inputFieldSize = self.inputField.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(TextFieldComponent(
|
||||||
|
context: self.item.context,
|
||||||
|
theme: self.presentationData.theme,
|
||||||
|
strings: self.presentationData.strings,
|
||||||
|
externalState: self.inputFieldExternalState,
|
||||||
|
fontSize: self.presentationData.listsFontSize.baseDisplaySize,
|
||||||
|
textColor: self.presentationData.theme.contextMenu.primaryColor,
|
||||||
|
accentColor: self.presentationData.theme.contextMenu.primaryColor,
|
||||||
|
insets: UIEdgeInsets(top: 8.0, left: 2.0, bottom: 8.0, right: 2.0),
|
||||||
|
hideKeyboard: false,
|
||||||
|
customInputView: nil,
|
||||||
|
resetText: nil,
|
||||||
|
isOneLineWhenUnfocused: false,
|
||||||
|
emptyLineHandling: .notAllowed,
|
||||||
|
formatMenuAvailability: .none,
|
||||||
|
returnKeyType: .search,
|
||||||
|
lockedFormatAction: {
|
||||||
|
},
|
||||||
|
present: { _ in
|
||||||
|
},
|
||||||
|
paste: { _ in
|
||||||
|
},
|
||||||
|
returnKeyAction: nil,
|
||||||
|
backspaceKeyAction: nil
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width - inputInset - 40.0, height: size.height)
|
||||||
|
)
|
||||||
|
let inputFieldFrame = CGRect(origin: CGPoint(x: inputInset, y: floorToScreenPixels((size.height - inputFieldSize.height) / 2.0)), size: inputFieldSize)
|
||||||
|
if let inputFieldView = self.inputField.view as? TextFieldComponent.View {
|
||||||
|
if inputFieldView.superview == nil {
|
||||||
|
self.view.addSubview(inputFieldView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: inputFieldView, frame: inputFieldFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.inputText != self.inputFieldExternalState.text.string {
|
||||||
|
self.inputText = self.inputFieldExternalState.text.string
|
||||||
|
self.item.valueChanged(self.inputText)
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputPlaceholderSize = self.inputPlaceholderView.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
MultilineTextComponent(text: .plain(NSAttributedString(
|
||||||
|
string: self.item.placeholder,
|
||||||
|
font: Font.regular(self.presentationData.listsFontSize.baseDisplaySize),
|
||||||
|
textColor: self.presentationData.theme.contextMenu.secondaryColor
|
||||||
|
)))
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: size
|
||||||
|
)
|
||||||
|
let inputPlaceholderFrame = CGRect(origin: CGPoint(x: inputInset + 10.0, y: floorToScreenPixels(inputFieldFrame.midY - inputPlaceholderSize.height / 2.0)), size: inputPlaceholderSize)
|
||||||
|
if let inputPlaceholderView = self.inputPlaceholderView.view {
|
||||||
|
if inputPlaceholderView.superview == nil {
|
||||||
|
inputPlaceholderView.isUserInteractionEnabled = false
|
||||||
|
self.view.addSubview(inputPlaceholderView)
|
||||||
|
}
|
||||||
|
inputPlaceholderView.frame = inputPlaceholderFrame
|
||||||
|
inputPlaceholderView.isHidden = self.inputFieldExternalState.hasText
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputClearSize = self.inputClear.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
Button(
|
||||||
|
content: AnyComponent(
|
||||||
|
BundleIconComponent(name: "Components/Search Bar/Clear", tintColor: self.presentationData.theme.contextMenu.secondaryColor, maxSize: CGSize(width: 24.0, height: 24.0))
|
||||||
|
),
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let inputFieldView = self.inputField.view as? TextFieldComponent.View {
|
||||||
|
inputFieldView.updateText(NSAttributedString(), selectionRange: 0..<0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 30.0, height: 30.0)
|
||||||
|
)
|
||||||
|
let inputClearFrame = CGRect(origin: CGPoint(x: size.width - inputClearSize.width - 16.0, y: floorToScreenPixels(inputFieldFrame.midY - inputClearSize.height / 2.0)), size: inputClearSize)
|
||||||
|
if let inputClearView = self.inputClear.view {
|
||||||
|
if inputClearView.superview == nil {
|
||||||
|
self.view.addSubview(inputClearView)
|
||||||
|
}
|
||||||
|
inputClearView.frame = inputClearFrame
|
||||||
|
inputClearView.isHidden = !self.inputFieldExternalState.hasText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||||
|
let maxWidth: CGFloat = 220.0
|
||||||
|
let height: CGFloat = 42.0
|
||||||
|
|
||||||
|
return (CGSize(width: maxWidth, height: height), { size, transition in
|
||||||
|
self.validLayout = size
|
||||||
|
self.internalUpdateLayout(size: size, transition: ComponentTransition(transition))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTheme(presentationData: PresentationData) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var isActionEnabled: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func performAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIsHighlighted(_ value: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func canBeHighlighted() -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateIsHighlighted(isHighlighted: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,95 @@ import MoreButtonNode
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
final class PriceButtonComponent: Component {
|
||||||
|
let price: Int64
|
||||||
|
|
||||||
|
init(
|
||||||
|
price: Int64
|
||||||
|
) {
|
||||||
|
self.price = price
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: PriceButtonComponent, rhs: PriceButtonComponent) -> Bool {
|
||||||
|
return lhs.price == rhs.price
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIView {
|
||||||
|
private let backgroundView = UIView()
|
||||||
|
|
||||||
|
private let icon = UIImageView()
|
||||||
|
private let text = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var component: PriceButtonComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.backgroundView.clipsToBounds = true
|
||||||
|
self.addSubview(self.backgroundView)
|
||||||
|
|
||||||
|
self.icon.image = UIImage(bundleImageName: "Premium/Stars/ButtonStar")?.withRenderingMode(.alwaysTemplate)
|
||||||
|
self.backgroundView.addSubview(self.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: PriceButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
var backgroundSize = CGSize(width: 42.0, height: 30.0)
|
||||||
|
let textSize = self.text.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: "\(component.price)",
|
||||||
|
font: Font.semibold(11.0),
|
||||||
|
textColor: UIColor(rgb: 0xffffff)
|
||||||
|
))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: 32.0, y: floorToScreenPixels((backgroundSize.height - textSize.height) / 2.0)), size: textSize)
|
||||||
|
if let textView = self.text.view {
|
||||||
|
if textView.superview == nil {
|
||||||
|
self.backgroundView.addSubview(textView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: textView, frame: textFrame)
|
||||||
|
}
|
||||||
|
backgroundSize.width += textSize.width
|
||||||
|
|
||||||
|
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
|
||||||
|
|
||||||
|
let backgroundColor: UIColor = UIColor(rgb: 0xffffff, alpha: 0.1)
|
||||||
|
transition.setBackgroundColor(view: self.backgroundView, color: backgroundColor)
|
||||||
|
|
||||||
|
let backgroundFrame = CGRect(origin: .zero, size: backgroundSize)
|
||||||
|
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||||
|
|
||||||
|
if let iconSize = self.icon.image?.size {
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floorToScreenPixels((backgroundSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||||
|
transition.setFrame(view: self.icon, frame: iconFrame)
|
||||||
|
}
|
||||||
|
self.icon.tintColor = UIColor(rgb: 0xffffff)
|
||||||
|
|
||||||
|
return backgroundSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class ButtonsComponent: Component {
|
final class ButtonsComponent: Component {
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let isOverlay: Bool
|
let isOverlay: Bool
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2010,7 +2010,8 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
self.isSelectionPanelOpen = !self.isSelectionPanelOpen
|
self.isSelectionPanelOpen = !self.isSelectionPanelOpen
|
||||||
self.state?.updated()
|
self.state?.updated()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
animateAlpha: false
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 33.0, height: 33.0)
|
containerSize: CGSize(width: 33.0, height: 33.0)
|
||||||
|
@ -4867,6 +4867,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
return profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
|
return profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
|
||||||
},
|
},
|
||||||
|
buyGift: { [weak profileGifts] slug, peerId in
|
||||||
|
guard let profileGifts else {
|
||||||
|
return .never()
|
||||||
|
}
|
||||||
|
return profileGifts.buyStarGift(slug: slug, peerId: peerId)
|
||||||
|
},
|
||||||
shareStory: { [weak self] uniqueGift in
|
shareStory: { [weak self] uniqueGift in
|
||||||
guard let self, let controller = self.controller else {
|
guard let self, let controller = self.controller else {
|
||||||
return
|
return
|
||||||
@ -11118,7 +11124,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
giftsContext?.updateSorting(sorting == .date ? .value : .date)
|
giftsContext?.updateSorting(sorting == .date ? .value : .date)
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if hasPinnedGifts {
|
if hasPinnedGifts && hasVisibility {
|
||||||
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Reorder, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Reorder, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
|
@ -600,6 +600,12 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
}
|
}
|
||||||
return self.profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
|
return self.profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
|
||||||
},
|
},
|
||||||
|
buyGift: { [weak self] slug, peerId in
|
||||||
|
guard let self else {
|
||||||
|
return .never()
|
||||||
|
}
|
||||||
|
return self.profileGifts.buyStarGift(slug: slug, peerId: peerId)
|
||||||
|
},
|
||||||
updateResellStars: { [weak self] price in
|
updateResellStars: { [weak self] price in
|
||||||
guard let self, case let .unique(uniqueGift) = product.gift else {
|
guard let self, case let .unique(uniqueGift) = product.gift else {
|
||||||
return
|
return
|
||||||
|
@ -147,9 +147,9 @@ private final class SheetContent: CombinedComponent {
|
|||||||
minAmount = StarsAmount(value: 1, nanos: 0)
|
minAmount = StarsAmount(value: 1, nanos: 0)
|
||||||
maxAmount = withdrawConfiguration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
maxAmount = withdrawConfiguration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||||
amountLabel = nil
|
amountLabel = nil
|
||||||
case .starGiftResell:
|
case let .starGiftResell(update):
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
titleString = "Sell Gift"
|
titleString = update ? "Edit Price" : "Sell Gift"
|
||||||
amountTitle = "PRICE IN STARS"
|
amountTitle = "PRICE IN STARS"
|
||||||
amountPlaceholder = "Enter Price"
|
amountPlaceholder = "Enter Price"
|
||||||
|
|
||||||
@ -358,12 +358,6 @@ private final class SheetContent: CombinedComponent {
|
|||||||
buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string))
|
buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
}
|
}
|
||||||
|
|
||||||
// if let range = buttonAttributedString.string.range(of: "#"), let starImage = state.cachedStarImage?.0 {
|
|
||||||
// buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
|
|
||||||
// buttonAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: buttonAttributedString.string))
|
|
||||||
// buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string))
|
|
||||||
// }
|
|
||||||
|
|
||||||
let button = button.update(
|
let button = button.update(
|
||||||
component: ButtonComponent(
|
component: ButtonComponent(
|
||||||
background: ButtonComponent.Background(
|
background: ButtonComponent.Background(
|
||||||
@ -558,10 +552,11 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||||||
case accountWithdraw
|
case accountWithdraw
|
||||||
case paidMedia(Int64?)
|
case paidMedia(Int64?)
|
||||||
case reaction(Int64?)
|
case reaction(Int64?)
|
||||||
case starGiftResell
|
case starGiftResell(Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
private let mode: StarsWithdrawScreen.Mode
|
||||||
fileprivate let completion: (Int64) -> Void
|
fileprivate let completion: (Int64) -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@ -570,6 +565,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||||||
completion: @escaping (Int64) -> Void
|
completion: @escaping (Int64) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.mode = mode
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
|
|
||||||
super.init(
|
super.init(
|
||||||
@ -603,12 +599,17 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
func presentMinAmountTooltip(_ minAmount: Int64) {
|
func presentMinAmountTooltip(_ minAmount: Int64) {
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
var text = presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string
|
||||||
|
if case .starGiftResell = self.mode {
|
||||||
|
text = "You cannot sell gift for less than \(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount)))."
|
||||||
|
}
|
||||||
|
|
||||||
let resultController = UndoOverlayController(
|
let resultController = UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .image(
|
content: .image(
|
||||||
image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
|
image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
|
||||||
title: nil,
|
title: nil,
|
||||||
text: presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string,
|
text: text,
|
||||||
round: false,
|
round: false,
|
||||||
undoText: nil
|
undoText: nil
|
||||||
),
|
),
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Media Editor/Collage.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Editor/Collage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "combine.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Collage.imageset/combine.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Collage.imageset/combine.pdf
vendored
Normal file
Binary file not shown.
@ -2987,11 +2987,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
))
|
))
|
||||||
controller.navigationPresentation = .modal
|
controller.navigationPresentation = .modal
|
||||||
|
|
||||||
let _ = combineLatest(
|
let _ = (combineLatest(
|
||||||
queue: Queue.mainQueue(),
|
queue: Queue.mainQueue(),
|
||||||
controller.result,
|
controller.result,
|
||||||
options.get()
|
options.get())
|
||||||
).startStandalone(next: { [weak controller] result, options in
|
|> take(1)).startStandalone(next: { [weak controller] result, options in
|
||||||
if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext {
|
if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext {
|
||||||
if case .starGiftTransfer = source {
|
if case .starGiftTransfer = source {
|
||||||
presentTransferAlertImpl?(EnginePeer(peer))
|
presentTransferAlertImpl?(EnginePeer(peer))
|
||||||
@ -3275,7 +3275,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
let controller = GiftStoreScreen(context: context, starsContext: starsContext, peerId: peerId, gift: gift)
|
let controller = GiftStoreScreen(context: context, starsContext: starsContext, peerId: peerId, gift: gift)
|
||||||
controller.navigationPresentation = .modal
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3668,8 +3667,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return StarsWithdrawScreen(context: context, mode: .accountWithdraw, completion: completion)
|
return StarsWithdrawScreen(context: context, mode: .accountWithdraw, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeStarGiftResellScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController {
|
public func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController {
|
||||||
return StarsWithdrawScreen(context: context, mode: .starGiftResell, completion: completion)
|
return StarsWithdrawScreen(context: context, mode: .starGiftResell(update), completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController {
|
public func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController {
|
||||||
@ -3689,7 +3688,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, dismissed: (() -> Void)?) -> ViewController {
|
public func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, dismissed: (() -> Void)?) -> ViewController {
|
||||||
let controller = GiftViewScreen(context: context, subject: .uniqueGift(gift), shareStory: shareStory)
|
let controller = GiftViewScreen(context: context, subject: .uniqueGift(gift, nil), shareStory: shareStory)
|
||||||
controller.disposed = {
|
controller.disposed = {
|
||||||
dismissed?()
|
dismissed?()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user