mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 11:23:48 +00:00
873 lines
40 KiB
Swift
873 lines
40 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import AccountContext
|
|
import TelegramPresentationData
|
|
import PresentationDataUtils
|
|
import ComponentFlow
|
|
import ViewControllerComponent
|
|
import SheetComponent
|
|
import MultilineTextComponent
|
|
import MultilineTextWithEntitiesComponent
|
|
import BundleIconComponent
|
|
import ButtonComponent
|
|
import Markdown
|
|
import BalancedTextComponent
|
|
import AvatarNode
|
|
import TextFormat
|
|
import TelegramStringFormatting
|
|
import StarsAvatarComponent
|
|
import EmojiTextAttachmentView
|
|
import EmojiStatusComponent
|
|
import UndoUI
|
|
import PlainButtonComponent
|
|
import TooltipUI
|
|
import GiftAnimationComponent
|
|
import LottieComponent
|
|
import ContextUI
|
|
import TelegramNotices
|
|
import GiftItemComponent
|
|
|
|
private final class GiftValueSheetContent: CombinedComponent {
|
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
|
|
|
let context: AccountContext
|
|
let gift: StarGift
|
|
let valueInfo: StarGift.UniqueGift.ValueInfo
|
|
let animateOut: ActionSlot<Action<()>>
|
|
let getController: () -> ViewController?
|
|
|
|
init(
|
|
context: AccountContext,
|
|
gift: StarGift,
|
|
valueInfo: StarGift.UniqueGift.ValueInfo,
|
|
animateOut: ActionSlot<Action<()>>,
|
|
getController: @escaping () -> ViewController?
|
|
) {
|
|
self.context = context
|
|
self.gift = gift
|
|
self.valueInfo = valueInfo
|
|
self.animateOut = animateOut
|
|
self.getController = getController
|
|
}
|
|
|
|
static func ==(lhs: GiftValueSheetContent, rhs: GiftValueSheetContent) -> Bool {
|
|
if lhs.context !== rhs.context {
|
|
return false
|
|
}
|
|
if lhs.gift != rhs.gift {
|
|
return false
|
|
}
|
|
if lhs.valueInfo != rhs.valueInfo {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
final class State: ComponentState {
|
|
let lastSalePriceTag = GenericComponentViewTag()
|
|
let floorPriceTag = GenericComponentViewTag()
|
|
let averagePriceTag = GenericComponentViewTag()
|
|
|
|
private let context: AccountContext
|
|
private let animateOut: ActionSlot<Action<()>>
|
|
private let getController: () -> ViewController?
|
|
|
|
private var disposable: Disposable?
|
|
var initialized = false
|
|
|
|
var starGiftsMap: [Int64: StarGift.Gift] = [:]
|
|
|
|
var cachedStarImage: (UIImage, PresentationTheme)?
|
|
var cachedSmallStarImage: (UIImage, PresentationTheme)?
|
|
var cachedSubtitleStarImage: (UIImage, PresentationTheme)?
|
|
var cachedTonImage: (UIImage, PresentationTheme)?
|
|
|
|
var cachedChevronImage: (UIImage, PresentationTheme)?
|
|
var cachedSmallChevronImage: (UIImage, PresentationTheme)?
|
|
|
|
init(
|
|
context: AccountContext,
|
|
animateOut: ActionSlot<Action<()>>,
|
|
getController: @escaping () -> ViewController?
|
|
) {
|
|
self.context = context
|
|
self.animateOut = animateOut
|
|
self.getController = getController
|
|
|
|
super.init()
|
|
|
|
self.disposable = (context.engine.payments.cachedStarGifts()
|
|
|> deliverOnMainQueue).startStrict(next: { [weak self] starGifts in
|
|
if let strongSelf = self {
|
|
var starGiftsMap: [Int64: StarGift.Gift] = [:]
|
|
if let starGifts {
|
|
for gift in starGifts {
|
|
if case let .generic(gift) = gift {
|
|
starGiftsMap[gift.id] = gift
|
|
}
|
|
}
|
|
}
|
|
strongSelf.starGiftsMap = starGiftsMap
|
|
strongSelf.updated(transition: .immediate)
|
|
}
|
|
})
|
|
}
|
|
|
|
deinit {
|
|
self.disposable?.dispose()
|
|
}
|
|
|
|
func showAttributeInfo(tag: Any, text: String) {
|
|
guard let controller = self.getController() as? GiftValueScreen else {
|
|
return
|
|
}
|
|
controller.dismissAllTooltips()
|
|
|
|
guard let sourceView = controller.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: controller.view) else {
|
|
return
|
|
}
|
|
|
|
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
|
|
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .markdown(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
|
return .dismiss(consume: false)
|
|
})
|
|
controller.present(tooltipController, in: .current)
|
|
}
|
|
|
|
func openGiftResale(gift: StarGift.Gift) {
|
|
guard let controller = self.getController() as? GiftValueScreen else {
|
|
return
|
|
}
|
|
let storeController = self.context.sharedContext.makeGiftStoreController(
|
|
context: self.context,
|
|
peerId: self.context.account.peerId,
|
|
gift: gift
|
|
)
|
|
controller.push(storeController)
|
|
|
|
Queue.mainQueue().after(2.0, {
|
|
controller.dismiss(animated: false)
|
|
})
|
|
}
|
|
|
|
func openGiftFragmentResale(url: String) {
|
|
guard let controller = self.getController() as? GiftValueScreen, let navigationController = controller.navigationController as? NavigationController else {
|
|
return
|
|
}
|
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
|
}
|
|
|
|
func dismiss(animated: Bool) {
|
|
guard let controller = self.getController() as? GiftValueScreen else {
|
|
return
|
|
}
|
|
if animated {
|
|
controller.dismissAllTooltips()
|
|
self.animateOut.invoke(Action { [weak controller] _ in
|
|
controller?.dismiss(completion: nil)
|
|
})
|
|
} else {
|
|
controller.dismiss(animated: false)
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeState() -> State {
|
|
return State(context: self.context, animateOut: self.animateOut, getController: self.getController)
|
|
}
|
|
|
|
static var body: Body {
|
|
let buttons = Child(ButtonsComponent.self)
|
|
let animation = Child(GiftCompositionComponent.self)
|
|
|
|
let titleBackground = Child(RoundedRectangle.self)
|
|
let title = Child(MultilineTextComponent.self)
|
|
|
|
let description = Child(MultilineTextComponent.self)
|
|
|
|
let table = Child(TableComponent.self)
|
|
|
|
let telegramSaleButton = Child(PlainButtonComponent.self)
|
|
let fragmentSaleButton = Child(PlainButtonComponent.self)
|
|
|
|
let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
|
|
|
|
return { context in
|
|
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
|
|
|
let component = context.component
|
|
let theme = environment.theme
|
|
let strings = environment.strings
|
|
let dateTimeFormat = environment.dateTimeFormat
|
|
//let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder
|
|
//let controller = environment.controller
|
|
|
|
let state = context.state
|
|
|
|
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
|
|
|
let titleString: String = formatCurrencyAmount(component.valueInfo.value, currency: component.valueInfo.currency)
|
|
var giftTitle: String = ""
|
|
var giftCollectionTitle: String = ""
|
|
var animationFile: TelegramMediaFile?
|
|
var giftIconSubject: GiftItemComponent.Subject?
|
|
var genericGift: StarGift.Gift?
|
|
|
|
switch component.gift {
|
|
case let .generic(gift):
|
|
animationFile = gift.file
|
|
giftIconSubject = .starGift(gift: gift, price: "")
|
|
case let .unique(gift):
|
|
for attribute in gift.attributes {
|
|
if case let .model(_, file, _) = attribute {
|
|
animationFile = file
|
|
}
|
|
}
|
|
giftCollectionTitle = gift.title
|
|
giftTitle = "\(gift.title) #\(formatCollectibleNumber(gift.number, dateTimeFormat: dateTimeFormat))"
|
|
|
|
if let gift = state.starGiftsMap[gift.giftId] {
|
|
giftIconSubject = .starGift(gift: gift, price: "")
|
|
genericGift = gift
|
|
}
|
|
}
|
|
|
|
let buttons = buttons.update(
|
|
component: ButtonsComponent(
|
|
theme: theme,
|
|
isOverlay: false,
|
|
showMoreButton: false,
|
|
closePressed: { [weak state] in
|
|
guard let state else {
|
|
return
|
|
}
|
|
state.dismiss(animated: true)
|
|
},
|
|
morePressed: { _, _ in
|
|
}
|
|
),
|
|
availableSize: CGSize(width: 30.0, height: 30.0),
|
|
transition: context.transition
|
|
)
|
|
|
|
var originY: CGFloat = 0.0
|
|
|
|
let headerHeight: CGFloat = 210.0
|
|
let headerSubject: GiftCompositionComponent.Subject?
|
|
if let animationFile {
|
|
headerSubject = .generic(animationFile)
|
|
} else {
|
|
headerSubject = nil
|
|
}
|
|
|
|
if let headerSubject {
|
|
let animation = animation.update(
|
|
component: GiftCompositionComponent(
|
|
context: component.context,
|
|
theme: environment.theme,
|
|
subject: headerSubject,
|
|
animationOffset: nil,
|
|
animationScale: nil,
|
|
displayAnimationStars: false,
|
|
externalState: giftCompositionExternalState,
|
|
requestUpdate: { [weak state] in
|
|
state?.updated()
|
|
}
|
|
),
|
|
availableSize: CGSize(width: context.availableSize.width, height: headerHeight),
|
|
transition: context.transition
|
|
)
|
|
context.add(animation
|
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: headerHeight / 2.0))
|
|
)
|
|
}
|
|
originY += headerHeight
|
|
|
|
let title = title.update(
|
|
component: MultilineTextComponent(
|
|
text: .plain(NSAttributedString(
|
|
string: titleString,
|
|
font: Font.with(size: 24.0, design: .round, weight: .bold),
|
|
textColor: theme.list.itemCheckColors.foregroundColor,
|
|
paragraphAlignment: .center
|
|
)),
|
|
horizontalAlignment: .center,
|
|
maximumNumberOfLines: 1
|
|
),
|
|
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
|
|
transition: .immediate
|
|
)
|
|
let titleBackground = titleBackground.update(
|
|
component: RoundedRectangle(color: theme.actionSheet.controlAccentColor, cornerRadius: 24.0),
|
|
environment: {},
|
|
availableSize: CGSize(width: title.size.width + 32.0, height: 48.0),
|
|
transition: .immediate
|
|
)
|
|
context.add(titleBackground
|
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: 187.0))
|
|
)
|
|
|
|
context.add(title
|
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: 187.0))
|
|
)
|
|
|
|
var descriptionText: String
|
|
if component.valueInfo.valueIsAverage {
|
|
descriptionText = "This is the average sale price of **\(giftCollectionTitle)** on Telegram and Fragment over the past month."
|
|
} else {
|
|
if component.valueInfo.isLastSaleOnFragment {
|
|
descriptionText = "This is the last price at which **\(giftTitle)** was last sold on Fragment."
|
|
} else {
|
|
descriptionText = "This is the last price at which **\(giftTitle)** was last sold on Telegram."
|
|
}
|
|
}
|
|
if !descriptionText.isEmpty {
|
|
let linkColor = theme.actionSheet.controlAccentColor
|
|
if state.cachedSmallStarImage == nil || state.cachedSmallStarImage?.1 !== environment.theme {
|
|
state.cachedSmallStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/ButtonStar"), color: .white)!, theme)
|
|
}
|
|
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
|
|
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
|
|
}
|
|
|
|
let textFont = Font.regular(15.0)
|
|
let boldTextFont = Font.semibold(15.0)
|
|
let textColor = theme.list.itemPrimaryTextColor
|
|
|
|
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
|
return (TelegramTextAttributes.URL, contents)
|
|
})
|
|
|
|
descriptionText = descriptionText.replacingOccurrences(of: " >]", with: "\u{00A0}>]")
|
|
let attributedString = parseMarkdownIntoAttributedString(descriptionText, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
|
|
if let range = attributedString.string.range(of: "*"), let starImage = state.cachedSmallStarImage?.0 {
|
|
attributedString.addAttribute(.font, value: Font.regular(13.0), range: NSRange(range, in: attributedString.string))
|
|
attributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: attributedString.string))
|
|
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))
|
|
}
|
|
if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
|
|
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
|
|
}
|
|
|
|
let description = description.update(
|
|
component: MultilineTextComponent(
|
|
text: .plain(attributedString),
|
|
horizontalAlignment: .center,
|
|
maximumNumberOfLines: 5,
|
|
lineSpacing: 0.2,
|
|
highlightColor: linkColor.withAlphaComponent(0.1),
|
|
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
|
|
highlightAction: { attributes in
|
|
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
|
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
|
} else {
|
|
return nil
|
|
}
|
|
},
|
|
tapAction: { _, _ in
|
|
}
|
|
),
|
|
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
|
|
transition: .immediate
|
|
)
|
|
context.add(description
|
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: 231.0 + description.size.height / 2.0))
|
|
.appear(.default(alpha: true))
|
|
.disappear(.default(alpha: true))
|
|
)
|
|
originY += description.size.height
|
|
originY += 42.0
|
|
} else {
|
|
originY += 9.0
|
|
}
|
|
|
|
let tableFont = Font.regular(15.0)
|
|
let tableTextColor = theme.list.itemPrimaryTextColor
|
|
|
|
var tableItems: [TableComponent.Item] = []
|
|
|
|
tableItems.append(.init(
|
|
id: "initialDate",
|
|
title: "Initial Sale",
|
|
component: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: component.valueInfo.initialSaleDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
|
)
|
|
))
|
|
|
|
let valueString = "⭐️\(formatStarsAmountText(StarsAmount(value: component.valueInfo.initialSaleStars, nanos: 0), dateTimeFormat: dateTimeFormat)) (≈\(formatCurrencyAmount(component.valueInfo.initialSalePrice, currency: component.valueInfo.currency)))"
|
|
let valueAttributedString = NSMutableAttributedString(string: valueString, font: tableFont, textColor: tableTextColor)
|
|
let range = (valueAttributedString.string as NSString).range(of: "⭐️")
|
|
if range.location != NSNotFound {
|
|
valueAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
|
|
valueAttributedString.addAttribute(.baselineOffset, value: 1.0, range: range)
|
|
}
|
|
|
|
tableItems.append(.init(
|
|
id: "initialPrice",
|
|
title: "Initial Price",
|
|
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
|
context: component.context,
|
|
animationCache: component.context.animationCache,
|
|
animationRenderer: component.context.animationRenderer,
|
|
placeholderColor: theme.list.mediaPlaceholderColor,
|
|
text: .plain(valueAttributedString),
|
|
maximumNumberOfLines: 0
|
|
)),
|
|
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
|
|
))
|
|
|
|
if let lastSaleDate = component.valueInfo.lastSaleDate {
|
|
tableItems.append(.init(
|
|
id: "lastDate",
|
|
title: "Last Sale",
|
|
component: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: lastSaleDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
|
)
|
|
))
|
|
}
|
|
|
|
if let lastSalePrice = component.valueInfo.lastSalePrice {
|
|
let lastSalePriceString = formatCurrencyAmount(lastSalePrice, currency: component.valueInfo.currency)
|
|
let tag = state.lastSalePriceTag
|
|
var items: [AnyComponentWithIdentity<Empty>] = []
|
|
items.append(
|
|
AnyComponentWithIdentity(
|
|
id: AnyHashable(0),
|
|
component: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: lastSalePriceString, font: tableFont, textColor: tableTextColor)))
|
|
)
|
|
)
|
|
)
|
|
|
|
let percentage = Int32(floor(Double(lastSalePrice) / Double(component.valueInfo.initialSalePrice) * 100.0 - 100.0))
|
|
let percentageString = percentage > 0 ? "+\(percentage)" : "\(percentage)"
|
|
|
|
items.append(AnyComponentWithIdentity(
|
|
id: AnyHashable(1),
|
|
component: AnyComponent(Button(
|
|
content: AnyComponent(ButtonContentComponent(
|
|
context: component.context,
|
|
text: percentageString,
|
|
color: theme.list.itemAccentColor
|
|
)),
|
|
action: { [weak state] in
|
|
state?.showAttributeInfo(tag: tag, text: "**\(lastSalePriceString)** is the last price for \(giftCollectionTitle) gifts listed on Telegram and Fragment.")
|
|
|
|
}
|
|
).tagged(tag))
|
|
))
|
|
let itemComponent = AnyComponent(
|
|
HStack(items, spacing: 4.0)
|
|
)
|
|
tableItems.append(.init(
|
|
id: "lastPrice",
|
|
title: "Last Price",
|
|
hasBackground: false,
|
|
component: itemComponent
|
|
))
|
|
}
|
|
|
|
if let floorPrice = component.valueInfo.floorPrice {
|
|
let floorPriceString = formatCurrencyAmount(floorPrice, currency: component.valueInfo.currency)
|
|
let tag = state.floorPriceTag
|
|
var items: [AnyComponentWithIdentity<Empty>] = []
|
|
items.append(
|
|
AnyComponentWithIdentity(
|
|
id: AnyHashable(0),
|
|
component: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: floorPriceString, font: tableFont, textColor: tableTextColor)))
|
|
)
|
|
)
|
|
)
|
|
items.append(AnyComponentWithIdentity(
|
|
id: AnyHashable(1),
|
|
component: AnyComponent(Button(
|
|
content: AnyComponent(ButtonContentComponent(
|
|
context: component.context,
|
|
text: "?",
|
|
color: theme.list.itemAccentColor
|
|
)),
|
|
action: { [weak state] in
|
|
state?.showAttributeInfo(tag: tag, text: "**\(floorPriceString)** is the floor price for \(giftCollectionTitle) gifts listed on Telegram and Fragment.")
|
|
|
|
}
|
|
).tagged(tag))
|
|
))
|
|
let itemComponent = AnyComponent(
|
|
HStack(items, spacing: 4.0)
|
|
)
|
|
tableItems.append(.init(
|
|
id: "floorPrice",
|
|
title: "Minumum Price",
|
|
hasBackground: false,
|
|
component: itemComponent
|
|
))
|
|
}
|
|
|
|
if let averagePrice = component.valueInfo.averagePrice {
|
|
let averagePriceString = formatCurrencyAmount(averagePrice, currency: component.valueInfo.currency)
|
|
let tag = state.averagePriceTag
|
|
var items: [AnyComponentWithIdentity<Empty>] = []
|
|
items.append(
|
|
AnyComponentWithIdentity(
|
|
id: AnyHashable(0),
|
|
component: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: averagePriceString, font: tableFont, textColor: tableTextColor)))
|
|
)
|
|
)
|
|
)
|
|
items.append(AnyComponentWithIdentity(
|
|
id: AnyHashable(1),
|
|
component: AnyComponent(Button(
|
|
content: AnyComponent(ButtonContentComponent(
|
|
context: component.context,
|
|
text: "?",
|
|
color: theme.list.itemAccentColor
|
|
)),
|
|
action: { [weak state] in
|
|
state?.showAttributeInfo(tag: tag, text: "**\(averagePriceString)** is the average sale price of \(giftCollectionTitle) on Telegram and Fragment over the past month.")
|
|
}
|
|
).tagged(tag))
|
|
))
|
|
let itemComponent = AnyComponent(
|
|
HStack(items, spacing: 4.0)
|
|
)
|
|
tableItems.append(.init(
|
|
id: "averagePrice",
|
|
title: "Average Price",
|
|
hasBackground: false,
|
|
component: itemComponent
|
|
))
|
|
}
|
|
|
|
let table = table.update(
|
|
component: TableComponent(
|
|
theme: environment.theme,
|
|
items: tableItems
|
|
),
|
|
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
|
|
transition: context.transition
|
|
)
|
|
context.add(table
|
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0))
|
|
.appear(.default(alpha: true))
|
|
.disappear(.default(alpha: true))
|
|
)
|
|
originY += table.size.height + 23.0
|
|
|
|
if component.valueInfo.listedCount != nil || component.valueInfo.fragmentListedCount != nil {
|
|
originY += 5.0
|
|
}
|
|
|
|
if let listedCount = component.valueInfo.listedCount, let giftIconSubject {
|
|
let telegramSaleButton = telegramSaleButton.update(
|
|
component: PlainButtonComponent(
|
|
content: AnyComponent(
|
|
HStack([
|
|
AnyComponentWithIdentity(id: "count", component: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(listedCount, dateTimeFormat.groupingSeparator), font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
|
)),
|
|
AnyComponentWithIdentity(id: "spacing", component: AnyComponent(
|
|
Rectangle(color: .clear, width: 8.0, height: 1.0)
|
|
)),
|
|
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
|
|
GiftItemComponent(
|
|
context: component.context,
|
|
theme: theme,
|
|
strings: strings,
|
|
peer: nil,
|
|
subject: giftIconSubject,
|
|
mode: .buttonIcon
|
|
)
|
|
)),
|
|
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: " for sale on Telegram", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
|
)),
|
|
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
|
|
BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: theme.actionSheet.controlAccentColor)
|
|
))
|
|
], spacing: 0.0)
|
|
),
|
|
action: { [weak state] in
|
|
guard let state, let genericGift else {
|
|
return
|
|
}
|
|
state.openGiftResale(gift: genericGift)
|
|
},
|
|
animateScale: false
|
|
),
|
|
environment: {},
|
|
availableSize: context.availableSize,
|
|
transition: .immediate
|
|
)
|
|
context.add(telegramSaleButton
|
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + telegramSaleButton.size.height / 2.0))
|
|
)
|
|
originY += telegramSaleButton.size.height
|
|
originY += 12.0
|
|
}
|
|
|
|
if let listedCount = component.valueInfo.fragmentListedCount, let fragmentListedUrl = component.valueInfo.fragmentListedUrl, let giftIconSubject {
|
|
if component.valueInfo.listedCount != nil {
|
|
originY += 18.0
|
|
}
|
|
|
|
let fragmentSaleButton = fragmentSaleButton.update(
|
|
component: PlainButtonComponent(
|
|
content: AnyComponent(
|
|
HStack([
|
|
AnyComponentWithIdentity(id: "count", component: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(listedCount, dateTimeFormat.groupingSeparator), font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
|
)),
|
|
AnyComponentWithIdentity(id: "spacing", component: AnyComponent(
|
|
Rectangle(color: .clear, width: 8.0, height: 1.0)
|
|
)),
|
|
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
|
|
GiftItemComponent(
|
|
context: component.context,
|
|
theme: theme,
|
|
strings: strings,
|
|
peer: nil,
|
|
subject: giftIconSubject,
|
|
mode: .buttonIcon
|
|
)
|
|
)),
|
|
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
|
MultilineTextComponent(text: .plain(NSAttributedString(string: " for sale on Fragment", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
|
)),
|
|
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
|
|
BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: theme.actionSheet.controlAccentColor)
|
|
))
|
|
], spacing: 0.0)
|
|
),
|
|
action: { [weak state] in
|
|
state?.openGiftFragmentResale(url: fragmentListedUrl)
|
|
},
|
|
animateScale: false
|
|
),
|
|
environment: {},
|
|
availableSize: context.availableSize,
|
|
transition: .immediate
|
|
)
|
|
context.add(fragmentSaleButton
|
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + fragmentSaleButton.size.height / 2.0))
|
|
)
|
|
originY += fragmentSaleButton.size.height
|
|
originY += 12.0
|
|
}
|
|
|
|
context.add(buttons
|
|
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0))
|
|
)
|
|
|
|
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
|
|
return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset)
|
|
}
|
|
}
|
|
}
|
|
|
|
final class GiftValueSheetComponent: CombinedComponent {
|
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
|
|
|
let context: AccountContext
|
|
let gift: StarGift
|
|
let valueInfo: StarGift.UniqueGift.ValueInfo
|
|
|
|
init(
|
|
context: AccountContext,
|
|
gift: StarGift,
|
|
valueInfo: StarGift.UniqueGift.ValueInfo
|
|
) {
|
|
self.context = context
|
|
self.gift = gift
|
|
self.valueInfo = valueInfo
|
|
}
|
|
|
|
static func ==(lhs: GiftValueSheetComponent, rhs: GiftValueSheetComponent) -> Bool {
|
|
if lhs.context !== rhs.context {
|
|
return false
|
|
}
|
|
if lhs.gift != rhs.gift {
|
|
return false
|
|
}
|
|
if lhs.valueInfo != rhs.valueInfo {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
static var body: Body {
|
|
let sheet = Child(SheetComponent<EnvironmentType>.self)
|
|
let animateOut = StoredActionSlot(Action<Void>.self)
|
|
|
|
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
|
|
|
|
return { context in
|
|
let environment = context.environment[EnvironmentType.self]
|
|
let controller = environment.controller
|
|
|
|
let sheet = sheet.update(
|
|
component: SheetComponent<EnvironmentType>(
|
|
content: AnyComponent<EnvironmentType>(GiftValueSheetContent(
|
|
context: context.component.context,
|
|
gift: context.component.gift,
|
|
valueInfo: context.component.valueInfo,
|
|
animateOut: animateOut,
|
|
getController: controller
|
|
)),
|
|
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
|
followContentSizeChanges: true,
|
|
clipsContent: true,
|
|
externalState: sheetExternalState,
|
|
animateOut: animateOut,
|
|
onPan: {
|
|
if let controller = controller() as? GiftValueScreen {
|
|
controller.dismissAllTooltips()
|
|
}
|
|
},
|
|
willDismiss: {
|
|
}
|
|
),
|
|
environment: {
|
|
environment
|
|
SheetComponentEnvironment(
|
|
isDisplaying: environment.value.isVisible,
|
|
isCentered: environment.metrics.widthClass == .regular,
|
|
hasInputHeight: !environment.inputHeight.isZero,
|
|
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
|
|
dismiss: { animated in
|
|
if animated {
|
|
if let controller = controller() as? GiftValueScreen {
|
|
controller.dismissAllTooltips()
|
|
animateOut.invoke(Action { _ in
|
|
controller.dismiss(completion: nil)
|
|
})
|
|
}
|
|
} else {
|
|
if let controller = controller() as? GiftValueScreen {
|
|
controller.dismissAllTooltips()
|
|
controller.dismiss(completion: nil)
|
|
}
|
|
}
|
|
}
|
|
)
|
|
},
|
|
availableSize: context.availableSize,
|
|
transition: context.transition
|
|
)
|
|
|
|
context.add(sheet
|
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
|
)
|
|
|
|
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
|
|
var sideInset: CGFloat = 0.0
|
|
var bottomInset: CGFloat = max(environment.safeInsets.bottom, sheetExternalState.contentHeight)
|
|
if case .regular = environment.metrics.widthClass {
|
|
sideInset = floor((context.availableSize.width - 430.0) / 2.0) - 12.0
|
|
bottomInset = (context.availableSize.height - sheetExternalState.contentHeight) / 2.0 + sheetExternalState.contentHeight
|
|
}
|
|
|
|
let layout = ContainerViewLayout(
|
|
size: context.availableSize,
|
|
metrics: environment.metrics,
|
|
deviceMetrics: environment.deviceMetrics,
|
|
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0),
|
|
safeInsets: UIEdgeInsets(top: 0.0, left: max(sideInset, environment.safeInsets.left), bottom: 0.0, right: max(sideInset, environment.safeInsets.right)),
|
|
additionalInsets: .zero,
|
|
statusBarHeight: environment.statusBarHeight,
|
|
inputHeight: nil,
|
|
inputHeightIsInteractivellyChanging: false,
|
|
inVoiceOver: false
|
|
)
|
|
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
|
|
}
|
|
|
|
return context.availableSize
|
|
}
|
|
}
|
|
}
|
|
|
|
final class GiftValueScreen: ViewControllerComponentContainer {
|
|
private let context: AccountContext
|
|
private let gift: StarGift
|
|
private let valueInfo: StarGift.UniqueGift.ValueInfo
|
|
|
|
public init(
|
|
context: AccountContext,
|
|
gift: StarGift,
|
|
valueInfo: StarGift.UniqueGift.ValueInfo
|
|
) {
|
|
self.context = context
|
|
self.gift = gift
|
|
self.valueInfo = valueInfo
|
|
|
|
super.init(
|
|
context: context,
|
|
component: GiftValueSheetComponent(
|
|
context: context,
|
|
gift: gift,
|
|
valueInfo: valueInfo
|
|
),
|
|
navigationBarAppearance: .none,
|
|
statusBarStyle: .ignore,
|
|
theme: .default
|
|
)
|
|
|
|
self.navigationPresentation = .flatModal
|
|
self.automaticallyControlPresentationContextLayout = false
|
|
}
|
|
|
|
required public init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
}
|
|
|
|
public override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
self.view.disablesInteractiveModalDismiss = true
|
|
}
|
|
|
|
public override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
self.dismissAllTooltips()
|
|
}
|
|
|
|
public func dismissAnimated() {
|
|
self.dismissAllTooltips()
|
|
|
|
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
|
view.dismissAnimated()
|
|
}
|
|
}
|
|
|
|
fileprivate func dismissAllTooltips() {
|
|
self.window?.forEachController({ controller in
|
|
if let controller = controller as? TooltipScreen {
|
|
controller.dismiss(inPlace: false)
|
|
}
|
|
if let controller = controller as? UndoOverlayController {
|
|
controller.dismiss()
|
|
}
|
|
})
|
|
self.forEachController({ controller in
|
|
if let controller = controller as? TooltipScreen {
|
|
controller.dismiss(inPlace: false)
|
|
}
|
|
if let controller = controller as? UndoOverlayController {
|
|
controller.dismiss()
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
}
|