mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-01 04:08:07 +00:00
Various improvements
This commit is contained in:
parent
74e1d22de4
commit
5e17b321f8
@ -15167,7 +15167,12 @@ Error: %8$@";
|
||||
"ScheduledMessages.Reminder.Delete" = "Delete Reminder";
|
||||
"ScheduledMessages.Reminder.DeleteMany" = "Delete Reminders";
|
||||
|
||||
"Gift.Setup.NextDropIn" = "next drop in {m}:{s}";
|
||||
"Gift.Setup.PlaceBid" = "Place a Bid";
|
||||
"Gift.Setup.AuctionInfo" = "%@ are dropped to the top %@ by bid amount. [Learn more >]()";
|
||||
"Gift.Setup.AuctionInfo.Gifts_1" = "%@ gift";
|
||||
"Gift.Setup.AuctionInfo.Gifts_any" = "%@ gifts";
|
||||
"Gift.Setup.AuctionInfo.Bidders_1" = "%@ bidder";
|
||||
"Gift.Setup.AuctionInfo.Bidders_any" = "%@ bidders";
|
||||
|
||||
"PrivacySettings.LoginEmailSetupInfo" = "Setup your email address for Telegram login codes.";
|
||||
|
||||
@ -15223,6 +15228,11 @@ Error: %8$@";
|
||||
"Gift.AuctionBid.Top" = "TOP %@";
|
||||
"Gift.AuctionBid.Custom" = "Custom";
|
||||
|
||||
"Gift.AuctionBid.CustomBid.Title" = "Place a Custom Bid";
|
||||
"Gift.AuctionBid.CustomBid.Text" = "If you fall below the top %@, your bid will roll over to the next drop.";
|
||||
"Gift.AuctionBid.CustomBid.Placeholder" = "Amount";
|
||||
"Gift.AuctionBid.CustomBid.Done" = "Place a Bid";
|
||||
|
||||
"Gift.Auction.Context.About" = "About";
|
||||
"Gift.Auction.Context.CopyLink" = "Copy Link";
|
||||
"Gift.Auction.Context.Share" = "Share";
|
||||
@ -15269,11 +15279,6 @@ Error: %8$@";
|
||||
"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";
|
||||
|
||||
"Gift.Auction.Info.Title" = "Auction";
|
||||
"Gift.Auction.Info.Description" = "Join the battle for exclusive gifts.";
|
||||
|
||||
@ -15368,7 +15373,14 @@ Error: %8$@";
|
||||
"Stars.Transaction.LiveStreamPaidMessage_any" = "Fee for %@ Live Stream Messages";
|
||||
"Stars.Transaction.LiveStreamPaidMessage.Text" = "You receive **%@%** of the price that you charge for each incoming message.";
|
||||
|
||||
"Notification.StarGift.Subtitle.NoConvert" = "We'll notify you once it becomes eligible for unique upgrades.";
|
||||
"Notification.StarGift.Subtitle.OtherNoConvert" = "We'll notify %1$@ once it becomes eligible for unique upgrades.";
|
||||
"Gift.View.NoConvertDescription" = "We'll notify you once it becomes eligible for unique upgrades.";
|
||||
"Gift.View.OtherNoConvertDescription" = "We'll notify %1$@ once it becomes eligible for unique upgrades.";
|
||||
"Notification.StarGift.Subtitle.NoConvert" = "Display this gift on your page and turn it into a collectible.";
|
||||
"Notification.StarGift.Subtitle.OtherNoConvert" = "Display this gift on your page and turn it into a collectible.";
|
||||
"Gift.View.NoConvertDescription" = "We will notify you once it becomes eligible for unique upgrades.";
|
||||
"Gift.View.OtherNoConvertDescription" = "We will notify %1$@ once it becomes eligible for unique upgrades.";
|
||||
|
||||
"Gift.AuctionTransfer.Title" = "Change Recipient";
|
||||
"Gift.AuctionTransfer.Text" = "The current recipient of this gift is **%@**. Change to **%@**?";
|
||||
"Gift.AuctionTransfer.TextFromYourself" = "The current recipient of this gift is you. Change to **%@**?";
|
||||
"Gift.AuctionTransfer.TextToYourself" = "The current recipient of this gift is **%@**. Change to yourself?";
|
||||
"Gift.AuctionTransfer.Change" = "Change";
|
||||
|
||||
|
||||
@ -1423,8 +1423,8 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, openChatTheme: (() -> Void)?, dismissed: (() -> Void)?) -> ViewController
|
||||
func makeGiftWearPreviewScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController
|
||||
func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController
|
||||
func makeGiftAuctionBidScreen(context: AccountContext, toPeerId: EnginePeer.Id, auctionContext: GiftAuctionContext) -> ViewController
|
||||
func makeGiftAuctionViewScreen(context: AccountContext, toPeerId: EnginePeer.Id, auctionContext: GiftAuctionContext) -> ViewController
|
||||
func makeGiftAuctionBidScreen(context: AccountContext, toPeerId: EnginePeer.Id, text: String?, entities: [MessageTextEntity]?, hideName: Bool, auctionContext: GiftAuctionContext) -> ViewController
|
||||
func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping () -> Void) -> ViewController
|
||||
func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController
|
||||
|
||||
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController
|
||||
|
||||
@ -565,7 +565,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
guard let self, let auction else {
|
||||
return
|
||||
}
|
||||
let controller = self.context.sharedContext.makeGiftAuctionBidScreen(context: self.context, toPeerId: auction.currentBidPeerId ?? self.context.account.peerId, auctionContext: auction)
|
||||
let controller = self.context.sharedContext.makeGiftAuctionBidScreen(context: self.context, toPeerId: auction.currentBidPeerId ?? self.context.account.peerId, text: nil, entities: nil, hideName: false, auctionContext: auction)
|
||||
self.push(controller)
|
||||
})
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ public enum BotPaymentInvoiceSource {
|
||||
case starGiftResale(slug: String, toPeerId: EnginePeer.Id, ton: Bool)
|
||||
case starGiftPrepaidUpgrade(peerId: EnginePeer.Id, hash: String)
|
||||
case starGiftDropOriginalDetails(reference: StarGiftReference)
|
||||
case starGiftAuctionBid(update: Bool, hideName: Bool, peerId: EnginePeer.Id, giftId: Int64, bidAmount: Int64, text: String?, entities: [MessageTextEntity]?)
|
||||
case starGiftAuctionBid(update: Bool, hideName: Bool, peerId: EnginePeer.Id?, giftId: Int64, bidAmount: Int64, text: String?, entities: [MessageTextEntity]?)
|
||||
}
|
||||
|
||||
public struct BotPaymentInvoiceFields: OptionSet {
|
||||
@ -426,22 +426,27 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv
|
||||
return reference.apiStarGiftReference(transaction: transaction).flatMap { .inputInvoiceStarGiftDropOriginalDetails(stargift: $0) }
|
||||
|
||||
case let .starGiftAuctionBid(update, hideName, peerId, giftId, bidAmount, text, entities):
|
||||
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
|
||||
return nil
|
||||
}
|
||||
var flags: Int32 = 0
|
||||
if hideName {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
var inputPeer: Api.InputPeer?
|
||||
var message: Api.TextWithEntities?
|
||||
if update {
|
||||
flags |= (1 << 2)
|
||||
}
|
||||
flags |= (1 << 3)
|
||||
|
||||
var message: Api.TextWithEntities?
|
||||
if let text, !text.isEmpty {
|
||||
flags |= (1 << 1)
|
||||
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
|
||||
if let peerId {
|
||||
guard let peer = transaction.getPeer(peerId).flatMap(apiInputPeer) else {
|
||||
return nil
|
||||
}
|
||||
flags |= (1 << 3)
|
||||
inputPeer = peer
|
||||
|
||||
if hideName {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
|
||||
if let text, !text.isEmpty {
|
||||
flags |= (1 << 1)
|
||||
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
|
||||
}
|
||||
}
|
||||
return .inputInvoiceStarGiftAuctionBid(flags: flags, peer: inputPeer, giftId: giftId, bidAmount: bidAmount, message: message)
|
||||
}
|
||||
|
||||
@ -464,6 +464,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Stars/StarsIntroScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftOptionsScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftStoreScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftSetupScreen",
|
||||
"//submodules/TelegramUI/Components/ContentReportScreen",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
||||
|
||||
@ -1946,13 +1946,13 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
view.animateIn()
|
||||
}
|
||||
}))
|
||||
.disappear(ComponentTransition.Disappear({ view, transition, completion in
|
||||
if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate {
|
||||
view.animateOut(completion: completion)
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
}))
|
||||
.disappear(ComponentTransition.Disappear({ view, transition, completion in
|
||||
if let view = view as? CollageIconCarouselComponent.View, !transition.animation.isImmediate {
|
||||
view.animateOut(completion: completion)
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
}))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,7 +556,11 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, _, channelPeerId, senderPeerId, _, _, _, _, _, toPeerId):
|
||||
var incoming = incoming
|
||||
var convertStars = convertStars
|
||||
if case let .generic(gift) = gift {
|
||||
if gift.flags.contains(.isAuction) {
|
||||
convertStars = nil
|
||||
}
|
||||
if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName {
|
||||
creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string
|
||||
}
|
||||
@ -656,7 +660,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
entities.append(MessageTextEntity(range: starsRange.range.lowerBound ..< starsRange.range.upperBound, type: .Bold))
|
||||
}
|
||||
} else {
|
||||
text = item.presentationData.strings.Notification_StarGift_Subtitle_OtherNoConvert(peerName).string
|
||||
text = item.presentationData.strings.Notification_StarGift_Subtitle_OtherNoConvert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,7 +441,7 @@ public final class GiftItemComponent: Component {
|
||||
let _ = loadingBackground.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
ItemShimmeringLoadingComponent(color: component.theme.list.itemAccentColor, cornerRadius: 10.0)
|
||||
ItemShimmeringLoadingComponent(color: component.theme.list.itemAccentColor, cornerRadius: cornerRadius)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
|
||||
@ -0,0 +1,267 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import AvatarNode
|
||||
import Markdown
|
||||
|
||||
private final class GiftAuctionTransferAlertContentNode: AlertContentNode {
|
||||
private let strings: PresentationStrings
|
||||
private let title: String
|
||||
private let text: String
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let arrowNode: ASImageNode
|
||||
private let secondAvatarNode: AvatarNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, fromPeer: EnginePeer, toPeer: EnginePeer, title: String, text: String, actions: [TextAlertAction]) {
|
||||
self.strings = strings
|
||||
self.title = title
|
||||
self.text = text
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
|
||||
self.secondAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
self.addSubnode(self.secondAvatarNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
|
||||
self.avatarNode.setPeer(context: context, theme: ptheme, peer: fromPeer)
|
||||
self.secondAvatarNode.setPeer(context: context, theme: ptheme, peer: toPeer)
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
), textAlignment: .center)
|
||||
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor)
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||
self.avatarNode.updateSize(size: avatarSize)
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
|
||||
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||
|
||||
if let arrowImage = self.arrowNode.image {
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
||||
}
|
||||
|
||||
let secondAvatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
|
||||
transition.updateFrame(node: self.secondAvatarNode, frame: secondAvatarFrame)
|
||||
|
||||
origin.y += avatarSize.height + 10.0
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 4.0
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 10.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
||||
effectiveActionLayout = .vertical
|
||||
}
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
case .vertical:
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
let contentWidth = max(size.width, minActionsWidth)
|
||||
|
||||
var actionsHeight: CGFloat = 0.0
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionsHeight = actionButtonHeight
|
||||
case .vertical:
|
||||
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 16.0 + insets.top + insets.bottom)
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
|
||||
func giftAuctionTransferController(context: AccountContext, fromPeer: EnginePeer, toPeer: EnginePeer, commit: @escaping () -> Void) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
let text: String
|
||||
if fromPeer.id == context.account.peerId {
|
||||
text = strings.Gift_AuctionTransfer_TextFromYourself(toPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
} else if toPeer.id == context.account.peerId {
|
||||
text = strings.Gift_AuctionTransfer_TextToYourself(fromPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
} else {
|
||||
text = strings.Gift_AuctionTransfer_Text(fromPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), toPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var contentNode: GiftAuctionTransferAlertContentNode?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
}), TextAlertAction(type: .defaultAction, title: strings.Gift_AuctionTransfer_Change, action: {
|
||||
dismissImpl?(true)
|
||||
commit()
|
||||
})]
|
||||
|
||||
contentNode = GiftAuctionTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, fromPeer: fromPeer, toPeer: toPeer, title: strings.Gift_AuctionTransfer_Title, text: text, actions: actions)
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
return controller
|
||||
}
|
||||
@ -390,44 +390,47 @@ final class GiftOptionsScreenComponent: Component {
|
||||
let giftController = component.context.sharedContext.makeGiftAuctionBidScreen(
|
||||
context: component.context,
|
||||
toPeerId: currentBidPeerId,
|
||||
text: nil,
|
||||
entities: nil,
|
||||
hideName: false,
|
||||
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 {
|
||||
let _ = (context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: currentBidPeerId),
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak mainController] fromPeer, toPeer in
|
||||
guard let component = self?.component, let mainController, let fromPeer, let toPeer 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,
|
||||
toPeerId: currentBidPeerId,
|
||||
auctionContext: auctionContext
|
||||
)
|
||||
mainController.push(giftController)
|
||||
})
|
||||
],
|
||||
parseMarkdown: true
|
||||
)
|
||||
|
||||
let alertController = giftAuctionTransferController(context: context, fromPeer: fromPeer, toPeer: toPeer, commit: {
|
||||
let controller = GiftSetupScreen(
|
||||
context: context,
|
||||
peerId: component.peerId,
|
||||
subject: .starGift(gift, nil),
|
||||
completion: nil
|
||||
)
|
||||
mainController.push(controller)
|
||||
})
|
||||
mainController.present(alertController, in: .window(.root))
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let giftController = component.context.sharedContext.makeGiftAuctionViewScreen(
|
||||
context: component.context,
|
||||
toPeerId: component.peerId,
|
||||
auctionContext: auctionContext
|
||||
auctionContext: auctionContext,
|
||||
completion: { [weak mainController] in
|
||||
let controller = GiftSetupScreen(
|
||||
context: context,
|
||||
peerId: component.peerId,
|
||||
subject: .starGift(gift, nil),
|
||||
completion: nil
|
||||
)
|
||||
mainController?.push(controller)
|
||||
}
|
||||
)
|
||||
mainController.push(giftController)
|
||||
}
|
||||
|
||||
@ -146,6 +146,11 @@ private final class GiftSetupScreenComponent: Component {
|
||||
private var peerMap: [EnginePeer.Id: EnginePeer] = [:]
|
||||
private var sendPaidMessageStars: StarsAmount?
|
||||
|
||||
private var giftAuction: GiftAuctionContext?
|
||||
private var giftAuctionState: GiftAuctionContext.State?
|
||||
private var giftAuctionDisposable: Disposable?
|
||||
private var giftAuctionTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var cachedStarImage: (UIImage, PresentationTheme)?
|
||||
|
||||
private var updateDisposable: Disposable?
|
||||
@ -227,6 +232,8 @@ private final class GiftSetupScreenComponent: Component {
|
||||
self.inputMediaNodeDataDisposable?.dispose()
|
||||
self.updateDisposable?.dispose()
|
||||
self.optionsDisposable?.dispose()
|
||||
self.giftAuctionDisposable?.dispose()
|
||||
self.giftAuctionTimer?.invalidate()
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
@ -452,12 +459,11 @@ private final class GiftSetupScreenComponent: Component {
|
||||
}
|
||||
|
||||
private func proceedWithStarGift() {
|
||||
guard let component = self.component, let starsContext = component.context.starsContext, let starsState = starsContext.currentState else {
|
||||
guard let component = self.component, let environment = self.environment, let starsContext = component.context.starsContext, let starsState = starsContext.currentState else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = component.context
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let peerId = component.peerId
|
||||
|
||||
var textInputText = NSAttributedString()
|
||||
@ -466,6 +472,22 @@ private final class GiftSetupScreenComponent: Component {
|
||||
}
|
||||
let entities = generateChatInputTextEntities(textInputText)
|
||||
|
||||
if case let .starGift(gift, _) = component.subject, gift.flags.contains(.isAuction), let navigationController = environment.controller()?.navigationController as? NavigationController, let auctionContext = self.giftAuction {
|
||||
let controller = context.sharedContext.makeGiftAuctionBidScreen(
|
||||
context: context,
|
||||
toPeerId: peerId,
|
||||
text: textInputText.string,
|
||||
entities: entities,
|
||||
hideName: self.hideName,
|
||||
auctionContext: auctionContext
|
||||
)
|
||||
environment.controller()?.dismiss()
|
||||
navigationController.pushViewController(controller)
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var finalPrice: Int64
|
||||
var perUserLimit: Int32?
|
||||
var giftFile: TelegramMediaFile?
|
||||
@ -879,6 +901,29 @@ private final class GiftSetupScreenComponent: Component {
|
||||
if isSelfGift {
|
||||
self.hideName = true
|
||||
}
|
||||
|
||||
if case let .starGift(gift, _) = component.subject, gift.flags.contains(.isAuction), let giftAuctionsManager = component.context.giftAuctionsManager {
|
||||
let _ = (giftAuctionsManager.auctionContext(for: .giftId(gift.id))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] auctionContext in
|
||||
guard let self, let auctionContext else {
|
||||
return
|
||||
}
|
||||
self.giftAuction = auctionContext
|
||||
self.giftAuctionDisposable = (auctionContext.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()
|
||||
})
|
||||
}
|
||||
|
||||
var releasedBy: EnginePeer.Id?
|
||||
if case let .starGift(gift, true) = component.subject, gift.upgradeStars != nil {
|
||||
@ -1726,18 +1771,62 @@ private final class GiftSetupScreenComponent: Component {
|
||||
}
|
||||
contentHeight += remainingCountSize.height
|
||||
contentHeight += 7.0
|
||||
|
||||
if starGift.flags.contains(.isAuction), let giftsPerRound = starGift.auctionGiftsPerRound {
|
||||
let parsedString = parseMarkdownIntoAttributedString(environment.strings.Gift_Setup_AuctionInfo(environment.strings.Gift_Setup_AuctionInfo_Gifts(giftsPerRound), environment.strings.Gift_Setup_AuctionInfo_Bidders(giftsPerRound)).string, attributes: footerAttributes)
|
||||
let auctionFooterText = NSMutableAttributedString(attributedString: parsedString)
|
||||
|
||||
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme {
|
||||
self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme)
|
||||
}
|
||||
if let range = auctionFooterText.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
|
||||
auctionFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: auctionFooterText.string))
|
||||
}
|
||||
|
||||
let auctionFooterSize = self.auctionFooter.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(auctionFooterText),
|
||||
maximumNumberOfLines: 0,
|
||||
highlightColor: environment.theme.list.itemAccentColor.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: { [weak self] _, _ in
|
||||
guard let self, let component = self.component, let controller = self.environment?.controller(), let auctionContext = self.giftAuction else {
|
||||
return
|
||||
}
|
||||
let infoController = component.context.sharedContext.makeGiftAuctionInfoScreen(context: component.context, auctionContext: auctionContext, completion: nil)
|
||||
controller.push(infoController)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 16.0 * 2.0, height: 10000.0)
|
||||
)
|
||||
let auctionFooterFrame = CGRect(origin: CGPoint(x: sideInset + 16.0, y: contentHeight), size: auctionFooterSize)
|
||||
if let auctionFooterView = self.auctionFooter.view {
|
||||
if auctionFooterView.superview == nil {
|
||||
self.scrollContentView.addSubview(auctionFooterView)
|
||||
}
|
||||
transition.setFrame(view: auctionFooterView, frame: auctionFooterFrame)
|
||||
}
|
||||
contentHeight += auctionFooterSize.height
|
||||
}
|
||||
|
||||
contentHeight += sectionSpacing
|
||||
}
|
||||
|
||||
|
||||
initialContentHeight = contentHeight
|
||||
|
||||
if self.cachedStarImage == nil || self.cachedStarImage?.1 !== environment.theme {
|
||||
self.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: .white)!, environment.theme)
|
||||
}
|
||||
|
||||
|
||||
var buttonIsEnabled = true
|
||||
let buttonString: String
|
||||
switch component.subject {
|
||||
@ -1763,19 +1852,76 @@ private final class GiftSetupScreenComponent: Component {
|
||||
}
|
||||
|
||||
var buttonTitleItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
|
||||
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 {
|
||||
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.foregroundColor, value: environment.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string))
|
||||
if let _ = self.giftAuction {
|
||||
let buttonAttributedString = NSMutableAttributedString(string: environment.strings.Gift_Setup_PlaceBid, 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(_, _, endTime, _, _, _, _, _, _, _):
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
|
||||
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 ? environment.strings.Gift_Auction_TimeLeftHours : environment.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]))))
|
||||
}
|
||||
|
||||
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 {
|
||||
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 {
|
||||
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.foregroundColor, value: environment.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string))
|
||||
}
|
||||
|
||||
buttonTitleItems.append(AnyComponentWithIdentity(id: buttonString, component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(buttonAttributedString))
|
||||
)))
|
||||
}
|
||||
|
||||
buttonTitleItems.append(AnyComponentWithIdentity(id: buttonString, component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(buttonAttributedString))
|
||||
)))
|
||||
|
||||
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 32.0)
|
||||
let buttonHeight: CGFloat = 52.0
|
||||
let actionButtonSize = self.actionButton.update(
|
||||
|
||||
@ -49,6 +49,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
|
||||
"//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent",
|
||||
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
||||
"//submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
|
||||
"//submodules/ActivityIndicator",
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
|
||||
@ -332,7 +332,7 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
|
||||
}
|
||||
controller.dismiss()
|
||||
|
||||
let bidController = component.context.sharedContext.makeGiftAuctionBidScreen(context: component.context, toPeerId: auction.currentBidPeerId ?? component.context.account.peerId, auctionContext: auction)
|
||||
let bidController = component.context.sharedContext.makeGiftAuctionBidScreen(context: component.context, toPeerId: auction.currentBidPeerId ?? component.context.account.peerId, text: nil, entities: nil, hideName: false, auctionContext: auction)
|
||||
navigationController.pushViewController(bidController)
|
||||
})
|
||||
}
|
||||
|
||||
@ -940,17 +940,26 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
|
||||
let context: AccountContext
|
||||
let toPeerId: EnginePeer.Id
|
||||
let text: String?
|
||||
let entities: [MessageTextEntity]?
|
||||
let hideName: Bool
|
||||
let gift: StarGift
|
||||
let auctionContext: GiftAuctionContext
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
toPeerId: EnginePeer.Id,
|
||||
text: String?,
|
||||
entities: [MessageTextEntity]?,
|
||||
hideName: Bool,
|
||||
gift: StarGift,
|
||||
auctionContext: GiftAuctionContext
|
||||
) {
|
||||
self.context = context
|
||||
self.toPeerId = toPeerId
|
||||
self.text = text
|
||||
self.entities = entities
|
||||
self.hideName = hideName
|
||||
self.gift = gift
|
||||
self.auctionContext = auctionContext
|
||||
}
|
||||
@ -994,7 +1003,7 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
|
||||
private static func makeSliderSteps(minRealValue: Int, maxRealValue: Int, isLogarithmic: Bool) -> [Int] {
|
||||
if isLogarithmic {
|
||||
var sliderSteps: [Int] = [1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 15_000, 20_000, 30_000, 40_000, 50_000]
|
||||
var sliderSteps: [Int] = [1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 10_000, 20_000, 30_000, 40_000, 50_000]
|
||||
sliderSteps.removeAll(where: { $0 <= minRealValue })
|
||||
sliderSteps.insert(minRealValue, at: 0)
|
||||
sliderSteps.removeAll(where: { $0 >= maxRealValue })
|
||||
@ -1423,6 +1432,10 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
}
|
||||
|
||||
var isUpdate = false
|
||||
var myBidPeerId: EnginePeer.Id?
|
||||
if let peerId = self.giftAuctionState?.myState.bidPeerId {
|
||||
myBidPeerId = peerId
|
||||
}
|
||||
if let myBidAmount = self.giftAuctionState?.myState.bidAmount {
|
||||
isUpdate = true
|
||||
if value == myBidAmount {
|
||||
@ -1456,14 +1469,19 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
self.isLoading = true
|
||||
self.state?.updated()
|
||||
|
||||
var peerId: EnginePeer.Id?
|
||||
if !isUpdate || (myBidPeerId != nil && myBidPeerId != component.toPeerId) {
|
||||
peerId = component.toPeerId
|
||||
}
|
||||
|
||||
let source: BotPaymentInvoiceSource = .starGiftAuctionBid(
|
||||
update: isUpdate,
|
||||
hideName: false,
|
||||
peerId: component.toPeerId,
|
||||
hideName: peerId != nil ? component.hideName : false,
|
||||
peerId: peerId,
|
||||
giftId: gift.id,
|
||||
bidAmount: value,
|
||||
text: nil,
|
||||
entities: nil
|
||||
text: peerId != nil ? component.text : nil,
|
||||
entities: peerId != nil ? component.entities : nil
|
||||
)
|
||||
|
||||
let signal = BotCheckoutController.InputData.fetch(context: component.context, source: source)
|
||||
@ -1489,7 +1507,7 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
self.isLoading = false
|
||||
|
||||
let newMaxValue = Int(Double(value) * 1.5)
|
||||
var updatedAmount = self.amount.withMinAllowedRealValue(Int(value))
|
||||
var updatedAmount = self.amount.withMinAllowedRealValue(Int(value)).withRealValue(Int(value))
|
||||
if newMaxValue > self.amount.maxRealValue {
|
||||
updatedAmount = updatedAmount.withMaxRealValue(newMaxValue)
|
||||
}
|
||||
@ -1682,15 +1700,33 @@ private final class GiftAuctionBidScreenComponent: Component {
|
||||
}
|
||||
|
||||
func presentCustomBidController() {
|
||||
guard let component = self.component else {
|
||||
guard let component = self.component, let environment = self.environment, case let .generic(gift) = component.gift else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let auctionState = self.giftAuctionState else {
|
||||
return
|
||||
}
|
||||
|
||||
var minBidAmount: Int64 = 100
|
||||
if case let .ongoing(_, _, _, auctionMinBidAmount, _, _, _, _, _, _) = auctionState.auctionState {
|
||||
minBidAmount = auctionMinBidAmount
|
||||
if let myMinBidAmount = auctionState.myState.minBidAmount {
|
||||
minBidAmount = myMinBidAmount
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let giftsPerRounds = gift.auctionGiftsPerRound ?? 50
|
||||
|
||||
let controller = giftAuctionCustomBidController(
|
||||
context: component.context,
|
||||
title: "Place a Custom Bid",
|
||||
text: "Description",
|
||||
placeholder: "Bid",
|
||||
value: 100,
|
||||
title: environment.strings.Gift_AuctionBid_CustomBid_Title,
|
||||
text: environment.strings.Gift_AuctionBid_CustomBid_Text("\(giftsPerRounds)").string,
|
||||
placeholder: environment.strings.Gift_AuctionBid_CustomBid_Placeholder,
|
||||
action: environment.strings.Gift_AuctionBid_CustomBid_Done,
|
||||
minValue: minBidAmount,
|
||||
value: minBidAmount,
|
||||
apply: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
@ -2698,12 +2734,15 @@ public class GiftAuctionBidScreen: ViewControllerComponentContainer {
|
||||
private var didPlayAppearAnimation: Bool = false
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
public init(context: AccountContext, toPeerId: EnginePeer.Id, auctionContext: GiftAuctionContext) {
|
||||
public init(context: AccountContext, toPeerId: EnginePeer.Id, text: String?, entities: [MessageTextEntity]?, hideName: Bool, auctionContext: GiftAuctionContext) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: GiftAuctionBidScreenComponent(
|
||||
context: context,
|
||||
toPeerId: toPeerId,
|
||||
text: text,
|
||||
entities: entities,
|
||||
hideName: hideName,
|
||||
gift: auctionContext.gift,
|
||||
auctionContext: auctionContext
|
||||
), navigationBarAppearance: .none, theme: .default)
|
||||
|
||||
@ -7,168 +7,23 @@ import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import UrlEscaping
|
||||
import ComponentFlow
|
||||
import StarsWithdrawalScreen
|
||||
|
||||
private final class GiftAuctionCustomBidInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
|
||||
private var theme: PresentationTheme
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textInputNode: EditableTextNode
|
||||
private let placeholderNode: ASTextNode
|
||||
|
||||
var updateHeight: (() -> Void)?
|
||||
var complete: (() -> Void)?
|
||||
var textChanged: ((String) -> Void)?
|
||||
|
||||
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
|
||||
private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)
|
||||
|
||||
var text: String {
|
||||
get {
|
||||
return self.textInputNode.attributedText?.string ?? ""
|
||||
}
|
||||
set {
|
||||
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor)
|
||||
self.placeholderNode.isHidden = !newValue.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
var placeholder: String = "" {
|
||||
didSet {
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, placeholder: String, returnKeyType: UIReturnKeyType = .done) {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
|
||||
self.textInputNode = EditableTextNode()
|
||||
self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
|
||||
self.textInputNode.clipsToBounds = true
|
||||
self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||
self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0)
|
||||
self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.textInputNode.keyboardType = .default
|
||||
self.textInputNode.autocapitalizationType = .sentences
|
||||
self.textInputNode.returnKeyType = returnKeyType
|
||||
self.textInputNode.autocorrectionType = .default
|
||||
self.textInputNode.tintColor = theme.actionSheet.controlAccentColor
|
||||
self.textInputNode.keyboardType = .numberPad
|
||||
|
||||
self.placeholderNode = ASTextNode()
|
||||
self.placeholderNode.isUserInteractionEnabled = false
|
||||
self.placeholderNode.displaysAsynchronously = false
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
|
||||
super.init()
|
||||
|
||||
self.textInputNode.delegate = self
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.textInputNode)
|
||||
self.addSubnode(self.placeholderNode)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
|
||||
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
|
||||
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
|
||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||
|
||||
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
|
||||
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
|
||||
|
||||
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - 20.0, height: backgroundFrame.size.height)))
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
func activateInput() {
|
||||
self.textInputNode.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func deactivateInput() {
|
||||
self.textInputNode.resignFirstResponder()
|
||||
}
|
||||
|
||||
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
self.updateTextNodeText(animated: true)
|
||||
self.textChanged?(editableTextNode.textView.text)
|
||||
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
|
||||
}
|
||||
|
||||
func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
}
|
||||
|
||||
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
}
|
||||
|
||||
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
||||
if updatedText.count > 1 {
|
||||
self.textInputNode.layer.addShakeAnimation()
|
||||
return false
|
||||
}
|
||||
if text == "\n" {
|
||||
self.complete?()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
|
||||
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right - 20.0, height: CGFloat.greatestFiniteMagnitude)).height))
|
||||
|
||||
return min(61.0, max(33.0, unboundTextFieldHeight))
|
||||
}
|
||||
|
||||
private func updateTextNodeText(animated: Bool) {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
|
||||
let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width)
|
||||
|
||||
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||
if !self.bounds.size.height.isEqual(to: panelHeight) {
|
||||
self.updateHeight?()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func clearPressed() {
|
||||
self.placeholderNode.isHidden = false
|
||||
|
||||
self.textInputNode.attributedText = nil
|
||||
self.updateHeight?()
|
||||
}
|
||||
}
|
||||
|
||||
private final class giftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
private final class GiftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
private let theme: PresentationTheme
|
||||
private let strings: PresentationStrings
|
||||
private let dateTimeFormat: PresentationDateTimeFormat
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let placeholder: String
|
||||
private let minValue: Int64
|
||||
fileprivate var value: Int64
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
let inputFieldNode: GiftAuctionCustomBidInputFieldNode
|
||||
private let backgroundView = UIImageView()
|
||||
let amountField = ComponentView<Empty>()
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
@ -182,7 +37,7 @@ private final class giftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
|
||||
var complete: (() -> Void)? {
|
||||
didSet {
|
||||
self.inputFieldNode.complete = self.complete
|
||||
//self.inputFieldNode.complete = self.complete
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,18 +45,23 @@ private final class giftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: Int64) {
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, actions: [TextAlertAction], title: String, text: String, placeholder: String, minValue: Int64, value: Int64) {
|
||||
self.theme = ptheme
|
||||
self.strings = strings
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.placeholder = placeholder
|
||||
self.minValue = minValue
|
||||
self.value = value
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 8
|
||||
|
||||
self.inputFieldNode = GiftAuctionCustomBidInputFieldNode(theme: ptheme, placeholder: placeholder)
|
||||
self.inputFieldNode.text = "\(value)"
|
||||
|
||||
// self.inputFieldNode = GiftAuctionCustomBidInputFieldNode(theme: ptheme, placeholder: placeholder)
|
||||
// self.inputFieldNode.text = "\(value)"
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
@ -225,7 +85,7 @@ private final class giftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.addSubnode(self.inputFieldNode)
|
||||
// self.addSubnode(self.inputFieldNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
@ -237,14 +97,6 @@ private final class giftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.inputFieldNode.updateHeight = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let _ = strongSelf.validLayout {
|
||||
strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
@ -252,10 +104,6 @@ private final class giftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
var value: String {
|
||||
return self.inputFieldNode.text
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
@ -327,13 +175,52 @@ private final class giftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
|
||||
let resultWidth = contentWidth + insets.left + insets.right
|
||||
|
||||
let inputFieldWidth = resultWidth
|
||||
let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
|
||||
let inputHeight = inputFieldHeight
|
||||
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
|
||||
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
|
||||
let fieldWidth = resultWidth - 18.0
|
||||
let amountFieldSize = self.amountField.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
AmountFieldComponent(
|
||||
textColor: self.theme.list.itemPrimaryTextColor,
|
||||
secondaryColor: self.theme.list.itemSecondaryTextColor,
|
||||
placeholderColor: self.theme.list.itemPlaceholderTextColor,
|
||||
accentColor: self.theme.list.itemAccentColor,
|
||||
value: self.value,
|
||||
minValue: self.minValue,
|
||||
forceMinValue: false,
|
||||
allowZero: false,
|
||||
maxValue: nil,
|
||||
placeholderText: self.placeholder,
|
||||
textFieldOffset: CGPoint(x: -4.0, y: -1.0),
|
||||
labelText: nil,
|
||||
currency: .stars,
|
||||
dateTimeFormat: self.dateTimeFormat,
|
||||
amountUpdated: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let value {
|
||||
self.value = value
|
||||
}
|
||||
},
|
||||
tag: nil
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: fieldWidth, height: 44.0)
|
||||
)
|
||||
let amountFieldFrame = CGRect(origin: CGPoint(x: floor((resultWidth - fieldWidth) / 2.0), y: origin.y - 2.0), size: amountFieldSize)
|
||||
if let amountFieldView = self.amountField.view {
|
||||
if amountFieldView.superview == nil {
|
||||
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
|
||||
self.view.addSubview(self.backgroundView)
|
||||
self.view.addSubview(amountFieldView)
|
||||
}
|
||||
self.backgroundView.frame = amountFieldFrame.insetBy(dx: 7.0, dy: 9.0)
|
||||
amountFieldView.frame = amountFieldFrame
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom)
|
||||
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + amountFieldSize.height + actionsHeight + insets.top + insets.bottom + 3.0)
|
||||
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
@ -381,19 +268,36 @@ private final class giftAuctionCustomBidAlertContentNode: AlertContentNode {
|
||||
}
|
||||
|
||||
if !hadValidLayout {
|
||||
self.inputFieldNode.activateInput()
|
||||
if let amountFieldView = self.amountField.view as? AmountFieldComponent.View {
|
||||
amountFieldView.activateInput()
|
||||
amountFieldView.selectAll()
|
||||
}
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
self.inputFieldNode.layer.addShakeAnimation()
|
||||
self.hapticFeedback.error()
|
||||
if let amountFieldView = self.amountField.view as? AmountFieldComponent.View {
|
||||
self.value = self.minValue
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
amountFieldView.resetValue()
|
||||
|
||||
amountFieldView.animateError()
|
||||
amountFieldView.selectAll()
|
||||
}
|
||||
}
|
||||
|
||||
func deactivateInput() {
|
||||
if let amountFieldView = self.amountField.view as? AmountFieldComponent.View {
|
||||
amountFieldView.deactivateInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func giftAuctionCustomBidController(context: AccountContext, title: String, text: String, placeholder: String, value: Int64, apply: @escaping (Int64) -> Void) -> AlertController {
|
||||
func giftAuctionCustomBidController(context: AccountContext, title: String, text: String, placeholder: String, action: String, minValue: Int64, value: Int64, apply: @escaping (Int64) -> Void) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
@ -401,11 +305,11 @@ func giftAuctionCustomBidController(context: AccountContext, title: String, text
|
||||
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
}), TextAlertAction(type: .defaultAction, title: "Place a Bid", action: {
|
||||
}), TextAlertAction(type: .defaultAction, title: action, action: {
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
let contentNode = giftAuctionCustomBidAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value)
|
||||
let contentNode = GiftAuctionCustomBidAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, actions: actions, title: title, text: text, placeholder: placeholder, minValue: minValue, value: value)
|
||||
contentNode.complete = {
|
||||
applyImpl?()
|
||||
}
|
||||
@ -413,23 +317,25 @@ func giftAuctionCustomBidController(context: AccountContext, title: String, text
|
||||
guard let contentNode = contentNode else {
|
||||
return
|
||||
}
|
||||
dismissImpl?(true)
|
||||
|
||||
if let value = Int64(contentNode.value.trimmingCharacters(in: .whitespacesAndNewlines)) {
|
||||
apply(value)
|
||||
let value = contentNode.value
|
||||
if value < minValue {
|
||||
contentNode.animateError()
|
||||
} else {
|
||||
dismissImpl?(true)
|
||||
apply(contentNode.value)
|
||||
}
|
||||
}
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
||||
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
||||
contentNode?.inputFieldNode.updateTheme(presentationData.theme)
|
||||
})
|
||||
controller.dismissed = { _ in
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
dismissImpl = { [weak controller, weak contentNode] animated in
|
||||
contentNode?.inputFieldNode.deactivateInput()
|
||||
contentNode?.deactivateInput()
|
||||
let _ = contentNode
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
|
||||
@ -33,20 +33,17 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let toPeerId: EnginePeer.Id
|
||||
let auctionContext: GiftAuctionContext
|
||||
let animateOut: ActionSlot<Action<()>>
|
||||
let getController: () -> ViewController?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
toPeerId: EnginePeer.Id,
|
||||
auctionContext: GiftAuctionContext,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
getController: @escaping () -> ViewController?
|
||||
) {
|
||||
self.context = context
|
||||
self.toPeerId = toPeerId
|
||||
self.auctionContext = auctionContext
|
||||
self.animateOut = animateOut
|
||||
self.getController = getController
|
||||
@ -63,7 +60,6 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
let averagePriceTag = GenericComponentViewTag()
|
||||
|
||||
private let context: AccountContext
|
||||
private let toPeerId: EnginePeer.Id
|
||||
private let auctionContext: GiftAuctionContext
|
||||
private let animateOut: ActionSlot<Action<()>>
|
||||
private let getController: () -> ViewController?
|
||||
@ -82,13 +78,11 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
toPeerId: EnginePeer.Id,
|
||||
auctionContext: GiftAuctionContext,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
getController: @escaping () -> ViewController?
|
||||
) {
|
||||
self.context = context
|
||||
self.toPeerId = toPeerId
|
||||
self.auctionContext = auctionContext
|
||||
self.animateOut = animateOut
|
||||
self.getController = getController
|
||||
@ -162,15 +156,16 @@ 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 {
|
||||
func proceed() {
|
||||
guard let controller = self.getController() as? GiftAuctionViewScreen else {
|
||||
return
|
||||
}
|
||||
|
||||
self.dismiss(animated: true)
|
||||
|
||||
let bidController = self.context.sharedContext.makeGiftAuctionBidScreen(context: self.context, toPeerId: self.auctionContext.currentBidPeerId ?? self.toPeerId, auctionContext: self.auctionContext)
|
||||
navigationController.pushViewController(bidController)
|
||||
controller.completion()
|
||||
|
||||
// let bidController = self.context.sharedContext.makeGiftAuctionBidScreen(context: self.context, toPeerId: self.auctionContext.currentBidPeerId ?? self.toPeerId, auctionContext: self.auctionContext)
|
||||
// navigationController.pushViewController(bidController)
|
||||
}
|
||||
|
||||
func openPeer(_ peer: EnginePeer, dismiss: Bool = true) {
|
||||
@ -350,7 +345,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context, toPeerId: self.toPeerId, auctionContext: self.auctionContext, animateOut: self.animateOut, getController: self.getController)
|
||||
return State(context: self.context, auctionContext: self.auctionContext, animateOut: self.animateOut, getController: self.getController)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
@ -765,7 +760,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
state.openAuction()
|
||||
state.proceed()
|
||||
}),
|
||||
availableSize: buttonSize,
|
||||
transition: .spring(duration: 0.2)
|
||||
@ -964,16 +959,13 @@ final class GiftAuctionViewSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let toPeerId: EnginePeer.Id
|
||||
let auctionContext: GiftAuctionContext
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
toPeerId: EnginePeer.Id,
|
||||
auctionContext: GiftAuctionContext
|
||||
) {
|
||||
self.context = context
|
||||
self.toPeerId = toPeerId
|
||||
self.auctionContext = auctionContext
|
||||
}
|
||||
|
||||
@ -998,7 +990,6 @@ final class GiftAuctionViewSheetComponent: CombinedComponent {
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(GiftAuctionViewSheetContent(
|
||||
context: context.component.context,
|
||||
toPeerId: context.component.toPeerId,
|
||||
auctionContext: context.component.auctionContext,
|
||||
animateOut: animateOut,
|
||||
getController: controller
|
||||
@ -1079,16 +1070,19 @@ final class GiftAuctionViewSheetComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
public final class GiftAuctionViewScreen: ViewControllerComponentContainer {
|
||||
fileprivate let completion: () -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
toPeerId: EnginePeer.Id,
|
||||
auctionContext: GiftAuctionContext
|
||||
auctionContext: GiftAuctionContext,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
self.completion = completion
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: GiftAuctionViewSheetComponent(
|
||||
context: context,
|
||||
toPeerId: toPeerId,
|
||||
auctionContext: auctionContext
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
|
||||
@ -562,6 +562,7 @@ private final class SheetContent: CombinedComponent {
|
||||
accentColor: theme.list.itemAccentColor,
|
||||
value: state.amount?.value,
|
||||
minValue: minAmount?.value,
|
||||
forceMinValue: false,
|
||||
allowZero: allowZero,
|
||||
maxValue: maxAmount?.value,
|
||||
placeholderText: amountPlaceholder,
|
||||
@ -1296,6 +1297,7 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||
|
||||
private let textField: UITextField
|
||||
private let minValue: Int64
|
||||
private let forceMinValue: Bool
|
||||
private let allowZero: Bool
|
||||
private let maxValue: Int64
|
||||
private let updated: (Int64) -> Void
|
||||
@ -1303,11 +1305,12 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||
private let animateError: () -> Void
|
||||
private let focusUpdated: (Bool) -> Void
|
||||
|
||||
init?(textField: UITextField, currency: CurrencyAmount.Currency, dateTimeFormat: PresentationDateTimeFormat, minValue: Int64, allowZero: Bool, maxValue: Int64, updated: @escaping (Int64) -> Void, isEmptyUpdated: @escaping (Bool) -> Void, animateError: @escaping () -> Void, focusUpdated: @escaping (Bool) -> Void) {
|
||||
init?(textField: UITextField, currency: CurrencyAmount.Currency, dateTimeFormat: PresentationDateTimeFormat, minValue: Int64, forceMinValue: Bool, allowZero: Bool, maxValue: Int64, updated: @escaping (Int64) -> Void, isEmptyUpdated: @escaping (Bool) -> Void, animateError: @escaping () -> Void, focusUpdated: @escaping (Bool) -> Void) {
|
||||
self.textField = textField
|
||||
self.currency = currency
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.minValue = minValue
|
||||
self.forceMinValue = forceMinValue
|
||||
self.allowZero = allowZero
|
||||
self.maxValue = maxValue
|
||||
self.updated = updated
|
||||
@ -1434,7 +1437,17 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||
}
|
||||
|
||||
let amount: Int64 = self.amountFrom(text: newText)
|
||||
if amount > self.maxValue {
|
||||
if self.forceMinValue && amount < self.minValue {
|
||||
switch self.currency {
|
||||
case .stars:
|
||||
textField.text = "\(self.minValue)"
|
||||
case .ton:
|
||||
textField.text = "\(formatTonAmountText(self.minValue, dateTimeFormat: PresentationDateTimeFormat(timeFormat: self.dateTimeFormat.timeFormat, dateFormat: self.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: self.dateTimeFormat.decimalSeparator, groupingSeparator: ""), maxDecimalPositions: nil))"
|
||||
}
|
||||
self.onTextChanged(text: self.textField.text ?? "")
|
||||
self.animateError()
|
||||
return false
|
||||
} else if amount > self.maxValue {
|
||||
switch self.currency {
|
||||
case .stars:
|
||||
textField.text = "\(self.maxValue)"
|
||||
@ -1452,8 +1465,8 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private final class AmountFieldComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
public final class AmountFieldComponent: Component {
|
||||
public typealias EnvironmentType = Empty
|
||||
|
||||
let textColor: UIColor
|
||||
let secondaryColor: UIColor
|
||||
@ -1461,25 +1474,29 @@ private final class AmountFieldComponent: Component {
|
||||
let accentColor: UIColor
|
||||
let value: Int64?
|
||||
let minValue: Int64?
|
||||
let forceMinValue: Bool
|
||||
let allowZero: Bool
|
||||
let maxValue: Int64?
|
||||
let placeholderText: String
|
||||
let textFieldOffset: CGPoint
|
||||
let labelText: String?
|
||||
let currency: CurrencyAmount.Currency
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let amountUpdated: (Int64?) -> Void
|
||||
let tag: AnyObject?
|
||||
|
||||
init(
|
||||
public init(
|
||||
textColor: UIColor,
|
||||
secondaryColor: UIColor,
|
||||
placeholderColor: UIColor,
|
||||
accentColor: UIColor,
|
||||
value: Int64?,
|
||||
minValue: Int64?,
|
||||
forceMinValue: Bool,
|
||||
allowZero: Bool,
|
||||
maxValue: Int64?,
|
||||
placeholderText: String,
|
||||
textFieldOffset: CGPoint = .zero,
|
||||
labelText: String?,
|
||||
currency: CurrencyAmount.Currency,
|
||||
dateTimeFormat: PresentationDateTimeFormat,
|
||||
@ -1492,9 +1509,11 @@ private final class AmountFieldComponent: Component {
|
||||
self.accentColor = accentColor
|
||||
self.value = value
|
||||
self.minValue = minValue
|
||||
self.forceMinValue = forceMinValue
|
||||
self.allowZero = allowZero
|
||||
self.maxValue = maxValue
|
||||
self.placeholderText = placeholderText
|
||||
self.textFieldOffset = textFieldOffset
|
||||
self.labelText = labelText
|
||||
self.currency = currency
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
@ -1502,7 +1521,7 @@ private final class AmountFieldComponent: Component {
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
static func ==(lhs: AmountFieldComponent, rhs: AmountFieldComponent) -> Bool {
|
||||
public static func ==(lhs: AmountFieldComponent, rhs: AmountFieldComponent) -> Bool {
|
||||
if lhs.textColor != rhs.textColor {
|
||||
return false
|
||||
}
|
||||
@ -1539,7 +1558,7 @@ private final class AmountFieldComponent: Component {
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView, UITextFieldDelegate, ComponentTaggedView {
|
||||
public final class View: UIView, UITextFieldDelegate, ComponentTaggedView {
|
||||
public func matches(tag: Any) -> Bool {
|
||||
if let component = self.component, let componentTag = component.tag {
|
||||
let tag = tag as AnyObject
|
||||
@ -1563,7 +1582,7 @@ private final class AmountFieldComponent: Component {
|
||||
|
||||
private var didSetValueOnce = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
public override init(frame: CGRect) {
|
||||
self.placeholderView = ComponentView<Empty>()
|
||||
self.textField = TextFieldNodeView(frame: .zero)
|
||||
self.labelView = ComponentView<Empty>()
|
||||
@ -1577,15 +1596,19 @@ private final class AmountFieldComponent: Component {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func activateInput() {
|
||||
public func activateInput() {
|
||||
self.textField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func selectAll() {
|
||||
self.textField.selectAll(nil)
|
||||
public func deactivateInput() {
|
||||
self.textField.resignFirstResponder()
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
public func selectAll() {
|
||||
self.textField.selectAll(nil)
|
||||
}
|
||||
|
||||
public func animateError() {
|
||||
self.textField.layer.addShakeAnimation()
|
||||
let hapticFeedback = HapticFeedback()
|
||||
hapticFeedback.error()
|
||||
@ -1594,6 +1617,13 @@ private final class AmountFieldComponent: Component {
|
||||
})
|
||||
}
|
||||
|
||||
public func resetValue() {
|
||||
guard let component = self.component, let value = component.value else {
|
||||
return
|
||||
}
|
||||
self.textField.text = "\(value)"
|
||||
}
|
||||
|
||||
func update(component: AmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
@ -1633,6 +1663,7 @@ private final class AmountFieldComponent: Component {
|
||||
currency: component.currency,
|
||||
dateTimeFormat: component.dateTimeFormat,
|
||||
minValue: component.minValue ?? 0,
|
||||
forceMinValue: component.forceMinValue,
|
||||
allowZero: component.allowZero,
|
||||
maxValue: component.maxValue ?? Int64.max,
|
||||
updated: { [weak self] value in
|
||||
@ -1669,6 +1700,7 @@ private final class AmountFieldComponent: Component {
|
||||
currency: component.currency,
|
||||
dateTimeFormat: component.dateTimeFormat,
|
||||
minValue: component.minValue ?? 0,
|
||||
forceMinValue: component.forceMinValue,
|
||||
allowZero: component.allowZero,
|
||||
maxValue: component.maxValue ?? 10000000,
|
||||
updated: { [weak self] value in
|
||||
@ -1790,7 +1822,7 @@ private final class AmountFieldComponent: Component {
|
||||
labelView.removeFromSuperview()
|
||||
}
|
||||
|
||||
self.textField.frame = CGRect(x: leftInset, y: 4.0, width: size.width - 30.0, height: 44.0)
|
||||
self.textField.frame = CGRect(x: leftInset + component.textFieldOffset.x, y: 4.0 + component.textFieldOffset.y, width: size.width - 30.0, height: 44.0)
|
||||
|
||||
return size
|
||||
}
|
||||
@ -1805,28 +1837,6 @@ private final class AmountFieldComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setStrokeColor(foregroundColor.cgColor)
|
||||
|
||||
context.move(to: CGPoint(x: 10.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 20.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
||||
private struct StarsWithdrawConfiguration {
|
||||
static var defaultValue: StarsWithdrawConfiguration {
|
||||
return StarsWithdrawConfiguration(minWithdrawAmount: nil, maxPaidMediaAmount: nil, usdWithdrawRate: nil, tonUsdRate: nil)
|
||||
|
||||
@ -37,6 +37,7 @@ import TelegramStringFormatting
|
||||
import TextFormat
|
||||
import BrowserUI
|
||||
import MediaEditorScreen
|
||||
import GiftSetupScreen
|
||||
|
||||
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
|
||||
if case .default = navigation {
|
||||
@ -1498,19 +1499,30 @@ func openResolvedUrlImpl(
|
||||
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, case let .generic(gift) = auctionContext.gift {
|
||||
if let currentBidPeerId = auctionContext.currentBidPeerId {
|
||||
let controller = context.sharedContext.makeGiftAuctionBidScreen(
|
||||
context: context,
|
||||
toPeerId: currentBidPeerId,
|
||||
text: nil,
|
||||
entities: nil,
|
||||
hideName: false,
|
||||
auctionContext: auctionContext
|
||||
)
|
||||
navigationController?.pushViewController(controller)
|
||||
} else {
|
||||
let controller = context.sharedContext.makeGiftAuctionViewScreen(
|
||||
context: context,
|
||||
toPeerId: context.account.peerId,
|
||||
auctionContext: auctionContext
|
||||
auctionContext: auctionContext,
|
||||
completion: { [weak navigationController] in
|
||||
let controller = GiftSetupScreen(
|
||||
context: context,
|
||||
peerId: context.account.peerId,
|
||||
subject: .starGift(gift, nil),
|
||||
completion: nil
|
||||
)
|
||||
navigationController?.pushViewController(controller)
|
||||
}
|
||||
)
|
||||
navigationController?.pushViewController(controller)
|
||||
}
|
||||
|
||||
@ -3846,12 +3846,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return GiftAuctionInfoScreen(context: context, auctionContext: auctionContext, completion: completion)
|
||||
}
|
||||
|
||||
public func makeGiftAuctionBidScreen(context: AccountContext, toPeerId: EnginePeer.Id, auctionContext: GiftAuctionContext) -> ViewController {
|
||||
return GiftAuctionBidScreen(context: context, toPeerId: toPeerId, auctionContext: auctionContext)
|
||||
public func makeGiftAuctionBidScreen(context: AccountContext, toPeerId: EnginePeer.Id, text: String?, entities: [MessageTextEntity]?, hideName: Bool, auctionContext: GiftAuctionContext) -> ViewController {
|
||||
return GiftAuctionBidScreen(context: context, toPeerId: toPeerId, text: text, entities: entities, hideName: hideName, auctionContext: auctionContext)
|
||||
}
|
||||
|
||||
public func makeGiftAuctionViewScreen(context: AccountContext, toPeerId: EnginePeer.Id, auctionContext: GiftAuctionContext) -> ViewController {
|
||||
return GiftAuctionViewScreen(context: context, toPeerId: toPeerId, auctionContext: auctionContext)
|
||||
public func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping () -> Void) -> ViewController {
|
||||
return GiftAuctionViewScreen(context: context, auctionContext: auctionContext, completion: completion)
|
||||
}
|
||||
|
||||
public func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user