mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Gift auctions
This commit is contained in:
parent
aba2704dcb
commit
412e02ef00
48 changed files with 3061 additions and 529 deletions
|
|
@ -15176,3 +15176,91 @@ Error: %8$@";
|
|||
|
||||
"LoginEmail.Success.Title" = "Email added";
|
||||
"LoginEmail.Success.Text" = "Your account is now protected!";
|
||||
|
||||
"Notification.GiftAuction.Acquired" = "You've successfully bought a gift in the auction for %@.";
|
||||
|
||||
"Gift.Auction.Auction" = "auction";
|
||||
"Gift.Auction.Description" = "Top %@ bidders will get a **%@** this round. [Learn More >]()";
|
||||
"Gift.Auction.Ended" = "Auction ended";
|
||||
"Gift.Auction.FirstSale" = "First Sale";
|
||||
"Gift.Auction.LastSale" = "Last Sale";
|
||||
"Gift.Auction.Started" = "Started";
|
||||
"Gift.Auction.Ends" = "Ends";
|
||||
"Gift.Auction.AveragePrice" = "Average Price";
|
||||
"Gift.Auction.CurrentRound" = "Current Round";
|
||||
"Gift.Auction.Round" = "%@ of %@";
|
||||
"Gift.Auction.Availability" = "Availability";
|
||||
"Gift.Auction.AvailabilityOf" = "%@ of %@ left";
|
||||
"Gift.Auction.Join" = "Join Auction";
|
||||
"Gift.Auction.TimeLeftHours" = "{h}h {m}m left";
|
||||
"Gift.Auction.TimeLeftMinutes" = "{m}:{s} left";
|
||||
"Gift.Auction.ItemsBought_1" = "item bought";
|
||||
"Gift.Auction.ItemsBought_any" = "items bought";
|
||||
"Gift.Auction.ForSaleOnTelegram" = "for sale on Telegram";
|
||||
"Gift.Auction.ForSaleOnFragment" = "for sale on Fragment";
|
||||
"Gift.Auction.AveragePriceInfo" = "**%1$@** is the average sale price for %2$@ gifts.";
|
||||
"Gift.Auction.Stars_1" = "%@ Star";
|
||||
"Gift.Auction.Stars_any" = "%@ Stars";
|
||||
|
||||
"Gift.AuctionBid.Title" = "Place a Bid";
|
||||
"Gift.AuctionBid.MinimumBid" = "minimum bid";
|
||||
"Gift.AuctionBid.UntilNext" = "until next round";
|
||||
"Gift.AuctionBid.Left" = "left";
|
||||
"Gift.AuctionBid.YourBid" = "your bid";
|
||||
"Gift.AuctionBid.Winning" = "You're winning";
|
||||
"Gift.AuctionBid.Outbid" = "You've been outbid";
|
||||
"Gift.AuctionBid.BidPreview" = "Your bid will be";
|
||||
"Gift.AuctionBid.TopWinners" = "Top 3 Winners";
|
||||
"Gift.AuctionBid.PlaceBid" = "Place a %@ Bid";
|
||||
"Gift.AuctionBid.AddToBid" = "Add %@ to Your Bid";
|
||||
|
||||
"Gift.Auction.Context.About" = "About";
|
||||
"Gift.Auction.Context.CopyLink" = "Copy Link";
|
||||
"Gift.Auction.Context.Share" = "Share";
|
||||
|
||||
"Gift.ActiveAuctions.Title_1" = "%@ Active Auction";
|
||||
"Gift.ActiveAuctions.Title_any" = "%@ Active Auctions";
|
||||
"Gift.ActiveAuctions.Round" = "Round %@ of %@";
|
||||
"Gift.ActiveAuctions.Winning" = "Your bid **%@** is ranked **#%@**";
|
||||
"Gift.ActiveAuctions.Outbid" = "Your bid **%@** is outbid";
|
||||
"Gift.ActiveAuctions.RaiseBid" = "Raise Bid";
|
||||
|
||||
"Gift.Acquired.Title_1" = "%@ Item Bought";
|
||||
"Gift.Acquired.Title_any" = "%@ Items Bought";
|
||||
"Gift.Acquired.Round" = "Round #%@";
|
||||
"Gift.Acquired.Recipient" = "Recipient";
|
||||
"Gift.Acquired.Date" = "Date";
|
||||
"Gift.Acquired.AcceptedBid" = "Accepted Bid";
|
||||
"Gift.Acquired.Top" = "TOP %@";
|
||||
|
||||
"Gift.Options.Gift.Auction" = "auction";
|
||||
|
||||
"Story.Camera.Live" = "Live";
|
||||
|
||||
"Chat.Auction" = "Gift Auction";
|
||||
"Chat.Auction.ViewResults" = "VIEW RESULTS";
|
||||
"Chat.Auction.Join" = "JOIN";
|
||||
"Chat.Auction.Finished" = "finished";
|
||||
"Chat.Auction.Gifts_1" = "%@ gift";
|
||||
"Chat.Auction.Gifts_any" = "%@ gifts";
|
||||
|
||||
"Story.Privacy.StartLiveAs" = "Start Live As";
|
||||
|
||||
"ChatList.Auctions.ActiveAuction_1" = "Active Auction";
|
||||
"ChatList.Auctions.ActiveAuction_any" = "%@ Active Auctions";
|
||||
"ChatList.Auctions.Status.Single.Winning" = "You're winning (%@ place).";
|
||||
"ChatList.Auctions.Status.Single.PlaceFirst" = "%@st";
|
||||
"ChatList.Auctions.Status.Single.PlaceSecond" = "%@nd";
|
||||
"ChatList.Auctions.Status.Single.PlaceThird" = "%@rd";
|
||||
"ChatList.Auctions.Status.Single.PlaceNTh" = "%@th";
|
||||
"ChatList.Auctions.Status.Single.Outbid" = "You've been outbid.";
|
||||
"ChatList.Auctions.Status.Many.WinningAll" = "You're winning all of them.";
|
||||
"ChatList.Auctions.Status.Many.Outbid_1" = "You've been outbid in %@ of them.";
|
||||
"ChatList.Auctions.Status.Many.Outbid_any" = "You've been outbid in %@ of them.";
|
||||
"ChatList.Auctions.Status.Many.OutbidAll" = "You've been outbid in all of them.";
|
||||
"ChatList.Auctions.View" = "View";
|
||||
|
||||
"Gift.Auction.Ongoing.Title" = "One Auction at a Time";
|
||||
"Gift.Auction.Ongoing.Text" = "You’ve already bid in this gift auction for **%@**.";
|
||||
"Gift.Auction.Ongoing.TextYourself" = "You’ve already bid in this gift auction for yourself.";
|
||||
"Gift.Auction.Ongoing.View" = "View";
|
||||
|
|
|
|||
|
|
@ -328,6 +328,7 @@ public enum ResolvedUrl {
|
|||
case premiumGiftCode(slug: String)
|
||||
case premiumMultiGift(reference: String?)
|
||||
case collectible(gift: StarGift.UniqueGift?)
|
||||
case auction(auction: GiftAuctionContext?)
|
||||
case messageLink(link: TelegramResolvedMessageLink?)
|
||||
case stars
|
||||
case ton
|
||||
|
|
@ -1424,7 +1425,8 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeGiftAuctionInfoScreen(context: AccountContext, gift: StarGift, completion: (() -> Void)?) -> ViewController
|
||||
func makeGiftAuctionBidScreen(context: AccountContext, auctionContext: GiftAuctionContext) -> ViewController
|
||||
func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext) -> ViewController
|
||||
|
||||
func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController
|
||||
|
||||
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController
|
||||
|
||||
func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?)
|
||||
|
|
|
|||
|
|
@ -1247,6 +1247,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[555358088] = { return Api.WebPage.parse_webPageEmpty($0) }
|
||||
dict[1930545681] = { return Api.WebPage.parse_webPageNotModified($0) }
|
||||
dict[-1328464313] = { return Api.WebPage.parse_webPagePending($0) }
|
||||
dict[55150251] = { return Api.WebPageAttribute.parse_webPageAttributeStarGiftAuction($0) }
|
||||
dict[835375875] = { return Api.WebPageAttribute.parse_webPageAttributeStarGiftCollection($0) }
|
||||
dict[1355547603] = { return Api.WebPageAttribute.parse_webPageAttributeStickerSet($0) }
|
||||
dict[781501415] = { return Api.WebPageAttribute.parse_webPageAttributeStory($0) }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
public extension Api {
|
||||
indirect enum WebPageAttribute: TypeConstructorDescription {
|
||||
case webPageAttributeStarGiftAuction(gift: Api.StarGift, endDate: Int32, centerColor: Int32, edgeColor: Int32, textColor: Int32)
|
||||
case webPageAttributeStarGiftCollection(icons: [Api.Document])
|
||||
case webPageAttributeStickerSet(flags: Int32, stickers: [Api.Document])
|
||||
case webPageAttributeStory(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?)
|
||||
|
|
@ -8,6 +9,16 @@ public extension Api {
|
|||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .webPageAttributeStarGiftAuction(let gift, let endDate, let centerColor, let edgeColor, let textColor):
|
||||
if boxed {
|
||||
buffer.appendInt32(55150251)
|
||||
}
|
||||
gift.serialize(buffer, true)
|
||||
serializeInt32(endDate, buffer: buffer, boxed: false)
|
||||
serializeInt32(centerColor, buffer: buffer, boxed: false)
|
||||
serializeInt32(edgeColor, buffer: buffer, boxed: false)
|
||||
serializeInt32(textColor, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .webPageAttributeStarGiftCollection(let icons):
|
||||
if boxed {
|
||||
buffer.appendInt32(835375875)
|
||||
|
|
@ -61,6 +72,8 @@ public extension Api {
|
|||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .webPageAttributeStarGiftAuction(let gift, let endDate, let centerColor, let edgeColor, let textColor):
|
||||
return ("webPageAttributeStarGiftAuction", [("gift", gift as Any), ("endDate", endDate as Any), ("centerColor", centerColor as Any), ("edgeColor", edgeColor as Any), ("textColor", textColor as Any)])
|
||||
case .webPageAttributeStarGiftCollection(let icons):
|
||||
return ("webPageAttributeStarGiftCollection", [("icons", icons as Any)])
|
||||
case .webPageAttributeStickerSet(let flags, let stickers):
|
||||
|
|
@ -74,6 +87,31 @@ public extension Api {
|
|||
}
|
||||
}
|
||||
|
||||
public static func parse_webPageAttributeStarGiftAuction(_ reader: BufferReader) -> WebPageAttribute? {
|
||||
var _1: Api.StarGift?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StarGift
|
||||
}
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.WebPageAttribute.webPageAttributeStarGiftAuction(gift: _1!, endDate: _2!, centerColor: _3!, edgeColor: _4!, textColor: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_webPageAttributeStarGiftCollection(_ reader: BufferReader) -> WebPageAttribute? {
|
||||
var _1: [Api.Document]?
|
||||
if let _ = reader.readInt32() {
|
||||
|
|
@ -1346,53 +1384,3 @@ public extension Api.account {
|
|||
|
||||
}
|
||||
}
|
||||
public extension Api.account {
|
||||
enum SavedRingtone: TypeConstructorDescription {
|
||||
case savedRingtone
|
||||
case savedRingtoneConverted(document: Api.Document)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .savedRingtone:
|
||||
if boxed {
|
||||
buffer.appendInt32(-1222230163)
|
||||
}
|
||||
|
||||
break
|
||||
case .savedRingtoneConverted(let document):
|
||||
if boxed {
|
||||
buffer.appendInt32(523271863)
|
||||
}
|
||||
document.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .savedRingtone:
|
||||
return ("savedRingtone", [])
|
||||
case .savedRingtoneConverted(let document):
|
||||
return ("savedRingtoneConverted", [("document", document as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_savedRingtone(_ reader: BufferReader) -> SavedRingtone? {
|
||||
return Api.account.SavedRingtone.savedRingtone
|
||||
}
|
||||
public static func parse_savedRingtoneConverted(_ reader: BufferReader) -> SavedRingtone? {
|
||||
var _1: Api.Document?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.Document
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.account.SavedRingtone.savedRingtoneConverted(document: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,53 @@
|
|||
public extension Api.account {
|
||||
enum SavedRingtone: TypeConstructorDescription {
|
||||
case savedRingtone
|
||||
case savedRingtoneConverted(document: Api.Document)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .savedRingtone:
|
||||
if boxed {
|
||||
buffer.appendInt32(-1222230163)
|
||||
}
|
||||
|
||||
break
|
||||
case .savedRingtoneConverted(let document):
|
||||
if boxed {
|
||||
buffer.appendInt32(523271863)
|
||||
}
|
||||
document.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .savedRingtone:
|
||||
return ("savedRingtone", [])
|
||||
case .savedRingtoneConverted(let document):
|
||||
return ("savedRingtoneConverted", [("document", document as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_savedRingtone(_ reader: BufferReader) -> SavedRingtone? {
|
||||
return Api.account.SavedRingtone.savedRingtone
|
||||
}
|
||||
public static func parse_savedRingtoneConverted(_ reader: BufferReader) -> SavedRingtone? {
|
||||
var _1: Api.Document?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.Document
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.account.SavedRingtone.savedRingtoneConverted(document: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.account {
|
||||
enum SavedRingtones: TypeConstructorDescription {
|
||||
case savedRingtones(hash: Int64, ringtones: [Api.Document])
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ swift_library(
|
|||
"//submodules/TelegramNotices:TelegramNotices",
|
||||
"//submodules/TooltipUI:TooltipUI",
|
||||
"//submodules/TelegramUI/Components/SliderContextItem:SliderContextItem",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftItemComponent",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,297 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import ButtonComponent
|
||||
import GiftItemComponent
|
||||
import AnimatedTextComponent
|
||||
|
||||
private let titleFont = Font.semibold(15.0)
|
||||
private let subtitleFont = Font.regular(14.0)
|
||||
|
||||
final class GiftAuctionAccessoryPanel: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
|
||||
private let tapAction: () -> Void
|
||||
|
||||
private let contentNode: ASDisplayNode
|
||||
|
||||
private let title = ComponentView<Empty>()
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
private let button = ComponentView<Empty>()
|
||||
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat, Bool)?
|
||||
private var states: [GiftAuctionContext.State] = []
|
||||
private var giftAuctionTimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, tapAction: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
self.tapAction = tapAction
|
||||
|
||||
self.contentNode = ASDisplayNode()
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.addSubnode(self.contentNode)
|
||||
|
||||
self.contentNode.addSubnode(self.separatorNode)
|
||||
|
||||
self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
|
||||
if let self, let (size, leftInset, rightInset, isHidden) = self.validLayout {
|
||||
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isHidden: isHidden, transition: .immediate)
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
self.giftAuctionTimer?.start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.giftAuctionTimer?.invalidate()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
self.view.addGestureRecognizer(tapRecognizer)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, isHidden: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (size, leftInset, rightInset, isHidden)
|
||||
|
||||
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: isHidden ? -size.height : 0.0), size: size))
|
||||
transition.updateAlpha(node: self.contentNode, alpha: isHidden ? 0.0 : 1.0)
|
||||
|
||||
|
||||
guard self.states.count > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
var titleItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
for auctionState in self.states {
|
||||
if case let .generic(gift) = auctionState.gift {
|
||||
titleItems.append(AnyComponentWithIdentity(id: "icon-\(gift.id)", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: self.context,
|
||||
theme: self.theme,
|
||||
strings: self.strings,
|
||||
peer: nil,
|
||||
subject: .starGift(gift: gift, price: ""),
|
||||
mode: .tableIcon
|
||||
)
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
let titleText: String = self.strings.ChatList_Auctions_ActiveAuction(Int32(self.states.count))
|
||||
titleItems.append(AnyComponentWithIdentity(id: "label", component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: titleText, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor))))))
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
HStack(titleItems, spacing: 3.0, alignment: .left)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: 16.0, y: 9.0), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.contentNode.view.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
|
||||
let subtitleText: String
|
||||
var subtitleTextColor = self.theme.rootController.navigationBar.secondaryTextColor
|
||||
var isOutbid = false
|
||||
|
||||
var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = []
|
||||
|
||||
if self.states.count == 1, let auctionState = self.states.first {
|
||||
let place = auctionState.place ?? 1
|
||||
if case let .generic(gift) = auctionState.gift, let auctionGiftsPerRound = gift.auctionGiftsPerRound, place > auctionGiftsPerRound {
|
||||
subtitleText = self.strings.ChatList_Auctions_Status_Single_Outbid
|
||||
subtitleTextColor = self.theme.list.itemDestructiveColor
|
||||
isOutbid = true
|
||||
} else {
|
||||
let placeText: String
|
||||
let lastDigit = place % 10
|
||||
switch lastDigit {
|
||||
case 1:
|
||||
placeText = self.strings.ChatList_Auctions_Status_Single_PlaceFirst("\(place)").string
|
||||
case 2:
|
||||
placeText = self.strings.ChatList_Auctions_Status_Single_PlaceSecond("\(place)").string
|
||||
case 3:
|
||||
placeText = self.strings.ChatList_Auctions_Status_Single_PlaceThird("\(place)").string
|
||||
default:
|
||||
placeText = self.strings.ChatList_Auctions_Status_Single_PlaceNTh("\(place)").string
|
||||
}
|
||||
subtitleText = self.strings.ChatList_Auctions_Status_Single_Winning(placeText).string
|
||||
}
|
||||
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
var endTime = currentTime
|
||||
if case let .ongoing(_, _, _, _, _, _, nextRoundDate, _, _, _) = auctionState.auctionState {
|
||||
endTime = nextRoundDate
|
||||
}
|
||||
|
||||
let endTimeout = max(0, endTime - currentTime)
|
||||
|
||||
let hours = Int(endTimeout / 3600)
|
||||
let minutes = Int((endTimeout % 3600) / 60)
|
||||
let seconds = Int(endTimeout % 60)
|
||||
|
||||
if hours > 0 {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "h", content: .number(hours, minDigits: 1)))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "colon1", content: .text(":")))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "m", content: .number(minutes, minDigits: 2)))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "colon2", content: .text(":")))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
|
||||
} else {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "m", content: .number(minutes, minDigits: 2)))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "colon2", content: .text(":")))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
|
||||
}
|
||||
} else {
|
||||
var outbidCount = 0
|
||||
for auctionState in self.states {
|
||||
let place = auctionState.place ?? 1
|
||||
if case let .generic(gift) = auctionState.gift, let auctionGiftsPerRound = gift.auctionGiftsPerRound, place > auctionGiftsPerRound {
|
||||
outbidCount += 1
|
||||
}
|
||||
}
|
||||
if outbidCount > 0 {
|
||||
if outbidCount == self.states.count {
|
||||
subtitleText = self.strings.ChatList_Auctions_Status_Many_OutbidAll
|
||||
} else {
|
||||
subtitleText = self.strings.ChatList_Auctions_Status_Many_Outbid(Int32(outbidCount))
|
||||
}
|
||||
subtitleTextColor = self.theme.list.itemDestructiveColor
|
||||
isOutbid = true
|
||||
} else {
|
||||
subtitleText = self.strings.ChatList_Auctions_Status_Many_WinningAll
|
||||
}
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "view", content: .text(self.strings.ChatList_Auctions_View)))
|
||||
}
|
||||
|
||||
let subtitleSize = self.subtitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: subtitleText, font: subtitleFont, textColor: subtitleTextColor)))),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
)
|
||||
let subtitleFrame = CGRect(origin: CGPoint(x: 16.0, y: 29.0), size: subtitleSize)
|
||||
if let subtitleView = self.subtitle.view {
|
||||
if subtitleView.superview == nil {
|
||||
self.contentNode.view.addSubview(subtitleView)
|
||||
}
|
||||
subtitleView.frame = subtitleFrame
|
||||
}
|
||||
|
||||
let buttonSize = self.button.update(
|
||||
transition: .spring(duration: 0.2),
|
||||
component: AnyComponent(
|
||||
ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: self.theme.list.itemCheckColors.fillColor,
|
||||
foreground: self.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: self.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
|
||||
cornerRadius: 14.0,
|
||||
isShimmering: isOutbid
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "content",
|
||||
component: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Premium/Auction/BidSmall", tintColor: self.theme.list.itemCheckColors.foregroundColor))),
|
||||
AnyComponentWithIdentity(id: "timer", component: AnyComponent(
|
||||
AnimatedTextComponent(
|
||||
font: Font.with(size: 15.0, weight: .semibold, traits: .monospacedNumbers),
|
||||
color: self.theme.list.itemCheckColors.foregroundColor,
|
||||
items: buttonAnimatedTitleItems,
|
||||
noDelay: true
|
||||
)
|
||||
))
|
||||
], spacing: 3.0))
|
||||
),
|
||||
fitToContentWidth: true,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.tapAction()
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width, height: 28.0)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: size.width - rightInset - buttonSize.width - 16.0, y: 14.0), size: buttonSize)
|
||||
if let buttonView = self.button.view {
|
||||
if buttonView.superview == nil {
|
||||
self.contentNode.view.addSubview(buttonView)
|
||||
}
|
||||
buttonView.frame = buttonFrame
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||
}
|
||||
|
||||
func update(states: [GiftAuctionContext.State]) {
|
||||
self.states = states
|
||||
if let (size, leftInset, rightInset, isHidden) = self.validLayout {
|
||||
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isHidden: isHidden, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn(_ transition: ContainedViewLayoutTransition) {
|
||||
let contentPosition = self.contentNode.layer.position
|
||||
|
||||
transition.animatePosition(node: self.contentNode, from: CGPoint(x: contentPosition.x, y: contentPosition.y - 56.0))
|
||||
|
||||
guard let (size, _, _, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
transition.animatePositionAdditive(node: self.separatorNode, offset: CGPoint(x: 0.0, y: size.height))
|
||||
}
|
||||
|
||||
func animateOut(_ transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
let contentPosition = self.contentNode.layer.position
|
||||
transition.animatePosition(node: self.contentNode, to: CGPoint(x: contentPosition.x, y: contentPosition.y - 56.0), removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
|
||||
guard let (size, _, _, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
transition.updatePosition(node: self.separatorNode, position: self.separatorNode.position.offsetBy(dx: 0.0, dy: size.height))
|
||||
}
|
||||
|
||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.tapAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -89,6 +89,10 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||
private var locationBroadcastMessages: [EngineMessage.Id: EngineMessage]?
|
||||
private var locationBroadcastAccessoryPanel: LocationBroadcastNavigationAccessoryPanel?
|
||||
|
||||
private var giftAuctionAccessoryPanel: GiftAuctionAccessoryPanel?
|
||||
private var giftAuctionStates: [GiftAuctionContext.State] = []
|
||||
private var giftAuctionDisposable: Disposable?
|
||||
|
||||
private var groupCallPanelData: GroupCallPanelData?
|
||||
public private(set) var groupCallAccessoryPanel: GroupCallNavigationAccessoryPanel?
|
||||
|
||||
|
|
@ -358,6 +362,17 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||
}
|
||||
}
|
||||
|
||||
if let giftAuctionsManager = context.giftAuctionsManager, case .summary = locationBroadcastPanelSource {
|
||||
self.giftAuctionDisposable = (giftAuctionsManager.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] states in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.giftAuctionStates = states
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = (self.updatedPresentationData.1
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
|
|
@ -522,6 +537,63 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||
}
|
||||
}
|
||||
|
||||
if !self.giftAuctionStates.isEmpty {
|
||||
let panelHeight: CGFloat = 56.0
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
additionalHeight += panelHeight
|
||||
panelStartY += panelHeight
|
||||
|
||||
let giftAuctionAccessoryPanel: GiftAuctionAccessoryPanel
|
||||
if let current = self.giftAuctionAccessoryPanel {
|
||||
giftAuctionAccessoryPanel = current
|
||||
transition.updateFrame(node: giftAuctionAccessoryPanel, frame: panelFrame)
|
||||
giftAuctionAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: transition)
|
||||
} else {
|
||||
giftAuctionAccessoryPanel = GiftAuctionAccessoryPanel(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, tapAction: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if !"".isEmpty, self.giftAuctionStates.count == 1, let gift = self.giftAuctionStates.first?.gift, case let .generic(gift) = gift {
|
||||
if let giftAuctionsManager = self.context.giftAuctionsManager {
|
||||
let _ = (giftAuctionsManager.auctionContext(for: .giftId(gift.id))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] auction in
|
||||
guard let self, let auction else {
|
||||
return
|
||||
}
|
||||
let controller = self.context.sharedContext.makeGiftAuctionBidScreen(context: self.context, auctionContext: auction)
|
||||
self.push(controller)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let controller = self.context.sharedContext.makeGiftAuctionActiveBidsScreen(context: self.context)
|
||||
self.push(controller)
|
||||
}
|
||||
})
|
||||
if let accessoryPanelContainer = self.accessoryPanelContainer {
|
||||
accessoryPanelContainer.addSubnode(giftAuctionAccessoryPanel)
|
||||
} else {
|
||||
self.navigationBar?.additionalContentNode.addSubnode(giftAuctionAccessoryPanel)
|
||||
}
|
||||
self.giftAuctionAccessoryPanel = giftAuctionAccessoryPanel
|
||||
giftAuctionAccessoryPanel.frame = panelFrame
|
||||
|
||||
giftAuctionAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, isHidden: !self.displayNavigationBar, transition: .immediate)
|
||||
if transition.isAnimated {
|
||||
giftAuctionAccessoryPanel.animateIn(transition)
|
||||
}
|
||||
}
|
||||
giftAuctionAccessoryPanel.update(states: self.giftAuctionStates)
|
||||
} else if let giftAuctionAccessoryPanel = self.giftAuctionAccessoryPanel {
|
||||
self.giftAuctionAccessoryPanel = nil
|
||||
if transition.isAnimated {
|
||||
giftAuctionAccessoryPanel.animateOut(transition, completion: { [weak giftAuctionAccessoryPanel] in
|
||||
giftAuctionAccessoryPanel?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
giftAuctionAccessoryPanel.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let locationBroadcastPeers = self.locationBroadcastPeers, let locationBroadcastMode = self.locationBroadcastMode {
|
||||
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelStartY), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
|
|
|
|||
|
|
@ -182,7 +182,6 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||
case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId):
|
||||
return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId)))
|
||||
case let .messageActionStarGift(flags, apiGift, message, convertStars, upgradeMessageId, upgradeStars, fromId, peer, savedId, prepaidUpgradeHash, giftMessageId, toId):
|
||||
let _ = toId
|
||||
let text: String?
|
||||
let entities: [MessageTextEntity]?
|
||||
switch message {
|
||||
|
|
@ -196,7 +195,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
}
|
||||
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, isPrepaidUpgrade: (flags & (1 << 13)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, prepaidUpgradeHash: prepaidUpgradeHash, giftMessageId: giftMessageId, upgradeSeparate: (flags & (1 << 16)) != 0))
|
||||
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, isPrepaidUpgrade: (flags & (1 << 13)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, prepaidUpgradeHash: prepaidUpgradeHash, giftMessageId: giftMessageId, upgradeSeparate: (flags & (1 << 16)) != 0, isAuctionAcquired: (flags & (1 << 17)) != 0, toPeerId: toId?.peerId))
|
||||
case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars):
|
||||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ func telegramMediaWebpageAttributeFromApiWebpageAttribute(_ attribute: Api.WebPa
|
|||
var files: [TelegramMediaFile] = []
|
||||
files = icons.compactMap { telegramMediaFileFromApiDocument($0, altDocuments: []) }
|
||||
return .giftCollection(TelegramMediaWebpageGiftCollectionAttribute(files: files))
|
||||
case let .webPageAttributeStarGiftAuction(apiGift, endDate, centerColor, edgeColor, textColor):
|
||||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
}
|
||||
return .giftAuction(TelegramMediaWebpageGiftAuctionAttribute(gift: gift, endDate: endDate, centerColor: centerColor, edgeColor: edgeColor, textColor: textColor))
|
||||
case .webPageAttributeStory:
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String)
|
||||
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
|
||||
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
|
||||
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?, giftMessageId: Int32?, upgradeSeparate: Bool)
|
||||
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?, giftMessageId: Int32?, upgradeSeparate: Bool, isAuctionAcquired: Bool, toPeerId: EnginePeer.Id?)
|
||||
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?, assigned: Bool)
|
||||
case paidMessagesRefunded(count: Int32, stars: Int64)
|
||||
case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool)
|
||||
|
|
@ -394,7 +394,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
}
|
||||
self = .prizeStars(amount: decoder.decodeInt64ForKey("amount", orElse: 0), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: boostPeerId, transactionId: decoder.decodeOptionalStringForKey("transactionId"), giveawayMessageId: giveawayMessageId)
|
||||
case 44:
|
||||
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), prepaidUpgradeHash: decoder.decodeOptionalStringForKey("prepaidUpgradeHash"), giftMessageId: decoder.decodeOptionalInt32ForKey("giftMessageId"), upgradeSeparate: decoder.decodeOptionalBoolForKey("upgradeSeparate") ?? false)
|
||||
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), prepaidUpgradeHash: decoder.decodeOptionalStringForKey("prepaidUpgradeHash"), giftMessageId: decoder.decodeOptionalInt32ForKey("giftMessageId"), upgradeSeparate: decoder.decodeOptionalBoolForKey("upgradeSeparate") ?? false, isAuctionAcquired: decoder.decodeOptionalBoolForKey("isAuctionAcquired") ?? false, toPeerId: decoder.decodeOptionalInt64ForKey("toPeerId").flatMap { EnginePeer.Id($0) })
|
||||
case 45:
|
||||
var resaleAmount: CurrencyAmount?
|
||||
if let amount = decoder.decodeCodable(CurrencyAmount.self, forKey: "resaleAmount") {
|
||||
|
|
@ -728,7 +728,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "giveawayMsgId")
|
||||
}
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash, giftMessageId, upgradeSeparate):
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash, giftMessageId, upgradeSeparate, isAuctionAcquired, toPeerId):
|
||||
encoder.encodeInt32(44, forKey: "_rawValue")
|
||||
encoder.encodeObject(gift, forKey: "gift")
|
||||
if let convertStars {
|
||||
|
|
@ -786,6 +786,12 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
encoder.encodeNil(forKey: "giftMessageId")
|
||||
}
|
||||
encoder.encodeBool(upgradeSeparate, forKey: "upgradeSeparate")
|
||||
encoder.encodeBool(isAuctionAcquired, forKey: "isAuctionAcquired")
|
||||
if let toPeerId {
|
||||
encoder.encodeInt64(toPeerId.toInt64(), forKey: "toPeerId")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "toPeerId")
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars, assigned):
|
||||
encoder.encodeInt32(45, forKey: "_rawValue")
|
||||
encoder.encodeObject(gift, forKey: "gift")
|
||||
|
|
@ -920,7 +926,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
return [peerId]
|
||||
case let .prizeStars(_, _, boostPeerId, _, _):
|
||||
return boostPeerId.flatMap { [$0] } ?? []
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _):
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, toPeerId):
|
||||
var peerIds: [PeerId] = []
|
||||
if let peerId {
|
||||
peerIds.append(peerId)
|
||||
|
|
@ -931,6 +937,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
if let releasedBy = gift.releasedBy {
|
||||
peerIds.append(releasedBy)
|
||||
}
|
||||
if let toPeerId {
|
||||
peerIds.append(toPeerId)
|
||||
}
|
||||
return peerIds
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, _):
|
||||
var peerIds: [PeerId] = []
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ private enum TelegramMediaWebpageAttributeTypes: Int32 {
|
|||
case stickerPack
|
||||
case starGift
|
||||
case giftCollection
|
||||
case giftAuction
|
||||
}
|
||||
|
||||
public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable {
|
||||
|
|
@ -16,6 +17,7 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable {
|
|||
case stickerPack(TelegramMediaWebpageStickerPackAttribute)
|
||||
case starGift(TelegramMediaWebpageStarGiftAttribute)
|
||||
case giftCollection(TelegramMediaWebpageGiftCollectionAttribute)
|
||||
case giftAuction(TelegramMediaWebpageGiftAuctionAttribute)
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
switch decoder.decodeInt32ForKey("r", orElse: 0) {
|
||||
|
|
@ -27,6 +29,8 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable {
|
|||
self = .starGift(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageStarGiftAttribute(decoder: $0) }) as! TelegramMediaWebpageStarGiftAttribute)
|
||||
case TelegramMediaWebpageAttributeTypes.giftCollection.rawValue:
|
||||
self = .giftCollection(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageGiftCollectionAttribute(decoder: $0) }) as! TelegramMediaWebpageGiftCollectionAttribute)
|
||||
case TelegramMediaWebpageAttributeTypes.giftAuction.rawValue:
|
||||
self = .giftAuction(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageGiftAuctionAttribute(decoder: $0) }) as! TelegramMediaWebpageGiftAuctionAttribute)
|
||||
default:
|
||||
self = .unsupported
|
||||
}
|
||||
|
|
@ -48,6 +52,9 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable {
|
|||
case let .giftCollection(attribute):
|
||||
encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.giftCollection.rawValue, forKey: "r")
|
||||
encoder.encodeObject(attribute, forKey: "a")
|
||||
case let .giftAuction(attribute):
|
||||
encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.giftAuction.rawValue, forKey: "r")
|
||||
encoder.encodeObject(attribute, forKey: "a")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -195,6 +202,63 @@ public final class TelegramMediaWebpageGiftCollectionAttribute: PostboxCoding, E
|
|||
}
|
||||
}
|
||||
|
||||
public final class TelegramMediaWebpageGiftAuctionAttribute: PostboxCoding, Equatable {
|
||||
public static func == (lhs: TelegramMediaWebpageGiftAuctionAttribute, rhs: TelegramMediaWebpageGiftAuctionAttribute) -> Bool {
|
||||
if lhs.gift != rhs.gift {
|
||||
return false
|
||||
}
|
||||
if lhs.endDate != rhs.endDate {
|
||||
return false
|
||||
}
|
||||
if lhs.centerColor != rhs.centerColor {
|
||||
return false
|
||||
}
|
||||
if lhs.edgeColor != rhs.edgeColor {
|
||||
return false
|
||||
}
|
||||
if lhs.textColor != rhs.textColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public let gift: StarGift
|
||||
public let endDate: Int32
|
||||
public let centerColor: Int32
|
||||
public let edgeColor: Int32
|
||||
public let textColor: Int32
|
||||
|
||||
public init(
|
||||
gift: StarGift,
|
||||
endDate: Int32,
|
||||
centerColor: Int32,
|
||||
edgeColor: Int32,
|
||||
textColor: Int32
|
||||
) {
|
||||
self.gift = gift
|
||||
self.endDate = endDate
|
||||
self.centerColor = centerColor
|
||||
self.edgeColor = edgeColor
|
||||
self.textColor = textColor
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.gift = decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift
|
||||
self.endDate = decoder.decodeInt32ForKey("endDate", orElse: 0)
|
||||
self.centerColor = decoder.decodeInt32ForKey("centerColor", orElse: 0)
|
||||
self.edgeColor = decoder.decodeInt32ForKey("edgeColor", orElse: 0)
|
||||
self.textColor = decoder.decodeInt32ForKey("textColor", orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeObject(self.gift, forKey: "gift")
|
||||
encoder.encodeInt32(self.endDate, forKey: "endDate")
|
||||
encoder.encodeInt32(self.centerColor, forKey: "centerColor")
|
||||
encoder.encodeInt32(self.edgeColor, forKey: "edgeColor")
|
||||
encoder.encodeInt32(self.textColor, forKey: "textColor")
|
||||
}
|
||||
}
|
||||
|
||||
public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
||||
public let url: String
|
||||
public let displayUrl: String
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public final class GiftAuctionContext {
|
|||
}
|
||||
|
||||
public enum AuctionState: Equatable {
|
||||
case ongoing(version: Int32, minBidAmount: Int64, bidLevels: [BidLevel], topBidders: [EnginePeer.Id], nextRoundDate: Int32, giftsLeft: Int32, currentRound: Int32, totalRounds: Int32)
|
||||
case ongoing(version: Int32, startDate: Int32, endDate: Int32, minBidAmount: Int64, bidLevels: [BidLevel], topBidders: [EnginePeer.Id], nextRoundDate: Int32, giftsLeft: Int32, currentRound: Int32, totalRounds: Int32)
|
||||
case finished(startDate: Int32, endDate: Int32, averagePrice: Int64)
|
||||
}
|
||||
|
||||
|
|
@ -90,6 +90,14 @@ public final class GiftAuctionContext {
|
|||
return self.stateValue.get()
|
||||
}
|
||||
|
||||
public var currentBidPeerId: EnginePeer.Id? {
|
||||
if self.myState?.bidAmount != nil {
|
||||
return self.myState?.bidPeerId
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public convenience init(account: Account, gift: StarGift) {
|
||||
self.init(account: account, gift: gift, initialAuctionState: nil, initialMyState: nil, initialTimeout: nil)
|
||||
}
|
||||
|
|
@ -112,7 +120,7 @@ public final class GiftAuctionContext {
|
|||
|
||||
private var currentVersion: Int32 {
|
||||
var currentVersion: Int32 = 0
|
||||
if case let .ongoing(version, _, _, _, _, _, _, _) = self.auctionState {
|
||||
if case let .ongoing(version, _, _, _, _, _, _, _, _, _) = self.auctionState {
|
||||
currentVersion = version
|
||||
}
|
||||
return currentVersion
|
||||
|
|
@ -130,7 +138,7 @@ public final class GiftAuctionContext {
|
|||
return
|
||||
}
|
||||
|
||||
if case let .ongoing(version, _, _, _, _, _, _, _) = auctionState, version < self.currentVersion {
|
||||
if case let .ongoing(version, _, _, _, _, _, _, _, _, _) = auctionState, version < self.currentVersion {
|
||||
} else if let auctionState {
|
||||
self.auctionState = auctionState
|
||||
}
|
||||
|
|
@ -190,9 +198,7 @@ extension GiftAuctionContext.State.AuctionState {
|
|||
init?(apiAuctionState: Api.StarGiftAuctionState) {
|
||||
switch apiAuctionState {
|
||||
case let .starGiftAuctionState(version, startDate, endDate, minBidAmount, bidLevels, topBidders, nextRoundAt, giftsLeft, currentRound, totalRounds):
|
||||
let _ = startDate
|
||||
let _ = endDate
|
||||
self = .ongoing(version: version, minBidAmount: minBidAmount, bidLevels: bidLevels.map(GiftAuctionContext.State.BidLevel.init(apiBidLevel:)), topBidders: topBidders.map { EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }, nextRoundDate: nextRoundAt, giftsLeft: giftsLeft, currentRound: currentRound, totalRounds: totalRounds)
|
||||
self = .ongoing(version: version, startDate: startDate, endDate: endDate, minBidAmount: minBidAmount, bidLevels: bidLevels.map(GiftAuctionContext.State.BidLevel.init(apiBidLevel:)), topBidders: topBidders.map { EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }, nextRoundDate: nextRoundAt, giftsLeft: giftsLeft, currentRound: currentRound, totalRounds: totalRounds)
|
||||
case let .starGiftAuctionStateFinished(startDate, endDate, averagePrice):
|
||||
self = .finished(startDate: startDate, endDate: endDate, averagePrice: averagePrice)
|
||||
case .starGiftAuctionStateNotModified:
|
||||
|
|
@ -418,10 +424,10 @@ public class GiftAuctionsManager {
|
|||
|
||||
private func updateState() {
|
||||
var signals: [Signal<GiftAuctionContext.State, NoError>] = []
|
||||
for (_, auction) in self.auctionContexts {
|
||||
for auction in self.auctionContexts.values.sorted(by: { $0.gift.giftId < $1.gift.giftId }) {
|
||||
signals.append(auction.state
|
||||
|> mapToSignal { state in
|
||||
if let state {
|
||||
if let state, state.myState.bidAmount != nil {
|
||||
return .single(state)
|
||||
} else {
|
||||
return .complete()
|
||||
|
|
@ -434,7 +440,7 @@ public class GiftAuctionsManager {
|
|||
|
||||
public extension GiftAuctionContext.State {
|
||||
var place: Int32? {
|
||||
guard case let .ongoing(_, _, bidLevels, _, _, _, _, _) = self.auctionState, let myBid = self.myState.bidAmount, let myBidDate = self.myState.bidDate else {
|
||||
guard case let .ongoing(_, _, _, _, bidLevels, _, _, _, _, _) = self.auctionState, let myBid = self.myState.bidAmount, let myBidDate = self.myState.bidDate else {
|
||||
return nil
|
||||
}
|
||||
var place: Int32 = 1
|
||||
|
|
@ -445,4 +451,22 @@ public extension GiftAuctionContext.State {
|
|||
}
|
||||
return place
|
||||
}
|
||||
|
||||
var startDate: Int32 {
|
||||
switch self.auctionState {
|
||||
case let .ongoing(_, startDate, _, _, _, _, _, _, _, _):
|
||||
return startDate
|
||||
case let .finished(startDate, _, _):
|
||||
return startDate
|
||||
}
|
||||
}
|
||||
|
||||
var endDate: Int32 {
|
||||
switch self.auctionState {
|
||||
case let .ongoing(_, _, endDate, _, _, _, _, _, _, _):
|
||||
return endDate
|
||||
case let .finished(_, endDate, _):
|
||||
return endDate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,6 +268,9 @@ public enum PresentationResourceKey: Int32 {
|
|||
case chatMessageAttachedContentHighlightedButtonIconLinkOutgoingWithWallpaper
|
||||
case chatMessageAttachedContentHighlightedButtonIconLinkOutgoingWithoutWallpaper
|
||||
|
||||
case chatMessageAttachedContentButtonIconBidIncoming
|
||||
case chatMessageAttachedContentButtonIconBidOutgoing
|
||||
|
||||
case chatCommandPanelArrowImage
|
||||
|
||||
case sharedMediaFileDownloadStartIcon
|
||||
|
|
|
|||
|
|
@ -869,7 +869,7 @@ public struct PresentationResourcesChat {
|
|||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLink"), color: theme.chat.message.incoming.accentControlColor)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public static func chatMessageAttachedContentHighlightedButtonIconLinkIncoming(_ theme: PresentationTheme, wallpaper: Bool) -> UIImage? {
|
||||
let key: PresentationResourceKey = !wallpaper ? PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconLinkIncomingWithoutWallpaper : PresentationResourceKey.chatMessageAttachedContentHighlightedButtonIconLinkIncomingWithWallpaper
|
||||
return theme.image(key.rawValue, { theme in
|
||||
|
|
@ -877,6 +877,12 @@ public struct PresentationResourcesChat {
|
|||
})
|
||||
}
|
||||
|
||||
public static func chatMessageAttachedContentButtonIconBidIncoming(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatMessageAttachedContentButtonIconBidIncoming.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Premium/Auction/BidSmall"), color: theme.chat.message.incoming.accentControlColor)
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatMessageAttachedContentButtonIconLinkOutgoing(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatMessageAttachedContentButtonIconLinkOutgoing.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotLink"), color: theme.chat.message.outgoing.accentControlColor)
|
||||
|
|
@ -890,6 +896,12 @@ public struct PresentationResourcesChat {
|
|||
})
|
||||
}
|
||||
|
||||
public static func chatMessageAttachedContentButtonIconBidOutgoing(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatMessageAttachedContentButtonIconBidOutgoing.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Premium/Auction/BidSmall"), color: theme.chat.message.outgoing.accentControlColor)
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatBubbleReplyThumbnailPlayImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatBubbleReplyThumbnailPlayImage.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
|
||||
|
|
|
|||
|
|
@ -1171,7 +1171,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = mutableString
|
||||
case .prizeStars:
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor)
|
||||
case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _, isPrepaidUpgrade, _, peerId, senderId, _, _, _, upgradeSeparate):
|
||||
case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _, isPrepaidUpgrade, _, peerId, senderId, _, _, _, upgradeSeparate, isAuctionAcquired, _):
|
||||
if !forAdditionalServiceMessage {
|
||||
if let text {
|
||||
let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities ?? [], baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage()))
|
||||
|
|
@ -1196,7 +1196,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
authorName = strings.Notification_StarsGift_UnknownUser
|
||||
peerIds = []
|
||||
}
|
||||
if message.id.peerId.isTelegramNotifications && senderId == nil {
|
||||
if isAuctionAcquired {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_GiftAuction_Acquired(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
} else if message.id.peerId.isTelegramNotifications && senderId == nil {
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsGift_SentSomeone, font: titleFont, textColor: primaryTextColor)
|
||||
} else if message.id.peerId == accountPeerId {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Self_Bought(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
|
|
|
|||
|
|
@ -381,6 +381,7 @@ public final class ButtonComponent: Component {
|
|||
|
||||
public let background: Background
|
||||
public let content: AnyComponentWithIdentity<Empty>
|
||||
public let fitToContentWidth: Bool
|
||||
public let isEnabled: Bool
|
||||
public let tintWhenDisabled: Bool
|
||||
public let allowActionWhenDisabled: Bool
|
||||
|
|
@ -390,6 +391,7 @@ public final class ButtonComponent: Component {
|
|||
public init(
|
||||
background: Background,
|
||||
content: AnyComponentWithIdentity<Empty>,
|
||||
fitToContentWidth: Bool = false,
|
||||
isEnabled: Bool = true,
|
||||
tintWhenDisabled: Bool = true,
|
||||
allowActionWhenDisabled: Bool = false,
|
||||
|
|
@ -398,6 +400,7 @@ public final class ButtonComponent: Component {
|
|||
) {
|
||||
self.background = background
|
||||
self.content = content
|
||||
self.fitToContentWidth = fitToContentWidth
|
||||
self.isEnabled = isEnabled
|
||||
self.tintWhenDisabled = tintWhenDisabled
|
||||
self.allowActionWhenDisabled = allowActionWhenDisabled
|
||||
|
|
@ -412,6 +415,9 @@ public final class ButtonComponent: Component {
|
|||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
if lhs.fitToContentWidth != rhs.fitToContentWidth {
|
||||
return false
|
||||
}
|
||||
if lhs.isEnabled != rhs.isEnabled {
|
||||
return false
|
||||
}
|
||||
|
|
@ -538,6 +544,12 @@ public final class ButtonComponent: Component {
|
|||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - cornerRadius, height: availableSize.height)
|
||||
)
|
||||
|
||||
var size = availableSize
|
||||
if component.fitToContentWidth {
|
||||
size.width = floor(contentSize.width + cornerRadius * 1.5)
|
||||
}
|
||||
|
||||
if let contentView = contentItem.view.view {
|
||||
var animateIn = false
|
||||
var contentTransition = transition
|
||||
|
|
@ -549,7 +561,7 @@ public final class ButtonComponent: Component {
|
|||
|
||||
contentItem.view.parentState = state
|
||||
}
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - contentSize.height) * 0.5)), size: contentSize)
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - contentSize.width) * 0.5), y: floorToScreenPixels((size.height - contentSize.height) * 0.5)), size: contentSize)
|
||||
|
||||
contentTransition.setFrame(view: contentView, frame: contentFrame)
|
||||
contentTransition.setAlpha(view: contentView, alpha: contentAlpha)
|
||||
|
|
@ -557,7 +569,7 @@ public final class ButtonComponent: Component {
|
|||
if animateIn && previousContentItem != nil && !transition.animation.isImmediate {
|
||||
contentView.layer.animateScale(from: 0.4, to: 1.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
contentView.layer.animateAlpha(from: 0.0, to: contentAlpha, duration: 0.1)
|
||||
contentView.layer.animatePosition(from: CGPoint(x: 0.0, y: -availableSize.height * 0.15), to: CGPoint(), duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
contentView.layer.animatePosition(from: CGPoint(x: 0.0, y: -size.height * 0.15), to: CGPoint(), duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -567,7 +579,7 @@ public final class ButtonComponent: Component {
|
|||
previousContentView.layer.animateAlpha(from: contentAlpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousContentView] _ in
|
||||
previousContentView?.removeFromSuperview()
|
||||
})
|
||||
previousContentView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: availableSize.height * 0.35), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
previousContentView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: size.height * 0.35), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
} else {
|
||||
previousContentView.removeFromSuperview()
|
||||
}
|
||||
|
|
@ -587,7 +599,7 @@ public final class ButtonComponent: Component {
|
|||
}
|
||||
let indicatorSize = CGSize(width: 22.0, height: 22.0)
|
||||
transition.setAlpha(view: activityIndicator.view, alpha: 1.0)
|
||||
activityIndicatorTransition.setFrame(view: activityIndicator.view, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - indicatorSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
activityIndicatorTransition.setFrame(view: activityIndicator.view, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - indicatorSize.width) / 2.0), y: floorToScreenPixels((size.height - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
} else {
|
||||
if let activityIndicator = self.activityIndicator {
|
||||
self.activityIndicator = nil
|
||||
|
|
@ -608,8 +620,8 @@ public final class ButtonComponent: Component {
|
|||
self.shimmeringView = shimmeringView
|
||||
self.containerView.insertSubview(shimmeringView, at: 0)
|
||||
}
|
||||
shimmeringView.update(size: availableSize, background: component.background, cornerRadius: component.background.cornerRadius, transition: shimmeringTransition)
|
||||
shimmeringTransition.setFrame(view: shimmeringView, frame: CGRect(origin: .zero, size: availableSize))
|
||||
shimmeringView.update(size: size, background: component.background, cornerRadius: component.background.cornerRadius, transition: shimmeringTransition)
|
||||
shimmeringTransition.setFrame(view: shimmeringView, frame: CGRect(origin: .zero, size: size))
|
||||
} else if let shimmeringView = self.shimmeringView {
|
||||
self.shimmeringView = nil
|
||||
shimmeringView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { _ in
|
||||
|
|
@ -636,7 +648,7 @@ public final class ButtonComponent: Component {
|
|||
chromeView.alpha = 0.8
|
||||
chromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0), isDark: component.background.color.lightness < 0.36, fillColor: .clear)
|
||||
}
|
||||
chromeTransition.setFrame(view: chromeView, frame: CGRect(origin: .zero, size: availableSize))
|
||||
chromeTransition.setFrame(view: chromeView, frame: CGRect(origin: .zero, size: size))
|
||||
} else if let chromeView = self.chromeView {
|
||||
self.chromeView = nil
|
||||
chromeView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { _ in
|
||||
|
|
@ -644,10 +656,10 @@ public final class ButtonComponent: Component {
|
|||
})
|
||||
}
|
||||
|
||||
transition.setPosition(view: self.containerView, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||
transition.setBoundsSize(view: self.containerView, size: availableSize)
|
||||
transition.setPosition(view: self.containerView, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||
transition.setBoundsSize(view: self.containerView, size: size)
|
||||
|
||||
return availableSize
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1451,6 +1451,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||
let captureControls = captureControls.update(
|
||||
component: CaptureControlsComponent(
|
||||
context: component.context,
|
||||
strings: environment.strings,
|
||||
isTablet: isTablet,
|
||||
isSticker: isSticker,
|
||||
hasGallery: !isSticker && !isAvatar,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Display
|
|||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import Photos
|
||||
import LocalMediaResources
|
||||
import CameraButtonComponent
|
||||
|
|
@ -37,6 +38,7 @@ private extension SimpleShapeLayer {
|
|||
}
|
||||
|
||||
private final class ShutterButtonContentComponent: Component {
|
||||
let strings: PresentationStrings
|
||||
let isTablet: Bool
|
||||
let hasAppeared: Bool
|
||||
let tintColor: UIColor
|
||||
|
|
@ -49,6 +51,7 @@ private final class ShutterButtonContentComponent: Component {
|
|||
let updateOffsetY: ActionSlot<(CGFloat, ComponentTransition)>
|
||||
|
||||
init(
|
||||
strings: PresentationStrings,
|
||||
isTablet: Bool,
|
||||
hasAppeared: Bool,
|
||||
tintColor: UIColor,
|
||||
|
|
@ -60,6 +63,7 @@ private final class ShutterButtonContentComponent: Component {
|
|||
updateOffsetX: ActionSlot<(CGFloat, ComponentTransition)>,
|
||||
updateOffsetY: ActionSlot<(CGFloat, ComponentTransition)>
|
||||
) {
|
||||
self.strings = strings
|
||||
self.isTablet = isTablet
|
||||
self.hasAppeared = hasAppeared
|
||||
self.tintColor = tintColor
|
||||
|
|
@ -673,6 +677,7 @@ final class CaptureControlsComponent: Component {
|
|||
}
|
||||
|
||||
let context: AccountContext
|
||||
let strings: PresentationStrings
|
||||
let isTablet: Bool
|
||||
let isSticker: Bool
|
||||
let hasGallery: Bool
|
||||
|
|
@ -701,6 +706,7 @@ final class CaptureControlsComponent: Component {
|
|||
|
||||
init(
|
||||
context: AccountContext,
|
||||
strings: PresentationStrings,
|
||||
isTablet: Bool,
|
||||
isSticker: Bool,
|
||||
hasGallery: Bool,
|
||||
|
|
@ -728,6 +734,7 @@ final class CaptureControlsComponent: Component {
|
|||
openResolvedPeer: @escaping (EnginePeer) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.isTablet = isTablet
|
||||
self.isSticker = isSticker
|
||||
self.hasGallery = hasGallery
|
||||
|
|
@ -1425,6 +1432,7 @@ final class CaptureControlsComponent: Component {
|
|||
Button(
|
||||
content: AnyComponent(
|
||||
ShutterButtonContentComponent(
|
||||
strings: component.strings,
|
||||
isTablet: component.isTablet,
|
||||
hasAppeared: component.hasAppeared,
|
||||
tintColor: component.tintColor,
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ extension CameraMode {
|
|||
case .video:
|
||||
return strings.Story_Camera_Video
|
||||
case .live:
|
||||
//TODO:localize
|
||||
return "LIVE"
|
||||
return strings.Story_Camera_Live
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import EmojiTextAttachmentView
|
|||
public enum ChatMessageAttachedContentActionIcon {
|
||||
case instant
|
||||
case link
|
||||
case bid
|
||||
}
|
||||
|
||||
public struct ChatMessageAttachedContentNodeMediaFlags: OptionSet {
|
||||
|
|
@ -323,7 +324,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||
|
||||
var mediaAndFlags = mediaAndFlags
|
||||
if let mediaAndFlagsValue = mediaAndFlags {
|
||||
if mediaAndFlagsValue.0.first is TelegramMediaStory || mediaAndFlagsValue.0.first is WallpaperPreviewMedia || mediaAndFlagsValue.0.first is UniqueGiftPreviewMedia {
|
||||
if mediaAndFlagsValue.0.first is TelegramMediaStory || mediaAndFlagsValue.0.first is WallpaperPreviewMedia || mediaAndFlagsValue.0.first is UniqueGiftPreviewMedia || mediaAndFlagsValue.0.first is GiftAuctionPreviewMedia {
|
||||
var flags = mediaAndFlagsValue.1
|
||||
flags.remove(.preferMediaInline)
|
||||
mediaAndFlags = (mediaAndFlagsValue.0, flags)
|
||||
|
|
@ -394,6 +395,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||
contentMediaValue = media
|
||||
} else if media is UniqueGiftPreviewMedia {
|
||||
contentMediaValue = media
|
||||
} else if media is GiftAuctionPreviewMedia {
|
||||
contentMediaValue = media
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -624,6 +627,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||
case .link:
|
||||
buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconLinkIncoming(presentationData.theme.theme)!
|
||||
cornerIcon = true
|
||||
case .bid:
|
||||
buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconBidIncoming(presentationData.theme.theme)!
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -634,6 +639,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||
case .link:
|
||||
buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconLinkOutgoing(presentationData.theme.theme)!
|
||||
cornerIcon = true
|
||||
case .bid:
|
||||
buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconBidOutgoing(presentationData.theme.theme)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
for media in item.message.media {
|
||||
if let action = media as? TelegramMediaAction {
|
||||
switch action.action {
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
|
|
@ -554,7 +554,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View
|
||||
hasServiceMessage = false
|
||||
}
|
||||
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, _, channelPeerId, senderPeerId, _, _, _, _):
|
||||
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, _, channelPeerId, senderPeerId, _, _, _, _, _, _):
|
||||
if case let .generic(gift) = gift {
|
||||
if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName {
|
||||
creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string
|
||||
|
|
|
|||
|
|
@ -1028,6 +1028,9 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
} else if let _ = media as? UniqueGiftPreviewMedia {
|
||||
isGift = true
|
||||
unboundSize = CGSize(width: 200.0, height: 200.0)
|
||||
} else if let _ = media as? GiftAuctionPreviewMedia {
|
||||
isGift = true
|
||||
unboundSize = CGSize(width: 200.0, height: 200.0)
|
||||
} else {
|
||||
var extendedMedia: TelegramExtendedMedia?
|
||||
if let invoice = media as? TelegramMediaInvoice, let selectedMedia = invoice.extendedMedia {
|
||||
|
|
@ -1832,6 +1835,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
}
|
||||
} else if let _ = media as? UniqueGiftPreviewMedia {
|
||||
updatedStatusSignal = .single((.Local, nil))
|
||||
} else if let _ = media as? GiftAuctionPreviewMedia {
|
||||
updatedStatusSignal = .single((.Local, nil))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2204,6 +2209,38 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
containerSize: imageFrame.size
|
||||
)
|
||||
|
||||
if let giftView = giftView.view {
|
||||
if giftView.superview == nil {
|
||||
strongSelf.pinchContainerNode.contentNode.view.addSubview(giftView)
|
||||
}
|
||||
giftView.frame = imageFrame
|
||||
}
|
||||
} else if let giftPreview = media as? GiftAuctionPreviewMedia, let gift = giftPreview.content {
|
||||
let giftView: ComponentView<Empty>
|
||||
if let current = strongSelf.giftView {
|
||||
giftView = current
|
||||
} else {
|
||||
giftView = ComponentView()
|
||||
strongSelf.giftView = giftView
|
||||
}
|
||||
|
||||
let _ = giftView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: context,
|
||||
theme: presentationData.theme.theme,
|
||||
strings: presentationData.strings,
|
||||
subject: .auction(gift: gift, centerColor: giftPreview.centerColor, edgeColor: giftPreview.edgeColor, endTime: giftPreview.endTime),
|
||||
title: gift.title ?? "",
|
||||
subtitle: presentationData.strings.Chat_Auction_Gifts(gift.availability?.total ?? 0),
|
||||
mode: .preview
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: imageFrame.size
|
||||
)
|
||||
|
||||
if let giftView = giftView.view {
|
||||
if giftView.superview == nil {
|
||||
strongSelf.pinchContainerNode.contentNode.view.addSubview(giftView)
|
||||
|
|
|
|||
|
|
@ -343,6 +343,14 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
break
|
||||
}
|
||||
}
|
||||
case "telegram_auction":
|
||||
for attribute in webpage.attributes {
|
||||
if case let .giftAuction(giftAuction) = attribute, case let .generic(gift) = giftAuction.gift {
|
||||
let media = GiftAuctionPreviewMedia(content: gift, centerColor: UIColor(rgb: UInt32(bitPattern: giftAuction.centerColor)), edgeColor: UIColor(rgb: UInt32(bitPattern: giftAuction.edgeColor)), endTime: giftAuction.endDate)
|
||||
mediaAndFlags = ([media], [])
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
if var file = mainMedia as? TelegramMediaFile, webpage.type != "telegram_theme" {
|
||||
if webpage.imageIsVideoCover, let image = webpage.image {
|
||||
|
|
@ -480,6 +488,21 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
actionTitle = item.presentationData.strings.Chat_ViewCollection
|
||||
case "telegram_story_album":
|
||||
actionTitle = item.presentationData.strings.Chat_ViewAlbum
|
||||
case "telegram_auction":
|
||||
var hasEnded = false
|
||||
for attribute in webpage.attributes {
|
||||
if case let .giftAuction(giftAuction) = attribute {
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
if giftAuction.endDate < currentTime {
|
||||
hasEnded = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
title = item.presentationData.strings.Chat_Auction
|
||||
text = nil
|
||||
actionTitle = hasEnded ? item.presentationData.strings.Chat_Auction_ViewResults : item.presentationData.strings.Chat_Auction_Join
|
||||
actionIcon = !hasEnded ? .bid : nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1462,6 +1462,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||
break
|
||||
case .giftCollection:
|
||||
break
|
||||
case .auction:
|
||||
break
|
||||
case .sendGift:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,12 @@ public final class GiftCompositionComponent: Component {
|
|||
private var animatePreviewTransition = false
|
||||
private var animateBackdropSwipe = false
|
||||
|
||||
private enum SpinState { case idle, spinning, decelerating, settled }
|
||||
private enum SpinState {
|
||||
case idle
|
||||
case spinning
|
||||
case decelerating
|
||||
case settled
|
||||
}
|
||||
private var spinState: SpinState = .idle
|
||||
private var spinLink: SharedDisplayLinkDriver.Link?
|
||||
private var lastSpawnTime: CFTimeInterval?
|
||||
|
|
@ -124,18 +129,17 @@ public final class GiftCompositionComponent: Component {
|
|||
private var decelerationStepIndex: Int = 0
|
||||
private var decelContainer: UIView?
|
||||
private var decelItemHosts: [UIView] = []
|
||||
private let decelAnimationKey = "decel.container.move"
|
||||
|
||||
private var activeWrappers: [UIView] = []
|
||||
|
||||
private struct SpinGeom {
|
||||
private struct SpinParams {
|
||||
var availableSize: CGSize
|
||||
var iconSize: CGSize
|
||||
var scale: CGFloat
|
||||
var centerX: CGFloat
|
||||
var centerY: CGFloat
|
||||
}
|
||||
private var spinGeom: SpinGeom?
|
||||
private var spinGeom: SpinParams?
|
||||
|
||||
private var spinPool: [StarGift.UniqueGift.Attribute] = []
|
||||
private var spinPoolIndex: Int = 0
|
||||
|
|
@ -285,7 +289,7 @@ public final class GiftCompositionComponent: Component {
|
|||
self.spinPoolIndex = 0
|
||||
let centerY = 88.0 + (self.component?.animationOffset?.y ?? 0.0)
|
||||
|
||||
self.spinGeom = SpinGeom(
|
||||
self.spinGeom = SpinParams(
|
||||
availableSize: availableSize,
|
||||
iconSize: iconSize,
|
||||
scale: scale,
|
||||
|
|
@ -893,7 +897,7 @@ public final class GiftCompositionComponent: Component {
|
|||
)
|
||||
} else if self.spinState == .spinning {
|
||||
let centerY = 88.0 + (component.animationOffset?.y ?? 0.0)
|
||||
self.spinGeom = SpinGeom(
|
||||
self.spinGeom = SpinParams(
|
||||
availableSize: availableSize,
|
||||
iconSize: iconSize,
|
||||
scale: scaleValue,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import PeerInfoCoverComponent
|
|||
import Markdown
|
||||
import CheckNode
|
||||
import BundleIconComponent
|
||||
import AnimatedTextComponent
|
||||
|
||||
public final class GiftItemComponent: Component {
|
||||
public enum Style {
|
||||
|
|
@ -28,6 +29,7 @@ public final class GiftItemComponent: Component {
|
|||
case premium(months: Int32, price: String)
|
||||
case starGift(gift: StarGift.Gift, price: String)
|
||||
case uniqueGift(gift: StarGift.UniqueGift, price: String?)
|
||||
case auction(gift: StarGift.Gift, centerColor: UIColor, edgeColor: UIColor, endTime: Int32)
|
||||
}
|
||||
|
||||
public struct Ribbon: Equatable {
|
||||
|
|
@ -141,6 +143,7 @@ public final class GiftItemComponent: Component {
|
|||
case select
|
||||
case buttonIcon
|
||||
case tableIcon
|
||||
case header
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
|
|
@ -298,6 +301,9 @@ public final class GiftItemComponent: Component {
|
|||
private let button = ComponentView<Empty>()
|
||||
private let label = ComponentView<Empty>()
|
||||
private let ton = ComponentView<Empty>()
|
||||
|
||||
private let badgeText = ComponentView<Empty>()
|
||||
private let badgeBackground = ComponentView<Empty>()
|
||||
|
||||
private let ribbonOutline = UIImageView()
|
||||
private let ribbon = UIImageView()
|
||||
|
|
@ -321,6 +327,8 @@ public final class GiftItemComponent: Component {
|
|||
private var resellBackground: BlurredBackgroundView?
|
||||
private let reselLabel = ComponentView<Empty>()
|
||||
|
||||
private var giftAuctionTimer: SwiftSignalKit.Timer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
|
|
@ -348,6 +356,7 @@ public final class GiftItemComponent: Component {
|
|||
|
||||
deinit {
|
||||
self.disposables.dispose()
|
||||
self.giftAuctionTimer?.invalidate()
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
|
|
@ -408,6 +417,10 @@ public final class GiftItemComponent: Component {
|
|||
size = CGSize(width: 18.0, height: 18.0)
|
||||
iconSize = size
|
||||
cornerRadius = 0.0
|
||||
case .header:
|
||||
size = availableSize
|
||||
iconSize = CGSize(width: 106.0, height: 106.0)
|
||||
cornerRadius = 16.0
|
||||
}
|
||||
var backgroundSize = size
|
||||
if case .grid = component.mode {
|
||||
|
|
@ -453,10 +466,13 @@ public final class GiftItemComponent: Component {
|
|||
var patternFile: TelegramMediaFile?
|
||||
var files: [Int64: TelegramMediaFile] = [:]
|
||||
|
||||
var animatedBadgeItems: [AnimatedTextComponent.Item] = []
|
||||
|
||||
var placeholderColor = component.theme.list.mediaPlaceholderColor
|
||||
|
||||
let emoji: ChatTextInputTextCustomEmojiAttribute?
|
||||
var animationOffset: CGFloat = 0.0
|
||||
var explicitAnimationOffset: CGFloat = 0.0
|
||||
switch component.subject {
|
||||
case let .premium(months, _):
|
||||
emoji = ChatTextInputTextCustomEmojiAttribute(
|
||||
|
|
@ -507,6 +523,48 @@ public final class GiftItemComponent: Component {
|
|||
} else {
|
||||
emoji = nil
|
||||
}
|
||||
case let .auction(gift, centerColor, edgeColor, endTime):
|
||||
animationOffset = 16.0
|
||||
explicitAnimationOffset = -16.0
|
||||
animationFile = gift.file
|
||||
backgroundColor = edgeColor
|
||||
secondBackgroundColor = centerColor
|
||||
|
||||
emoji = ChatTextInputTextCustomEmojiAttribute(
|
||||
interactivelySelectedFromPackId: nil,
|
||||
fileId: gift.file.fileId.id,
|
||||
file: gift.file
|
||||
)
|
||||
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
let endTimeout = max(0, endTime - currentTime)
|
||||
|
||||
if endTimeout > 0 {
|
||||
let hours = Int(endTimeout / 3600)
|
||||
let minutes = Int((endTimeout % 3600) / 60)
|
||||
let seconds = Int(endTimeout % 60)
|
||||
|
||||
if hours > 0 {
|
||||
animatedBadgeItems.append(AnimatedTextComponent.Item(id: "h", content: .number(hours, minDigits: 1)))
|
||||
animatedBadgeItems.append(AnimatedTextComponent.Item(id: "colon1", content: .text(":")))
|
||||
animatedBadgeItems.append(AnimatedTextComponent.Item(id: "m", content: .number(minutes, minDigits: 2)))
|
||||
animatedBadgeItems.append(AnimatedTextComponent.Item(id: "colon2", content: .text(":")))
|
||||
animatedBadgeItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
|
||||
} else {
|
||||
animatedBadgeItems.append(AnimatedTextComponent.Item(id: "m", content: .number(minutes, minDigits: 2)))
|
||||
animatedBadgeItems.append(AnimatedTextComponent.Item(id: "colon2", content: .text(":")))
|
||||
animatedBadgeItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
|
||||
}
|
||||
} else {
|
||||
animatedBadgeItems.append(AnimatedTextComponent.Item(id: "finished", content: .text(component.strings.Chat_Auction_Finished)))
|
||||
}
|
||||
|
||||
if self.giftAuctionTimer == nil {
|
||||
self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
|
||||
self?.componentState?.updated()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.giftAuctionTimer?.start()
|
||||
}
|
||||
}
|
||||
|
||||
if [.buttonIcon, .tableIcon].contains(component.mode) {
|
||||
|
|
@ -547,7 +605,7 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
let animationFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - iconSize.width) / 2.0), y: component.mode == .generic ? animationOffset : floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
let animationFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - iconSize.width) / 2.0), y: component.mode == .generic ? animationOffset : (floorToScreenPixels((size.height - iconSize.height) / 2.0) + explicitAnimationOffset)), size: iconSize)
|
||||
if let animationLayer = self.animationLayer {
|
||||
animationTransition.setFrame(layer: animationLayer, frame: animationFrame)
|
||||
}
|
||||
|
|
@ -582,7 +640,84 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
if case .generic = component.mode {
|
||||
if case .preview = component.mode {
|
||||
if let title = component.title {
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)),
|
||||
horizontalAlignment: .center
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: size.height - 52.0), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
}
|
||||
|
||||
if let subtitle = component.subtitle {
|
||||
let subtitleSize = self.subtitle.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: subtitle, font: Font.regular(14.0), textColor: .white.withAlphaComponent(0.5))),
|
||||
horizontalAlignment: .center
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - subtitleSize.width) / 2.0), y: size.height - 33.0), size: subtitleSize)
|
||||
if let subtitleView = self.subtitle.view {
|
||||
if subtitleView.superview == nil {
|
||||
self.addSubview(subtitleView)
|
||||
}
|
||||
transition.setFrame(view: subtitleView, frame: subtitleFrame)
|
||||
}
|
||||
}
|
||||
|
||||
if !animatedBadgeItems.isEmpty {
|
||||
let badgeTextSize = self.badgeText.update(
|
||||
transition: .spring(duration: 0.2),
|
||||
component: AnyComponent(
|
||||
AnimatedTextComponent(
|
||||
font: Font.with(size: 11.0, weight: .regular, traits: .monospacedNumbers),
|
||||
color: .white,
|
||||
items: animatedBadgeItems,
|
||||
noDelay: true
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let badgeBackgroundSize = CGSize(width: badgeTextSize.width + 8.0, height: 18.0)
|
||||
let _ = self.badgeBackground.update(
|
||||
transition: .spring(duration: 0.2),
|
||||
component: AnyComponent(
|
||||
RoundedRectangle(color: UIColor(white: 0.0, alpha: 0.2), cornerRadius: 9.0)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: badgeBackgroundSize
|
||||
)
|
||||
|
||||
if let badgeBackgroundView = self.badgeBackground.view, let badgeTextView = self.badgeText.view {
|
||||
if badgeBackgroundView.superview == nil {
|
||||
self.addSubview(badgeBackgroundView)
|
||||
self.addSubview(badgeTextView)
|
||||
}
|
||||
badgeTextView.frame = CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: badgeTextSize)
|
||||
badgeBackgroundView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(badgeTextView.frame.center.x - badgeBackgroundSize.width / 2.0), y: floorToScreenPixels(badgeTextView.frame.center.y - badgeBackgroundSize.height / 2.0)), size: badgeBackgroundSize)
|
||||
}
|
||||
}
|
||||
} else if case .generic = component.mode {
|
||||
if let title = component.title {
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
|
|
@ -631,10 +766,9 @@ public final class GiftItemComponent: Component {
|
|||
let price: String
|
||||
switch component.subject {
|
||||
case let .premium(_, priceValue), let .starGift(_, priceValue):
|
||||
if case let .starGift(gift, _) = component.subject, gift.flags.contains(.isAuction) {
|
||||
if case let .starGift(gift, priceValue) = component.subject, gift.flags.contains(.isAuction) {
|
||||
buttonColor = component.theme.overallDarkAppearance ? UIColor(rgb: 0xffc337) : UIColor(rgb: 0xd3720a)
|
||||
//todo:localize
|
||||
price = "Join"
|
||||
price = priceValue
|
||||
} else {
|
||||
if priceValue.contains("#") {
|
||||
buttonColor = component.theme.overallDarkAppearance ? UIColor(rgb: 0xffc337) : UIColor(rgb: 0xd3720a)
|
||||
|
|
@ -656,6 +790,10 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
price = priceValue ?? component.strings.Gift_Options_Gift_Transfer
|
||||
tinted = true
|
||||
case .auction:
|
||||
buttonColor = .clear
|
||||
price = ""
|
||||
break
|
||||
}
|
||||
|
||||
let buttonSize = self.button.update(
|
||||
|
|
@ -1160,7 +1298,12 @@ public final class GiftItemComponent: Component {
|
|||
context.translateBy(x: 0.0, y: size.height)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(x: size.width - 58.0, y: 91.0 - UIScreenPixel), size: ribbonOutline.size), mask: cgImage)
|
||||
var maskY = 91.0 - UIScreenPixel
|
||||
if size.height < 121.0 {
|
||||
maskY = 57.0 - UIScreenPixel
|
||||
}
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(x: size.width - 58.0 - UIScreenPixel, y: maskY), size: ribbonOutline.size), mask: cgImage)
|
||||
context.setBlendMode(.clear)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fill(CGRect(origin: .zero, size: size))
|
||||
|
|
@ -1172,7 +1315,6 @@ public final class GiftItemComponent: Component {
|
|||
|
||||
outlineLayer.frame = outlineFrame
|
||||
}
|
||||
|
||||
} else if let outlineLayer = self.outlineLayer {
|
||||
self.outlineLayer = nil
|
||||
outlineLayer.removeFromSuperlayer()
|
||||
|
|
|
|||
|
|
@ -240,6 +240,8 @@ final class GiftOptionsScreenComponent: Component {
|
|||
|
||||
private var dismissed = false
|
||||
|
||||
private var auctionDisposable = MetaDisposable()
|
||||
|
||||
private var chevronImage: (UIImage, PresentationTheme)?
|
||||
|
||||
private var resaleConfiguration: StarsSubscriptionConfiguration?
|
||||
|
|
@ -274,6 +276,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||
|
||||
deinit {
|
||||
self.starsStateDisposable?.dispose()
|
||||
self.auctionDisposable.dispose()
|
||||
}
|
||||
|
||||
func scrollToTop() {
|
||||
|
|
@ -363,10 +366,58 @@ final class GiftOptionsScreenComponent: Component {
|
|||
}
|
||||
|
||||
if gift.flags.contains(.isAuction) {
|
||||
// let giftController = component.context.sharedContext.makeGiftAuctionViewScreen(
|
||||
// context: component,
|
||||
// auctionContext: <#T##GiftAuctionContext#>
|
||||
// )
|
||||
guard let giftAuctionsManager = component.context.giftAuctionsManager else {
|
||||
return
|
||||
}
|
||||
self.auctionDisposable.set((giftAuctionsManager.auctionContext(for: .giftId(gift.id))
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak mainController] auctionContext in
|
||||
guard let auctionContext, let component = self?.component, let mainController else {
|
||||
return
|
||||
}
|
||||
if let currentBidPeerId = auctionContext.currentBidPeerId {
|
||||
if currentBidPeerId == component.peerId {
|
||||
let giftController = component.context.sharedContext.makeGiftAuctionBidScreen(
|
||||
context: component.context,
|
||||
auctionContext: auctionContext
|
||||
)
|
||||
mainController.push(giftController)
|
||||
} else {
|
||||
let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: currentBidPeerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak mainController] peer in
|
||||
guard let component = self?.component, let environment = self?.environment, let mainController else {
|
||||
return
|
||||
}
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let alertController = textAlertController(
|
||||
context: component.context,
|
||||
title: environment.strings.Gift_Auction_Ongoing_Title,
|
||||
text: auctionContext.currentBidPeerId == component.context.account.peerId ? environment.strings.Gift_Auction_Ongoing_TextYourself : environment.strings.Gift_Auction_Ongoing_Text(peer?.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder) ?? "").string,
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: environment.strings.Gift_Auction_Ongoing_View, action: { [weak mainController] in
|
||||
guard let mainController else {
|
||||
return
|
||||
}
|
||||
let giftController = component.context.sharedContext.makeGiftAuctionBidScreen(
|
||||
context: component.context,
|
||||
auctionContext: auctionContext
|
||||
)
|
||||
mainController.push(giftController)
|
||||
})
|
||||
],
|
||||
parseMarkdown: true
|
||||
)
|
||||
mainController.present(alertController, in: .window(.root))
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let giftController = component.context.sharedContext.makeGiftAuctionViewScreen(
|
||||
context: component.context,
|
||||
auctionContext: auctionContext
|
||||
)
|
||||
mainController.push(giftController)
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
if let availability = gift.availability, availability.remains == 0 {
|
||||
if availability.resale > 0 {
|
||||
|
|
@ -529,8 +580,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||
let text: String
|
||||
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
|
||||
if gift.flags.contains(.isAuction) {
|
||||
//TODO:localize
|
||||
text = "auction"
|
||||
text = environment.strings.Gift_Options_Gift_Auction
|
||||
ribbonColor = .orange
|
||||
outline = .orange
|
||||
} else if let perUserLimit = gift.perUserLimit {
|
||||
|
|
@ -546,8 +596,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||
if !isSoldOut && gift.flags.contains(.requiresPremium) {
|
||||
let text: String
|
||||
if gift.flags.contains(.isAuction) {
|
||||
//TODO:localize
|
||||
text = "auction"
|
||||
text = environment.strings.Gift_Options_Gift_Auction
|
||||
} else if component.context.isPremium, let perUserLimit = gift.perUserLimit {
|
||||
text = environment.strings.Gift_Options_Gift_Premium_Left(perUserLimit.remains)
|
||||
} else {
|
||||
|
|
@ -582,7 +631,9 @@ final class GiftOptionsScreenComponent: Component {
|
|||
let subject: GiftItemComponent.Subject
|
||||
switch gift {
|
||||
case let .generic(gift):
|
||||
if let availability = gift.availability, availability.remains == 0, let minResaleStars = availability.minResaleStars {
|
||||
if gift.flags.contains(.isAuction) {
|
||||
subject = .starGift(gift: gift, price: "Join")
|
||||
} else if let availability = gift.availability, availability.remains == 0, let minResaleStars = availability.minResaleStars {
|
||||
let priceString = presentationStringsFormattedNumber(Int32(minResaleStars), environment.dateTimeFormat.groupingSeparator)
|
||||
if let resaleConfiguration = self.resaleConfiguration, minResaleStars == resaleConfiguration.starGiftResaleMaxStarsAmount || availability.resale == 1 {
|
||||
subject = .starGift(gift: gift, price: "# \(priceString)")
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
|
|||
case let .starGift(gift):
|
||||
media = [
|
||||
TelegramMediaAction(
|
||||
action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: gift.upgradeStars != nil, upgradeStars: item.upgradeStars, isRefunded: false, isPrepaidUpgrade: false, upgradeMessageId: nil, peerId: nil, senderId: nil, savedId: nil, prepaidUpgradeHash: nil, giftMessageId: nil, upgradeSeparate: false)
|
||||
action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: gift.upgradeStars != nil, upgradeStars: item.upgradeStars, isRefunded: false, isPrepaidUpgrade: false, upgradeMessageId: nil, peerId: nil, senderId: nil, savedId: nil, prepaidUpgradeHash: nil, giftMessageId: nil, upgradeSeparate: false, isAuctionAcquired: false, toPeerId: nil)
|
||||
)
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,6 +241,40 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
|||
}
|
||||
self.bottomEdgeEffectView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
|
||||
private func openPeer(_ peer: EnginePeer, dismiss: Bool = true) {
|
||||
guard let component = self.component, let controller = self.environment?.controller() as? GiftAuctionAcquiredScreen, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = component.context
|
||||
let action = {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
chatController: nil,
|
||||
context: context,
|
||||
chatLocation: .peer(peer),
|
||||
subject: nil,
|
||||
botStart: nil,
|
||||
updateTextInputState: nil,
|
||||
keepStack: .always,
|
||||
useExisting: true,
|
||||
purposefulAction: nil,
|
||||
scrollToEndIfExists: false,
|
||||
activateMessageSearch: nil,
|
||||
animated: true
|
||||
))
|
||||
}
|
||||
|
||||
if dismiss {
|
||||
controller.dismiss()
|
||||
Queue.mainQueue().after(0.4, {
|
||||
action()
|
||||
})
|
||||
} else {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: GiftAuctionAcquiredScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
|
|
@ -268,15 +302,6 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
|||
if themeUpdated {
|
||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
self.backgroundLayer.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor.cgColor
|
||||
|
||||
var locations: [NSNumber] = []
|
||||
var colors: [CGColor] = []
|
||||
let numStops = 6
|
||||
for i in 0 ..< numStops {
|
||||
let step = CGFloat(i) / CGFloat(numStops - 1)
|
||||
locations.append(step as NSNumber)
|
||||
colors.append(environment.theme.list.blocksBackgroundColor.withAlphaComponent(1.0 - step * step).cgColor)
|
||||
}
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
|
@ -325,7 +350,7 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
|||
AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Round #\(gift.round)", font: tableBoldFont, textColor: tableTextColor)))
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_Acquired_Round("\(gift.round)").string, font: tableBoldFont, textColor: tableTextColor)))
|
||||
)
|
||||
)
|
||||
], spacing: 1.0))
|
||||
|
|
@ -334,7 +359,7 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
|||
|
||||
items.append(.init(
|
||||
id: "recipient",
|
||||
title: "Recipient",
|
||||
title: environment.strings.Gift_Acquired_Recipient,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
PeerCellComponent(
|
||||
|
|
@ -344,15 +369,18 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
|||
peer: gift.peer
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openPeer(gift.peer, dismiss: false)
|
||||
}
|
||||
))
|
||||
))
|
||||
|
||||
items.append(.init(
|
||||
id: "date",
|
||||
title: "Date",
|
||||
title: environment.strings.Gift_Acquired_Date,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: gift.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat), font: tableFont, textColor: tableTextColor))))
|
||||
))
|
||||
|
||||
|
|
@ -366,7 +394,7 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
|||
|
||||
items.append(.init(
|
||||
id: "bid",
|
||||
title: "Accepted Bid",
|
||||
title: environment.strings.Gift_Acquired_AcceptedBid,
|
||||
component: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: "stars", component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
|
|
@ -381,11 +409,10 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
|||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: component.context,
|
||||
text: "TOP \(gift.position)",
|
||||
text: environment.strings.Gift_Acquired_Top("\(gift.position)").string,
|
||||
color: environment.theme.list.itemAccentColor
|
||||
)),
|
||||
action: {
|
||||
|
||||
}
|
||||
))
|
||||
)
|
||||
|
|
@ -463,13 +490,7 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
|||
let title = self.title
|
||||
let actionButton = self.actionButton
|
||||
|
||||
let titleText: String
|
||||
if component.acquiredGifts.count == 1 {
|
||||
titleText = "1 Item Bought"
|
||||
} else {
|
||||
titleText = "\(component.acquiredGifts.count) Items Bought"
|
||||
}
|
||||
|
||||
let titleText = environment.strings.Gift_Acquired_Title(Int32(component.acquiredGifts.count))
|
||||
let titleSize = title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
|
|
@ -617,14 +638,6 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
|
|||
}
|
||||
|
||||
public class GiftAuctionAcquiredScreen: ViewControllerComponentContainer {
|
||||
public final class TransitionOut {
|
||||
public let sourceView: UIView
|
||||
|
||||
init(sourceView: UIView) {
|
||||
self.sourceView = sourceView
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
|
||||
private var didPlayAppearAnimation: Bool = false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,774 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import AccountContext
|
||||
import ViewControllerComponent
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import ButtonComponent
|
||||
import PlainButtonComponent
|
||||
import Markdown
|
||||
import BundleIconComponent
|
||||
import TextFormat
|
||||
import TelegramStringFormatting
|
||||
import GlassBarButtonComponent
|
||||
import GiftItemComponent
|
||||
import EdgeEffect
|
||||
import AnimatedTextComponent
|
||||
|
||||
private final class GiftAuctionActiveBidsScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
|
||||
init(
|
||||
context: AccountContext
|
||||
) {
|
||||
self.context = context
|
||||
}
|
||||
|
||||
static func ==(lhs: GiftAuctionActiveBidsScreenComponent, rhs: GiftAuctionActiveBidsScreenComponent) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
private struct ItemLayout: Equatable {
|
||||
var containerSize: CGSize
|
||||
var containerInset: CGFloat
|
||||
var containerCornerRadius: CGFloat
|
||||
var bottomInset: CGFloat
|
||||
var topInset: CGFloat
|
||||
|
||||
init(containerSize: CGSize, containerInset: CGFloat, containerCornerRadius: CGFloat, bottomInset: CGFloat, topInset: CGFloat) {
|
||||
self.containerSize = containerSize
|
||||
self.containerInset = containerInset
|
||||
self.containerCornerRadius = containerCornerRadius
|
||||
self.bottomInset = bottomInset
|
||||
self.topInset = topInset
|
||||
}
|
||||
}
|
||||
|
||||
private final class ScrollView: UIScrollView {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let dimView: UIView
|
||||
private let containerView: UIView
|
||||
private let backgroundLayer: SimpleLayer
|
||||
private let navigationBarContainer: SparseContainerView
|
||||
private let scrollView: ScrollView
|
||||
private let scrollContentClippingView: SparseContainerView
|
||||
private let scrollContentView: UIView
|
||||
|
||||
private let topEdgeEffectView: EdgeEffectView
|
||||
|
||||
private let backgroundHandleView: UIImageView
|
||||
|
||||
private let closeButton = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
private var itemsViews: [Int64: ComponentView<Empty>] = [:]
|
||||
|
||||
private var auctionStates: [GiftAuctionContext.State] = []
|
||||
private var auctionStatesDisposable: Disposable?
|
||||
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var giftAuctionTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var component: GiftAuctionActiveBidsScreenComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
private var environment: ViewControllerComponentContainer.Environment?
|
||||
private var itemLayout: ItemLayout?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.dimView = UIView()
|
||||
self.containerView = UIView()
|
||||
|
||||
self.containerView.clipsToBounds = true
|
||||
self.containerView.layer.cornerRadius = 40.0
|
||||
self.containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.backgroundLayer.cornerRadius = 40.0
|
||||
|
||||
self.backgroundHandleView = UIImageView()
|
||||
|
||||
self.navigationBarContainer = SparseContainerView()
|
||||
|
||||
self.scrollView = ScrollView()
|
||||
|
||||
self.scrollContentClippingView = SparseContainerView()
|
||||
self.scrollContentClippingView.clipsToBounds = true
|
||||
|
||||
self.scrollContentView = UIView()
|
||||
|
||||
self.topEdgeEffectView = EdgeEffectView()
|
||||
self.topEdgeEffectView.clipsToBounds = true
|
||||
self.topEdgeEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.topEdgeEffectView.layer.cornerRadius = 40.0
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.dimView)
|
||||
self.addSubview(self.containerView)
|
||||
self.containerView.layer.addSublayer(self.backgroundLayer)
|
||||
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
if #available(iOS 13.0, *) {
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
}
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.alwaysBounceVertical = true
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
self.scrollView.clipsToBounds = true
|
||||
|
||||
self.containerView.addSubview(self.scrollContentClippingView)
|
||||
self.scrollContentClippingView.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.addSubview(self.scrollContentView)
|
||||
|
||||
self.containerView.addSubview(self.navigationBarContainer)
|
||||
|
||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.auctionStatesDisposable?.dispose()
|
||||
self.giftAuctionTimer?.invalidate()
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.ignoreScrolling {
|
||||
self.updateScrolling(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
if !self.backgroundLayer.frame.contains(point) {
|
||||
return self.dimView
|
||||
}
|
||||
|
||||
if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) {
|
||||
return result
|
||||
}
|
||||
let result = super.hitTest(point, with: event)
|
||||
return result
|
||||
}
|
||||
|
||||
@objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
guard let environment = self.environment, let controller = environment.controller() else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateScrolling(transition: ComponentTransition) {
|
||||
guard let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
|
||||
topOffset = max(0.0, topOffset)
|
||||
transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
|
||||
|
||||
transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset))
|
||||
|
||||
var topOffsetFraction = self.scrollView.bounds.minY / 100.0
|
||||
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
|
||||
|
||||
let minScale: CGFloat = (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width
|
||||
let minScaledTranslation: CGFloat = (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0
|
||||
let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius
|
||||
|
||||
let scale = minScale * (1.0 - topOffsetFraction) + 1.0 * topOffsetFraction
|
||||
let scaledTranslation = minScaledTranslation * (1.0 - topOffsetFraction)
|
||||
let scaledCornerRadius = minScaledCornerRadius * (1.0 - topOffsetFraction) + itemLayout.containerCornerRadius * topOffsetFraction
|
||||
|
||||
var containerTransform = CATransform3DIdentity
|
||||
containerTransform = CATransform3DTranslate(containerTransform, 0.0, scaledTranslation, 0.0)
|
||||
containerTransform = CATransform3DScale(containerTransform, scale, scale, scale)
|
||||
transition.setTransform(view: self.containerView, transform: containerTransform)
|
||||
transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: scaledCornerRadius)
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
|
||||
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
|
||||
func update(component: GiftAuctionActiveBidsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let themeUpdated = self.environment?.theme !== environment.theme
|
||||
|
||||
let resetScrolling = self.scrollView.bounds.width != availableSize.width
|
||||
|
||||
let fillingSize: CGFloat
|
||||
if case .regular = environment.metrics.widthClass {
|
||||
fillingSize = min(availableSize.width, 414.0) - environment.safeInsets.left * 2.0
|
||||
} else {
|
||||
fillingSize = min(availableSize.width, 428.0) - environment.safeInsets.left * 2.0
|
||||
}
|
||||
let sideInset: CGFloat = floor((availableSize.width - fillingSize) * 0.5) + 16.0
|
||||
|
||||
if self.component == nil, let giftAuctionsManager = component.context.giftAuctionsManager {
|
||||
self.auctionStatesDisposable = (giftAuctionsManager.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] auctionStates in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.auctionStates = auctionStates
|
||||
self.state?.updated(transition: .immediate)
|
||||
})
|
||||
|
||||
self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
|
||||
self?.state?.updated()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.giftAuctionTimer?.start()
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
self.environment = environment
|
||||
|
||||
let theme = environment.theme.withModalBlocksBackground()
|
||||
|
||||
if themeUpdated {
|
||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
self.backgroundLayer.backgroundColor = theme.list.blocksBackgroundColor.cgColor
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
|
||||
var contentHeight: CGFloat = 75.0
|
||||
for auctionState in self.auctionStates {
|
||||
let id = auctionState.gift.giftId
|
||||
let itemView: ComponentView<Empty>
|
||||
if let current = self.itemsViews[id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemView = ComponentView()
|
||||
self.itemsViews[id] = itemView
|
||||
}
|
||||
|
||||
let itemSize = itemView.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
ActiveAuctionComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: environment.strings,
|
||||
dateTimeFormat: environment.dateTimeFormat,
|
||||
state: auctionState,
|
||||
currentTime: currentTime,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if let giftAuctionsManager = component.context.giftAuctionsManager {
|
||||
let _ = (giftAuctionsManager.auctionContext(for: .giftId(id))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] auction in
|
||||
guard let self, let component = self.component, let auction, let controller = environment.controller(), let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
|
||||
let bidController = component.context.sharedContext.makeGiftAuctionBidScreen(context: component.context, auctionContext: auction)
|
||||
navigationController.pushViewController(bidController)
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: itemSize)
|
||||
if let view = itemView.view {
|
||||
if view.superview == nil {
|
||||
self.scrollContentView.addSubview(view)
|
||||
}
|
||||
view.frame = itemFrame
|
||||
}
|
||||
contentHeight += itemSize.height
|
||||
contentHeight += 20.0
|
||||
}
|
||||
contentHeight -= 10.0
|
||||
|
||||
if self.backgroundHandleView.image == nil {
|
||||
self.backgroundHandleView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.backgroundHandleView.tintColor = environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(environment.theme.overallDarkAppearance ? 0.2 : 0.07)
|
||||
let backgroundHandleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - 36.0) * 0.5), y: 5.0), size: CGSize(width: 36.0, height: 5.0))
|
||||
if self.backgroundHandleView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(self.backgroundHandleView)
|
||||
}
|
||||
transition.setFrame(view: self.backgroundHandleView, frame: backgroundHandleFrame)
|
||||
|
||||
let closeButtonSize = self.closeButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor
|
||||
)
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.dismiss()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
let closeButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: closeButtonSize)
|
||||
if let closeButtonView = self.closeButton.view {
|
||||
if closeButtonView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(closeButtonView)
|
||||
}
|
||||
transition.setFrame(view: closeButtonView, frame: closeButtonFrame)
|
||||
}
|
||||
|
||||
let containerInset: CGFloat = environment.statusBarHeight + 10.0
|
||||
contentHeight += environment.safeInsets.bottom
|
||||
|
||||
var initialContentHeight = contentHeight
|
||||
let clippingY: CGFloat
|
||||
|
||||
let titleText: String = environment.strings.Gift_ActiveAuctions_Title(Int32(self.auctionStates.count))
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: 26.0), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(titleView)
|
||||
}
|
||||
transition.setFrame(view: titleView, frame: titleFrame)
|
||||
}
|
||||
|
||||
initialContentHeight = contentHeight
|
||||
|
||||
let edgeEffectHeight: CGFloat = 80.0
|
||||
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame)
|
||||
self.topEdgeEffectView.update(content: environment.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
|
||||
if self.topEdgeEffectView.superview == nil {
|
||||
self.navigationBarContainer.insertSubview(self.topEdgeEffectView, at: 0)
|
||||
}
|
||||
|
||||
clippingY = availableSize.height
|
||||
|
||||
let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight)
|
||||
|
||||
let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset)
|
||||
|
||||
self.scrollContentClippingView.layer.cornerRadius = 38.0
|
||||
|
||||
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, containerCornerRadius: environment.deviceMetrics.screenCornerRadius, bottomInset: environment.safeInsets.bottom, topInset: topInset)
|
||||
|
||||
transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight)))
|
||||
|
||||
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: fillingSize, height: availableSize.height)))
|
||||
|
||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: containerInset), size: CGSize(width: availableSize.width, height: clippingY - containerInset))
|
||||
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
|
||||
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
|
||||
|
||||
self.ignoreScrolling = true
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
||||
let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight)
|
||||
if contentSize != self.scrollView.contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
if resetScrolling {
|
||||
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize)
|
||||
}
|
||||
self.ignoreScrolling = false
|
||||
self.updateScrolling(transition: transition)
|
||||
|
||||
transition.setPosition(view: self.containerView, position: CGRect(origin: CGPoint(), size: availableSize).center)
|
||||
transition.setBounds(view: self.containerView, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
if let controller = environment.controller(), !controller.automaticallyControlPresentationContextLayout {
|
||||
let bottomInset: CGFloat = contentHeight - 12.0
|
||||
|
||||
let layout = ContainerViewLayout(
|
||||
size: 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: 0.0, bottom: 0.0, right: 0.0),
|
||||
additionalInsets: .zero,
|
||||
statusBarHeight: environment.statusBarHeight,
|
||||
inputHeight: nil,
|
||||
inputHeightIsInteractivellyChanging: false,
|
||||
inVoiceOver: false
|
||||
)
|
||||
controller.presentationContext.containerLayoutUpdated(layout, transition: transition.containedViewLayoutTransition)
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public class GiftAuctionActiveBidsScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
|
||||
private var didPlayAppearAnimation: Bool = false
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: GiftAuctionActiveBidsScreenComponent(
|
||||
context: context
|
||||
), navigationBarAppearance: .none, theme: .default)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
self.navigationPresentation = .flatModal
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
|
||||
if !self.didPlayAppearAnimation {
|
||||
self.didPlayAppearAnimation = true
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? GiftAuctionActiveBidsScreenComponent.View {
|
||||
componentView.animateIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? GiftAuctionActiveBidsScreenComponent.View {
|
||||
componentView.animateOut(completion: { [weak self] in
|
||||
completion?()
|
||||
self?.dismiss(animated: false)
|
||||
})
|
||||
} else {
|
||||
self.dismiss(animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ActiveAuctionComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let state: GiftAuctionContext.State
|
||||
let currentTime: Int32
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
dateTimeFormat: PresentationDateTimeFormat,
|
||||
state: GiftAuctionContext.State,
|
||||
currentTime: Int32,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.state = state
|
||||
self.currentTime = currentTime
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: ActiveAuctionComponent, rhs: ActiveAuctionComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.state != rhs.state {
|
||||
return false
|
||||
}
|
||||
if lhs.currentTime != rhs.currentTime {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let icon = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
private let button = ComponentView<Empty>()
|
||||
|
||||
private var component: ActiveAuctionComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.cornerRadius = 26.0
|
||||
self.clipsToBounds = true
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: ActiveAuctionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
self.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
||||
|
||||
var size = CGSize(width: availableSize.width, height: 0.0)
|
||||
size.height += 11.0
|
||||
|
||||
|
||||
if case let .generic(gift) = component.state.gift {
|
||||
let titleSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
subject: .starGift(gift: gift, price: ""),
|
||||
mode: .preview
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 64.0, height: 64.0)
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: 2.0, y: 0.0), size: titleSize)
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
self.addSubview(iconView)
|
||||
}
|
||||
iconView.frame = iconFrame
|
||||
}
|
||||
}
|
||||
|
||||
var endTime = component.currentTime
|
||||
|
||||
var titleText: String = ""
|
||||
var subtitleText: String = ""
|
||||
var subtitleTextColor = component.theme.list.itemPrimaryTextColor
|
||||
if case let .ongoing(_, _, _, _, _, _, nextRoundDate, _, currentRound, totalRound) = component.state.auctionState, let myBid = component.state.myState.bidAmount {
|
||||
titleText = component.strings.Gift_ActiveAuctions_Round("\(currentRound)", "\(totalRound)").string
|
||||
|
||||
let bidString = "#\(presentationStringsFormattedNumber(Int32(clamping: myBid), component.dateTimeFormat.groupingSeparator))"
|
||||
if let place = component.state.place, case let .generic(gift) = component.state.gift, let auctionGiftsPerRound = gift.auctionGiftsPerRound, place > auctionGiftsPerRound {
|
||||
subtitleText = component.strings.Gift_ActiveAuctions_Outbid(bidString).string
|
||||
subtitleTextColor = component.theme.list.itemDestructiveColor
|
||||
} else {
|
||||
subtitleText = component.strings.Gift_ActiveAuctions_Winning(bidString, "\(component.state.place ?? 0)").string
|
||||
}
|
||||
|
||||
endTime = nextRoundDate
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)),
|
||||
maximumNumberOfLines: 2
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 63.0 - 20.0, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: 63.0, y: size.height), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
size.height += titleSize.height
|
||||
size.height += 2.0
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let textColor = subtitleTextColor
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
let attributedString = parseMarkdownIntoAttributedString(subtitleText, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = attributedString.string.range(of: "#") {
|
||||
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
|
||||
let subtitleSize = self.subtitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextWithEntitiesComponent(context: component.context, animationCache: component.context.animationCache, animationRenderer: component.context.animationRenderer, placeholderColor: .clear, text: .plain(attributedString), maximumNumberOfLines: 2
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 63.0 - 20.0, height: 100.0)
|
||||
)
|
||||
let subtitleFrame = CGRect(origin: CGPoint(x: 63.0, y: size.height), size: subtitleSize)
|
||||
if let subtitleView = self.subtitle.view {
|
||||
if subtitleView.superview == nil {
|
||||
self.addSubview(subtitleView)
|
||||
}
|
||||
subtitleView.frame = subtitleFrame
|
||||
}
|
||||
size.height += subtitleSize.height
|
||||
size.height += 19.0
|
||||
|
||||
let endTimeout = max(0, endTime - component.currentTime)
|
||||
let hours = Int(endTimeout / 3600)
|
||||
let minutes = Int((endTimeout % 3600) / 60)
|
||||
let seconds = Int(endTimeout % 60)
|
||||
|
||||
var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = []
|
||||
if hours > 0 {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "h", content: .number(hours, minDigits: 1)))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "colon1", content: .text(":")))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "m", content: .number(minutes, minDigits: 2)))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "colon2", content: .text(":")))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
|
||||
} else {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "m", content: .number(minutes, minDigits: 2)))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "colon2", content: .text(":")))
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
|
||||
}
|
||||
|
||||
let buttonSize = self.button.update(
|
||||
transition: .spring(duration: 0.2),
|
||||
component: AnyComponent(
|
||||
ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: component.theme.list.itemCheckColors.fillColor,
|
||||
foreground: component.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
|
||||
BundleIconComponent(name: "Premium/Auction/BidSmall", tintColor: component.theme.list.itemCheckColors.foregroundColor)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: component.strings.Gift_ActiveAuctions_RaiseBid, font: Font.semibold(17.0), textColor: component.theme.list.itemCheckColors.foregroundColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "timer", component: AnyComponent(
|
||||
AnimatedTextComponent(
|
||||
font: Font.with(size: 17.0, weight: .medium, traits: .monospacedNumbers),
|
||||
color: component.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7),
|
||||
items: buttonAnimatedTitleItems,
|
||||
noDelay: true
|
||||
)
|
||||
))
|
||||
], spacing: 5.0)
|
||||
)),
|
||||
action: {
|
||||
component.action()
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 32.0, height: 52.0)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - buttonSize.width) / 2.0), y: size.height), size: buttonSize)
|
||||
if let buttonView = self.button.view {
|
||||
if buttonView.superview == nil {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
buttonView.frame = buttonFrame
|
||||
}
|
||||
size.height += buttonSize.height
|
||||
size.height += 16.0
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -17,7 +17,6 @@ import BundleIconComponent
|
|||
import Markdown
|
||||
import TextFormat
|
||||
import TelegramStringFormatting
|
||||
import GiftAnimationComponent
|
||||
import GlassBarButtonComponent
|
||||
import ButtonComponent
|
||||
import LottieComponent
|
||||
|
|
@ -89,17 +88,14 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent {
|
|||
|
||||
static var body: Body {
|
||||
let closeButton = Child(GlassBarButtonComponent.self)
|
||||
let animation = Child(GiftCompositionComponent.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
let title = Child(BalancedTextComponent.self)
|
||||
let text = Child(BalancedTextComponent.self)
|
||||
let list = Child(List<Empty>.self)
|
||||
let button = Child(ButtonComponent.self)
|
||||
|
||||
let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
|
||||
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let component = context.component
|
||||
let state = context.state
|
||||
let controller = environment.controller
|
||||
|
||||
|
|
@ -110,7 +106,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent {
|
|||
let sideInset: CGFloat = 30.0 + environment.safeInsets.left
|
||||
let textSideInset: CGFloat = 30.0 + environment.safeInsets.left
|
||||
|
||||
let titleFont = Font.semibold(20.0)
|
||||
let titleFont = Font.bold(24.0)
|
||||
let textFont = Font.regular(15.0)
|
||||
|
||||
let textColor = theme.actionSheet.primaryTextColor
|
||||
|
|
@ -118,46 +114,26 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent {
|
|||
let linkColor = theme.actionSheet.controlAccentColor
|
||||
|
||||
let spacing: CGFloat = 16.0
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 30.0)
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 33.0)
|
||||
|
||||
var animationFile: TelegramMediaFile?
|
||||
switch component.gift {
|
||||
case let .generic(gift):
|
||||
animationFile = gift.file
|
||||
default:
|
||||
animationFile = nil
|
||||
var auctionGiftsPerRound: Int32 = 50
|
||||
if case let .generic(gift) = context.component.gift, let auctionGiftsPerRoundValue = gift.auctionGiftsPerRound {
|
||||
auctionGiftsPerRound = auctionGiftsPerRoundValue
|
||||
}
|
||||
|
||||
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: 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))
|
||||
)
|
||||
}
|
||||
contentSize.height += headerHeight - 78.0
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: "Premium/Auction/BidLarge",
|
||||
tintColor: linkColor
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + icon.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += icon.size.height
|
||||
contentSize.height += 8.0
|
||||
|
||||
let title = title.update(
|
||||
component: BalancedTextComponent(
|
||||
|
|
@ -173,7 +149,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent {
|
|||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
contentSize.height += spacing - 14.0
|
||||
contentSize.height += spacing - 8.0
|
||||
|
||||
let text = text.update(
|
||||
component: BalancedTextComponent(
|
||||
|
|
@ -189,19 +165,17 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent {
|
|||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += text.size.height
|
||||
contentSize.height += spacing + 7.0
|
||||
|
||||
contentSize.height += spacing + 9.0
|
||||
|
||||
//TODO:localize
|
||||
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "top",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
title: "Top 50 Bidders",
|
||||
title: "Top \(auctionGiftsPerRound) Bidders",
|
||||
titleColor: textColor,
|
||||
text: "50 gifts are dropped in 10 rounds to the top 50 bidders by bid amount.",
|
||||
text: "\(auctionGiftsPerRound) gifts are dropped in 10 rounds to the top \(auctionGiftsPerRound) bidders by bid amount.",
|
||||
textColor: secondaryTextColor,
|
||||
accentColor: linkColor,
|
||||
iconName: "Premium/Auction/Drop",
|
||||
|
|
@ -215,7 +189,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent {
|
|||
component: AnyComponent(ParagraphComponent(
|
||||
title: "Bid Carryover",
|
||||
titleColor: textColor,
|
||||
text: "If your bid leaves the top 50, it will automatically join the next drop.",
|
||||
text: "If your bid leaves the top \(auctionGiftsPerRound), it will automatically join the next drop.",
|
||||
textColor: secondaryTextColor,
|
||||
accentColor: linkColor,
|
||||
iconName: "Premium/Auction/NextDrop",
|
||||
|
|
|
|||
|
|
@ -18,13 +18,16 @@ import Markdown
|
|||
import BalancedTextComponent
|
||||
import TextFormat
|
||||
import TelegramStringFormatting
|
||||
import StarsAvatarComponent
|
||||
import PlainButtonComponent
|
||||
import TooltipUI
|
||||
import GiftAnimationComponent
|
||||
import ContextUI
|
||||
import GiftItemComponent
|
||||
import GlassBarButtonComponent
|
||||
import ButtonComponent
|
||||
import UndoUI
|
||||
import LottieComponent
|
||||
import AnimatedTextComponent
|
||||
|
||||
private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -54,113 +57,66 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
let lastSalePriceTag = GenericComponentViewTag()
|
||||
let floorPriceTag = GenericComponentViewTag()
|
||||
let averagePriceTag = GenericComponentViewTag()
|
||||
|
||||
private let context: AccountContext
|
||||
private let auctionContext: GiftAuctionContext
|
||||
private let animateOut: ActionSlot<Action<()>>
|
||||
private let getController: () -> ViewController?
|
||||
|
||||
private var disposable: Disposable?
|
||||
var initialized = false
|
||||
private(set) var auctionState: GiftAuctionContext.State?
|
||||
|
||||
private var giftAuctionTimer: SwiftSignalKit.Timer?
|
||||
fileprivate var giftAuctionAcquiredGifts: [GiftAuctionAcquiredGift] = []
|
||||
private var giftAuctionAcquiredGiftsDisposable: Disposable?
|
||||
|
||||
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,
|
||||
auctionContext: GiftAuctionContext,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
getController: @escaping () -> ViewController?
|
||||
) {
|
||||
self.context = context
|
||||
self.auctionContext = auctionContext
|
||||
self.animateOut = animateOut
|
||||
self.getController = getController
|
||||
|
||||
super.init()
|
||||
|
||||
// if case let .starGift(gift, _) = component.subject, gift.flags.contains(.isAuction), let giftAuctionsManager = component.context.giftAuctionsManager, let giftAuction = giftAuctionsManager.auctionContext(for: .giftId(gift.id)) {
|
||||
// self.giftAuction = giftAuction
|
||||
// self.giftAuctionDisposable = (giftAuction.state
|
||||
// |> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
// guard let self else {
|
||||
// return
|
||||
// }
|
||||
// self.giftAuctionState = state
|
||||
// self.state?.updated()
|
||||
// })
|
||||
//
|
||||
// self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
|
||||
// self?.state?.updated()
|
||||
// }, queue: Queue.mainQueue())
|
||||
// self.giftAuctionTimer?.start()
|
||||
// }
|
||||
self.disposable = (auctionContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.auctionState = state
|
||||
self.updated()
|
||||
})
|
||||
|
||||
// if let _ = self.giftAuction {
|
||||
// //TODO:localize
|
||||
// let buttonAttributedString = NSMutableAttributedString(string: "Place a Bid", font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
// buttonTitleItems.append(AnyComponentWithIdentity(id: "bid", component: AnyComponent(
|
||||
// MultilineTextComponent(text: .plain(buttonAttributedString))
|
||||
// )))
|
||||
// if let giftAuctionState = self.giftAuctionState {
|
||||
// switch giftAuctionState.auctionState {
|
||||
// case let .ongoing(_, _, _, _, nextDropDate, _, _, _):
|
||||
// let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
// let dropTimeout = nextDropDate - currentTime
|
||||
//
|
||||
// let minutes = Int(dropTimeout / 60)
|
||||
// let seconds = Int(dropTimeout % 60)
|
||||
//
|
||||
// let rawString = environment.strings.Gift_Setup_NextDropIn
|
||||
// var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = []
|
||||
// var startIndex = rawString.startIndex
|
||||
// while true {
|
||||
// if let range = rawString.range(of: "{", range: startIndex ..< rawString.endIndex) {
|
||||
// if range.lowerBound != startIndex {
|
||||
// buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .text(String(rawString[startIndex ..< range.lowerBound]))))
|
||||
// }
|
||||
//
|
||||
// startIndex = range.upperBound
|
||||
// if let endRange = rawString.range(of: "}", range: startIndex ..< rawString.endIndex) {
|
||||
// let controlString = rawString[range.upperBound ..< endRange.lowerBound]
|
||||
// if controlString == "m" {
|
||||
// buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .number(minutes, minDigits: 2)))
|
||||
// } else if controlString == "s" {
|
||||
// buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .number(seconds, minDigits: 2)))
|
||||
// }
|
||||
//
|
||||
// startIndex = endRange.upperBound
|
||||
// }
|
||||
// } else {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if startIndex != rawString.endIndex {
|
||||
// buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: AnyHashable(buttonAnimatedTitleItems.count), content: .text(String(rawString[startIndex ..< rawString.endIndex]))))
|
||||
// }
|
||||
//
|
||||
// buttonTitleItems.append(AnyComponentWithIdentity(id: "timer", component: AnyComponent(AnimatedTextComponent(
|
||||
// font: Font.with(size: 12.0, weight: .medium, traits: .monospacedNumbers),
|
||||
// color: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7),
|
||||
// items: buttonAnimatedTitleItems,
|
||||
// noDelay: true
|
||||
// ))))
|
||||
// case .finished:
|
||||
// buttonIsEnabled = false
|
||||
// }
|
||||
// } else {
|
||||
// buttonIsLoading = true
|
||||
// }
|
||||
// } else {
|
||||
self.giftAuctionAcquiredGiftsDisposable = (context.engine.payments.getGiftAuctionAcquiredGifts(giftId: auctionContext.gift.giftId)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] acquiredGifts in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.giftAuctionAcquiredGifts = acquiredGifts
|
||||
self.updated(transition: .easeInOut(duration: 0.25))
|
||||
})
|
||||
|
||||
self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
|
||||
self?.updated()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.giftAuctionTimer?.start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
self.giftAuctionAcquiredGiftsDisposable?.dispose()
|
||||
self.giftAuctionTimer?.invalidate()
|
||||
}
|
||||
|
||||
func showAttributeInfo(tag: Any, text: String) {
|
||||
|
|
@ -200,6 +156,175 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
}
|
||||
|
||||
func openAuction() {
|
||||
guard let controller = self.getController() as? GiftAuctionViewScreen, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
self.dismiss(animated: true)
|
||||
|
||||
let bidController = self.context.sharedContext.makeGiftAuctionBidScreen(context: self.context, auctionContext: self.auctionContext)
|
||||
navigationController.pushViewController(bidController)
|
||||
}
|
||||
|
||||
func openPeer(_ peer: EnginePeer, dismiss: Bool = true) {
|
||||
guard let controller = self.getController() as? GiftAuctionViewScreen, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
controller.dismissAllTooltips()
|
||||
|
||||
let context = self.context
|
||||
let action = {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
chatController: nil,
|
||||
context: context,
|
||||
chatLocation: .peer(peer),
|
||||
subject: nil,
|
||||
botStart: nil,
|
||||
updateTextInputState: nil,
|
||||
keepStack: .always,
|
||||
useExisting: true,
|
||||
purposefulAction: nil,
|
||||
scrollToEndIfExists: false,
|
||||
activateMessageSearch: nil,
|
||||
animated: true
|
||||
))
|
||||
}
|
||||
|
||||
if dismiss {
|
||||
self.dismiss(animated: true)
|
||||
Queue.mainQueue().after(0.4, {
|
||||
action()
|
||||
})
|
||||
} else {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
func share() {
|
||||
guard let controller = self.getController() as? GiftAuctionViewScreen else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var link = ""
|
||||
if case let .generic(gift) = self.auctionContext.gift, let slug = gift.auctionSlug {
|
||||
link = "https://t.me/auction/\(slug)"
|
||||
}
|
||||
|
||||
let shareController = self.context.sharedContext.makeShareController(
|
||||
context: self.context,
|
||||
subject: .url(link),
|
||||
forceExternal: false,
|
||||
shareStory: nil,
|
||||
enqueued: { [weak self, weak controller] peerIds, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(
|
||||
EngineDataList(
|
||||
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
|
||||
)
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self, weak controller] peerList in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let peers = peerList.compactMap { $0 }
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let text: String
|
||||
var savedMessages = false
|
||||
if peerIds.count == 1, let peerId = peerIds.first, peerId == context.account.peerId {
|
||||
text = presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One
|
||||
savedMessages = true
|
||||
} else {
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
var peerName = peer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
peerName = peerName.replacingOccurrences(of: "**", with: "")
|
||||
text = presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string
|
||||
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
|
||||
var firstPeerName = firstPeer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
|
||||
var secondPeerName = secondPeer.id == context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
|
||||
text = presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string
|
||||
} else if let peer = peers.first {
|
||||
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
peerName = peerName.replacingOccurrences(of: "**", with: "")
|
||||
text = presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
|
||||
controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: false, action: { [weak self, weak controller] action in
|
||||
if let self, savedMessages, action == .info {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak controller] peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
self?.openPeer(peer)
|
||||
Queue.mainQueue().after(0.6) {
|
||||
controller?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
return false
|
||||
}, additionalView: nil), in: .current)
|
||||
})
|
||||
},
|
||||
actionCompleted: { [weak controller] in
|
||||
controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
)
|
||||
controller.present(shareController, in: .window(.root))
|
||||
}
|
||||
|
||||
func morePressed(view: UIView, gesture: ContextGesture?) {
|
||||
guard let controller = self.getController() as? GiftAuctionViewScreen else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let gift = self.auctionContext.gift
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var link = ""
|
||||
if case let .generic(gift) = gift, let slug = gift.auctionSlug {
|
||||
link = "https://t.me/auction/\(slug)"
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_Auction_Context_About, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, f in
|
||||
f(.default)
|
||||
|
||||
let infoController = context.sharedContext.makeGiftAuctionInfoScreen(context: context, gift: gift, completion: nil)
|
||||
controller?.push(infoController)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_Auction_Context_CopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, f in
|
||||
f(.default)
|
||||
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_Auction_Context_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
|
||||
f(.default)
|
||||
|
||||
self?.share()
|
||||
})))
|
||||
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: controller, sourceView: view)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
func dismiss(animated: Bool) {
|
||||
guard let controller = self.getController() as? GiftAuctionViewScreen else {
|
||||
return
|
||||
|
|
@ -216,24 +341,26 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context, animateOut: self.animateOut, getController: self.getController)
|
||||
return State(context: self.context, auctionContext: self.auctionContext, animateOut: self.animateOut, getController: self.getController)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let closeButton = Child(GlassBarButtonComponent.self)
|
||||
let animation = Child(GiftCompositionComponent.self)
|
||||
let moreButton = Child(GlassBarButtonComponent.self)
|
||||
let animation = Child(GiftItemComponent.self)
|
||||
|
||||
let titleBackground = Child(RoundedRectangle.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
|
||||
let description = Child(MultilineTextComponent.self)
|
||||
|
||||
let description = Child(BalancedTextComponent.self)
|
||||
|
||||
let table = Child(TableComponent.self)
|
||||
|
||||
let button = Child(ButtonComponent.self)
|
||||
|
||||
let acquiredButton = Child(PlainButtonComponent.self)
|
||||
// let telegramSaleButton = Child(PlainButtonComponent.self)
|
||||
// let fragmentSaleButton = Child(PlainButtonComponent.self)
|
||||
|
||||
let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
|
||||
let moreButtonPlayOnce = ActionSlot<Void>()
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
|
|
@ -247,14 +374,13 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
|
||||
let titleString: String = "Gift Name"
|
||||
var animationFile: TelegramMediaFile?
|
||||
var titleString: String = ""
|
||||
var giftIconSubject: GiftItemComponent.Subject?
|
||||
var genericGift: StarGift.Gift?
|
||||
|
||||
switch component.auctionContext.gift {
|
||||
case let .generic(gift):
|
||||
animationFile = gift.file
|
||||
titleString = gift.title ?? ""
|
||||
giftIconSubject = .starGift(gift: gift, price: "")
|
||||
genericGift = gift
|
||||
default:
|
||||
|
|
@ -265,43 +391,33 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
let _ = genericGift
|
||||
|
||||
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 {
|
||||
|
||||
if let genericGift {
|
||||
let animation = animation.update(
|
||||
component: GiftCompositionComponent(
|
||||
component: GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
subject: headerSubject,
|
||||
animationOffset: nil,
|
||||
animationScale: nil,
|
||||
displayAnimationStars: false,
|
||||
externalState: giftCompositionExternalState,
|
||||
requestUpdate: { [weak state] _ in
|
||||
state?.updated()
|
||||
}
|
||||
strings: environment.strings,
|
||||
subject: .starGift(gift: genericGift, price: ""),
|
||||
ribbon: GiftItemComponent.Ribbon(text: strings.Gift_Auction_Auction, color: .orange),
|
||||
outline: .orange,
|
||||
mode: .header
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: headerHeight),
|
||||
availableSize: CGSize(width: 120.0, height: 120.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(animation
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: headerHeight / 2.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 92.0))
|
||||
)
|
||||
}
|
||||
originY += headerHeight
|
||||
originY += 177.0
|
||||
|
||||
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,
|
||||
font: Font.bold(24.0),
|
||||
textColor: theme.list.itemPrimaryTextColor,
|
||||
paragraphAlignment: .center
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
|
|
@ -310,54 +426,197 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
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))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 174.0))
|
||||
)
|
||||
|
||||
let descriptionText: String = "description"
|
||||
var descriptionText: String = ""
|
||||
var descriptionColor = theme.list.itemSecondaryTextColor
|
||||
|
||||
let tableFont = Font.regular(15.0)
|
||||
let tableTextColor = theme.list.itemPrimaryTextColor
|
||||
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
var endTime = currentTime
|
||||
|
||||
var isEnded = false
|
||||
var tableItems: [TableComponent.Item] = []
|
||||
if let auctionState = state.auctionState, case let .generic(gift) = component.auctionContext.gift {
|
||||
endTime = auctionState.endDate
|
||||
if case .finished = auctionState.auctionState {
|
||||
isEnded = true
|
||||
} else if auctionState.endDate < currentTime {
|
||||
isEnded = true
|
||||
}
|
||||
|
||||
if isEnded {
|
||||
descriptionText = strings.Gift_Auction_Ended
|
||||
descriptionColor = theme.list.itemDestructiveColor
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "firstSale",
|
||||
title: strings.Gift_Auction_FirstSale,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: auctionState.startDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
tableItems.append(.init(
|
||||
id: "lastSale",
|
||||
title: strings.Gift_Auction_LastSale,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: auctionState.endDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
if case let .finished(_, _, averagePrice) = auctionState.auctionState {
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
|
||||
let valueString = "\(presentationStringsFormattedNumber(abs(Int32(clamping: averagePrice)), dateTimeFormat.groupingSeparator))⭐️"
|
||||
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)
|
||||
}
|
||||
|
||||
let averagePriceString = strings.Gift_Auction_Stars(Int32(clamping: averagePrice))
|
||||
items.append(AnyComponentWithIdentity(id: "value", component: AnyComponent(
|
||||
MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: theme.list.mediaPlaceholderColor,
|
||||
text: .plain(valueAttributedString),
|
||||
maximumNumberOfLines: 0
|
||||
)
|
||||
)))
|
||||
items.append(AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: component.context,
|
||||
text: "?",
|
||||
color: theme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
state.showAttributeInfo(tag: state.averagePriceTag, text: strings.Gift_Auction_AveragePriceInfo(averagePriceString, titleString).string)
|
||||
}
|
||||
).tagged(state.averagePriceTag))
|
||||
))
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "averagePrice",
|
||||
title: strings.Gift_Auction_AveragePrice,
|
||||
component: AnyComponent(HStack(items, spacing: 4.0)),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
|
||||
))
|
||||
}
|
||||
tableItems.append(.init(
|
||||
id: "availability",
|
||||
title: strings.Gift_Auction_Availability,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Auction_AvailabilityOf("0", presentationStringsFormattedNumber(gift.availability?.total ?? 0, dateTimeFormat.groupingSeparator)).string, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
} else {
|
||||
var auctionGiftsPerRound: Int32 = 50
|
||||
if let auctionGiftsPerRoundValue = gift.auctionGiftsPerRound {
|
||||
auctionGiftsPerRound = auctionGiftsPerRoundValue
|
||||
}
|
||||
descriptionText = strings.Gift_Auction_Description("\(auctionGiftsPerRound)", gift.title ?? "").string
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "start",
|
||||
title: strings.Gift_Auction_Started,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: auctionState.startDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
tableItems.append(.init(
|
||||
id: "ends",
|
||||
title: strings.Gift_Auction_Ends,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: auctionState.endDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
if case let .ongoing(_, _, _, _, _, _, _, giftsLeft, currentRound, totalRounds) = auctionState.auctionState {
|
||||
tableItems.append(.init(
|
||||
id: "round",
|
||||
title: strings.Gift_Auction_CurrentRound,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Auction_Round("\(currentRound)", "\(totalRounds)").string, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
tableItems.append(.init(
|
||||
id: "availability",
|
||||
title: strings.Gift_Auction_Availability,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Auction_AvailabilityOf(presentationStringsFormattedNumber(giftsLeft, dateTimeFormat.groupingSeparator), presentationStringsFormattedNumber(gift.availability?.total ?? 0, dateTimeFormat.groupingSeparator)).string, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let textColor = descriptionColor
|
||||
let linkColor = theme.list.itemAccentColor
|
||||
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)
|
||||
})
|
||||
|
||||
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
|
||||
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
|
||||
}
|
||||
if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
|
||||
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme)
|
||||
}
|
||||
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 chevronImage = state.cachedChevronImage?.0 {
|
||||
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
|
||||
let description = description.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: descriptionText))
|
||||
component: BalancedTextComponent(
|
||||
text: .plain(attributedString),
|
||||
maximumNumberOfLines: 0,
|
||||
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: { attributes, _ in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
let controller = component.context.sharedContext.makeGiftAuctionInfoScreen(
|
||||
context: component.context,
|
||||
gift: component.auctionContext.gift,
|
||||
completion: nil
|
||||
)
|
||||
environment.controller()?.push(controller)
|
||||
}
|
||||
}
|
||||
),
|
||||
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))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 198.0 + description.size.height / 2.0))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
originY += description.size.height
|
||||
originY += 42.0
|
||||
|
||||
let tableFont = Font.regular(15.0)
|
||||
let tableTextColor = theme.list.itemPrimaryTextColor
|
||||
|
||||
var tableItems: [TableComponent.Item] = []
|
||||
tableItems.append(.init(
|
||||
id: "firstSale",
|
||||
title: "First Sale",
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: 0, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
tableItems.append(.init(
|
||||
id: "lastSale",
|
||||
title: "Last Sale",
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: 0, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
|
||||
let table = table.update(
|
||||
component: TableComponent(
|
||||
theme: environment.theme,
|
||||
|
|
@ -371,7 +630,158 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
originY += table.size.height + 23.0
|
||||
originY += table.size.height + 26.0
|
||||
|
||||
var hasAdditionalButtons = false
|
||||
if state.giftAuctionAcquiredGifts.count > 0, case let .generic(gift) = component.auctionContext.gift {
|
||||
originY += 5.0
|
||||
|
||||
let text = strings.Gift_Auction_ItemsBought(Int32(state.giftAuctionAcquiredGifts.count))
|
||||
let acquiredButton = acquiredButton.update(
|
||||
component: PlainButtonComponent(content: AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: .starGift(gift: gift, price: ""),
|
||||
mode: .tableIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "text", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: text, font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "chevron", component: AnyComponent(
|
||||
BundleIconComponent(name: "Settings/TextArrowRight", tintColor: environment.theme.actionSheet.controlAccentColor)
|
||||
))
|
||||
], spacing: 6.0)
|
||||
), action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
let giftController = GiftAuctionAcquiredScreen(context: component.context, gift: component.auctionContext.gift, acquiredGifts: state.giftAuctionAcquiredGifts)
|
||||
environment.controller()?.push(giftController)
|
||||
}, animateScale: false),
|
||||
availableSize: CGSize(width: context.availableSize.width - 64.0, height: context.availableSize.height),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(acquiredButton
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + acquiredButton.size.height / 2.0)))
|
||||
originY += acquiredButton.size.height
|
||||
originY += 12.0
|
||||
|
||||
hasAdditionalButtons = true
|
||||
}
|
||||
|
||||
if hasAdditionalButtons {
|
||||
originY += 21.0
|
||||
}
|
||||
|
||||
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let buttonSize = CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0)
|
||||
let buttonBackground = ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
)
|
||||
|
||||
let buttonChild: _UpdatedChildComponent
|
||||
if !isEnded {
|
||||
let buttonAttributedString = NSMutableAttributedString(string: strings.Gift_Auction_Join, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
|
||||
let endTimeout = max(0, endTime - currentTime)
|
||||
|
||||
let hours = Int(endTimeout / 3600)
|
||||
let minutes = Int((endTimeout % 3600) / 60)
|
||||
let seconds = Int(endTimeout % 60)
|
||||
|
||||
let rawString = hours > 0 ? strings.Gift_Auction_TimeLeftHours : strings.Gift_Auction_TimeLeftMinutes
|
||||
var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = []
|
||||
var startIndex = rawString.startIndex
|
||||
while true {
|
||||
if let range = rawString.range(of: "{", range: startIndex ..< rawString.endIndex) {
|
||||
if range.lowerBound != startIndex {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "prefix", content: .text(String(rawString[startIndex ..< range.lowerBound]))))
|
||||
}
|
||||
|
||||
startIndex = range.upperBound
|
||||
if let endRange = rawString.range(of: "}", range: startIndex ..< rawString.endIndex) {
|
||||
let controlString = rawString[range.upperBound ..< endRange.lowerBound]
|
||||
if controlString == "h" {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "h", content: .number(hours, minDigits: 2)))
|
||||
} else if controlString == "m" {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "m", content: .number(minutes, minDigits: 2)))
|
||||
} else if controlString == "s" {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
|
||||
}
|
||||
|
||||
startIndex = endRange.upperBound
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if startIndex != rawString.endIndex {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "suffix", content: .text(String(rawString[startIndex ..< rawString.endIndex]))))
|
||||
}
|
||||
|
||||
let items: [AnyComponentWithIdentity<Empty>] = [
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(AnimatedTextComponent(
|
||||
font: Font.with(size: 12.0, weight: .medium, traits: .monospacedNumbers),
|
||||
color: theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7),
|
||||
items: buttonAnimatedTitleItems,
|
||||
noDelay: true
|
||||
)))
|
||||
]
|
||||
|
||||
buttonChild = button.update(
|
||||
component: ButtonComponent(
|
||||
background: buttonBackground,
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable("buy"),
|
||||
component: AnyComponent(VStack(items, spacing: 1.0))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
state.openAuction()
|
||||
}),
|
||||
availableSize: buttonSize,
|
||||
transition: .spring(duration: 0.2)
|
||||
)
|
||||
} else {
|
||||
buttonChild = button.update(
|
||||
component: ButtonComponent(
|
||||
background: buttonBackground,
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable("ok"),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Common_OK, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
state.dismiss(animated: true)
|
||||
}),
|
||||
availableSize: buttonSize,
|
||||
transition: context.transition
|
||||
)
|
||||
}
|
||||
|
||||
context.add(buttonChild
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + buttonChild.size.height / 2.0))
|
||||
)
|
||||
originY += buttonChild.size.height
|
||||
originY += buttonInsets.bottom
|
||||
|
||||
// if component.valueInfo.listedCount != nil || component.valueInfo.fragmentListedCount != nil {
|
||||
// originY += 5.0
|
||||
|
|
@ -500,8 +910,38 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
|
||||
)
|
||||
|
||||
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
|
||||
return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset)
|
||||
let moreButton = moreButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
component: AnyComponentWithIdentity(id: "more", component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(
|
||||
name: "anim_morewide"
|
||||
),
|
||||
color: theme.rootController.navigationBar.glassBarButtonForegroundColor,
|
||||
size: CGSize(width: 34.0, height: 34.0),
|
||||
playOnce: moreButtonPlayOnce
|
||||
)
|
||||
)),
|
||||
action: { [weak state] view in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
state.morePressed(view: view, gesture: nil)
|
||||
moreButtonPlayOnce.invoke(Void())
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(moreButton
|
||||
.position(CGPoint(x: context.availableSize.width - 16.0 - moreButton.size.width / 2.0, y: 16.0 + moreButton.size.height / 2.0))
|
||||
)
|
||||
|
||||
return CGSize(width: context.availableSize.width, height: originY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -680,3 +1120,17 @@ public final class GiftAuctionViewScreen: ViewControllerComponentContainer {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
private final class GiftViewContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceView: UIView
|
||||
|
||||
init(controller: ViewController, sourceView: UIView) {
|
||||
self.controller = controller
|
||||
self.sourceView = sourceView
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5108,7 +5108,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
case let .message(message):
|
||||
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction {
|
||||
switch action.action {
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash, giftMessageId, upgradeSeparate):
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash, giftMessageId, upgradeSeparate, _, _):
|
||||
var reference: StarGiftReference
|
||||
if let peerId, let giftMessageId {
|
||||
reference = .message(messageId: EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Cloud, id: giftMessageId))
|
||||
|
|
|
|||
|
|
@ -2801,9 +2801,8 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
var subtitle: String?
|
||||
switch component.stateContext.subject {
|
||||
case .peers:
|
||||
//TODO:localize
|
||||
if component.stateContext.liveStream {
|
||||
title = "Start Live As"
|
||||
title = environment.strings.Story_Privacy_StartLiveAs
|
||||
} else {
|
||||
title = environment.strings.Story_Privacy_PostStoryAs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,3 +108,49 @@ public final class UniqueGiftPreviewMedia: Media {
|
|||
return self.isEqual(to: other)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final class GiftAuctionPreviewMedia: Media {
|
||||
public var id: MediaId? {
|
||||
return nil
|
||||
}
|
||||
public let peerIds: [PeerId] = []
|
||||
|
||||
public let content: StarGift.Gift?
|
||||
public let centerColor: UIColor
|
||||
public let edgeColor: UIColor
|
||||
public let endTime: Int32
|
||||
|
||||
public init(content: StarGift.Gift, centerColor: UIColor, edgeColor: UIColor, endTime: Int32) {
|
||||
self.content = content
|
||||
self.centerColor = centerColor
|
||||
self.edgeColor = edgeColor
|
||||
self.endTime = endTime
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.content = nil
|
||||
self.centerColor = .clear
|
||||
self.edgeColor = .clear
|
||||
self.endTime = 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
}
|
||||
|
||||
public func isEqual(to other: Media) -> Bool {
|
||||
guard let other = other as? GiftAuctionPreviewMedia else {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.content != other.content {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public func isSemanticallyEqual(to other: Media) -> Bool {
|
||||
return self.isEqual(to: other)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidLarge.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidLarge.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bid_120.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidLarge.imageset/bid_120.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidLarge.imageset/bid_120.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidMedium.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidMedium.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bid_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidMedium.imageset/bid_30.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidMedium.imageset/bid_30.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidSmall.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidSmall.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bid_20.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidSmall.imageset/bid_20.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Auction/BidSmall.imageset/bid_20.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -1497,6 +1497,24 @@ func openResolvedUrlImpl(
|
|||
} else {
|
||||
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
case let .auction(auctionContext):
|
||||
if let auctionContext {
|
||||
if let _ = auctionContext.currentBidPeerId {
|
||||
let controller = context.sharedContext.makeGiftAuctionBidScreen(
|
||||
context: context,
|
||||
auctionContext: auctionContext
|
||||
)
|
||||
navigationController?.pushViewController(controller)
|
||||
} else {
|
||||
let controller = context.sharedContext.makeGiftAuctionViewScreen(
|
||||
context: context,
|
||||
auctionContext: auctionContext
|
||||
)
|
||||
navigationController?.pushViewController(controller)
|
||||
}
|
||||
} else {
|
||||
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
case let .messageLink(link):
|
||||
if let link {
|
||||
if let navigationController = navigationController {
|
||||
|
|
|
|||
|
|
@ -671,6 +671,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||
convertedUrl = "https://t.me/nft/\(slug)"
|
||||
}
|
||||
}
|
||||
} else if parsedUrl.host == "stargift_auction" {
|
||||
if let components = URLComponents(string: "/?" + query) {
|
||||
var slug: String?
|
||||
if let queryItems = components.queryItems {
|
||||
for queryItem in queryItems {
|
||||
if let value = queryItem.value {
|
||||
if queryItem.name == "slug" {
|
||||
slug = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let slug {
|
||||
convertedUrl = "https://t.me/auction/\(slug)"
|
||||
}
|
||||
}
|
||||
} else if parsedUrl.host == "privatepost" {
|
||||
if let components = URLComponents(string: "/?" + query) {
|
||||
var channelId: Int64?
|
||||
|
|
|
|||
|
|
@ -3854,6 +3854,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return GiftAuctionViewScreen(context: context, auctionContext: auctionContext)
|
||||
}
|
||||
|
||||
public func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController {
|
||||
return GiftAuctionActiveBidsScreen(context: context)
|
||||
}
|
||||
|
||||
public func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController {
|
||||
let editorSubject: Signal<MediaEditorScreenImpl.Subject?, NoError>
|
||||
switch subject {
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ public enum ParsedInternalUrl {
|
|||
case premiumGiftCode(slug: String)
|
||||
case messageLink(slug: String)
|
||||
case collectible(slug: String)
|
||||
case auction(slug: String)
|
||||
case externalUrl(url: String)
|
||||
}
|
||||
|
||||
|
|
@ -568,6 +569,8 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou
|
|||
return .theme(pathComponents[1])
|
||||
} else if pathComponents[0] == "nft" {
|
||||
return .collectible(slug: pathComponents[1])
|
||||
} else if pathComponents[0] == "auction" {
|
||||
return .auction(slug: pathComponents[1])
|
||||
} else if pathComponents[0] == "addlist" || pathComponents[0] == "folder" || pathComponents[0] == "list" {
|
||||
return .chatFolder(slug: pathComponents[1])
|
||||
} else if pathComponents[0] == "boost", pathComponents.count == 2 {
|
||||
|
|
@ -1185,6 +1188,15 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||
|> map { gift -> ResolveInternalUrlResult in
|
||||
return .result(.collectible(gift: gift))
|
||||
})
|
||||
case let .auction(slug):
|
||||
if let giftAuctionsManager = context.giftAuctionsManager {
|
||||
return .single(.progress) |> then(giftAuctionsManager.auctionContext(for: .slug(slug))
|
||||
|> map { auction -> ResolveInternalUrlResult in
|
||||
return .result(.auction(auction: auction))
|
||||
})
|
||||
} else {
|
||||
return .single(.result(nil))
|
||||
}
|
||||
case let .messageLink(slug):
|
||||
return .single(.progress)
|
||||
|> then(context.engine.peers.resolveMessageLink(slug: slug)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue