Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mikhail Filimonov 2025-12-03 11:22:28 +04:00
commit 6a0763f8fc
31 changed files with 1435 additions and 649 deletions

View file

@ -15506,3 +15506,33 @@ Error: %8$@";
"Gift.Auction.Extension" = "+ %1$@ for late bids in top %2$@";
"ChatList.Auctions.UpcomingAuction" = "Upcoming Auction";
"Gift.AuctionBid.UpcomingBid" = "You placed an early bid";
"Login.PhoneWithPasskeySubtitle" = "Enter your phone number\nor [log in using Passkey >](passkey)";
"PrivacySettings.Passkey" = "Passkey";
"Attachment.SharedAudio" = "SHARED AUDIO";
"Attachment.FilesSearchPlaceholder" = "Search shared audio";
"Chat.GiftPurchaseOffer.Reject" = "Reject";
"Chat.GiftPurchaseOffer.Accept" = "Accept";
"Passkeys.DeleteAlert.Title" = "Delete Passkey?";
"Passkeys.DeleteAlert.Text" = "Once deleted, this passkey can't be used to log in.\n\nDon't forget to remove it from your password manager too.";
"Passkeys.DeleteAlert.Action" = "Delete";
"Passkeys.Into.Title" = "Protect your account";
"Passkeys.Subtitle" = "Log in safely and keep your account secure.";
"Passkeys.Into.Title0" = "Create a Passkey";
"Passkeys.Into.Text0" = "Make a passkey to sign in easily and safely.";
"Passkeys.Into.Title1" = "Log in with Face ID";
"Passkeys.Into.Text1" = "Use Face ID, Touch ID, or your passcode to sign in.";
"Passkeys.Into.Title2" = "Store Passkey Securely";
"Passkeys.Into.Text2" = "Your passkey is safely kept in your iCloud Keychain.";
"Passkeys.ButtonCreate" = "Create Passkey";
"Passkeys.ButtonSkip" = "Skip";
"Passkeys.Title" = "Passkeys";
"Passkeys.Subtitle" = "Log in safely and keep your account secure.";
"Passkeys.EmptyName" = "Passkey";
"Passkeys.PasskeyCreatedPattern" = "created %@";
"Passkeys.PasskeyCreatedAndUsedPattern" = "created %1$@ • used %2$@";
"Passkeys.AddPasskey" = "Create Passkey";
"Passkeys.ListFooter" = "Your passkeys are stored securely in your password manager.";

View file

@ -1426,10 +1426,11 @@ public protocol SharedAccountContext: AnyObject {
func makeGiftUpgradePreviewScreen(context: AccountContext, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController
func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController
func makeGiftAuctionBidScreen(context: AccountContext, toPeerId: EnginePeer.Id, text: String?, entities: [MessageTextEntity]?, hideName: Bool, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?) -> ViewController
func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>) -> Void) -> ViewController
func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController
func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController
func makeGiftOfferScreen(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController
func makeGiftUpgradeVariantsPreviewScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute]) -> ViewController
func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController
@ -1475,6 +1476,7 @@ public protocol SharedAccountContext: AnyObject {
func makeNewContactScreen(context: AccountContext, peer: EnginePeer?, phoneNumber: String?, shareViaException: Bool, completion: @escaping (EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData?) -> Void) -> ViewController
func makeLoginEmailSetupController(context: AccountContext, blocking: Bool, emailPattern: String?, canAutoDismissIfNeeded: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController
func makePasskeySetupController(context: AccountContext, displaySkip: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController
func navigateToCurrentCall()
var hasOngoingCall: ValuePromise<Bool> { get }

View file

@ -576,11 +576,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
}
func updateDisplayPasskeyLoginOption() {
//TODO:localize
if self.account == nil {
return
}
let attributedText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("Enter your phone number\nor [log in using Passkey >](passkey)", attributes: MarkdownAttributes(
let attributedText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(self.strings.Login_PhoneWithPasskeySubtitle, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(17.0), textColor: self.theme.list.itemPrimaryTextColor),
link: MarkdownAttributeSet(font: Font.regular(17.0), textColor: self.theme.list.itemAccentColor),
@ -607,7 +606,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
let titleInset: CGFloat = layout.size.width > 320.0 ? 18.0 : 0.0
let additionalBottomInset: CGFloat = layout.size.width > 320.0 ? 80.0 : 10.0
self.titleNode.attributedText = NSAttributedString(string: self.account == nil ? strings.Login_NewNumber : strings.Login_PhoneTitle, font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
self.titleNode.attributedText = NSAttributedString(string: self.account == nil ? self.strings.Login_NewNumber : self.strings.Login_PhoneTitle, font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
self.titleActivateAreaNode.accessibilityLabel = self.titleNode.attributedText?.string ?? ""
let inset: CGFloat = 24.0

View file

@ -137,6 +137,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private let dismissAutoarchiveDisposable = MetaDisposable()
private var didSuggestAutoarchive = false
private var didSuggestLoginEmailSetup = false
private var didSuggestLoginPasskeySetup = false
private var presentationData: PresentationData
private let presentationDataValue = Promise<PresentationData>()
@ -2586,6 +2587,30 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
if values.contains(.setupPasskey) {
if strongSelf.didSuggestLoginPasskeySetup {
return
}
strongSelf.didSuggestLoginPasskeySetup = true
let _ = (context.engine.notices.getServerProvidedSuggestions(reload: true)
|> deliverOnMainQueue).start(next: { [weak strongSelf] currentValues in
guard let strongSelf, currentValues.contains(.setupPasskey) else {
return
}
if let navigationController = strongSelf.navigationController as? NavigationController {
let controller = strongSelf.context.sharedContext.makePasskeySetupController(context: strongSelf.context, displaySkip: true, navigationController: navigationController, completion: {
let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPasskey.id).startStandalone()
}, dismiss: {
let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupPasskey.id).startStandalone()
})
navigationController.pushViewController(controller)
}
})
return
}
if strongSelf.didSuggestAutoarchive {
return
}

View file

@ -673,12 +673,11 @@ private func privacyAndSecurityControllerEntries(
entries.append(.twoStepVerification(presentationData.theme, presentationData.strings.PrivacySettings_TwoStepAuth, twoStepAuthString, twoStepAuthData))
if displayPasskeys {
//TODO:localize
var passkeysString = ""
if let hasPasskeys = hasPasskeys {
passkeysString = hasPasskeys ? "On" : "Off"
passkeysString = hasPasskeys ? presentationData.strings.PrivacySettings_PasscodeOn : presentationData.strings.PrivacySettings_PasscodeOff
}
entries.append(.passkeys(presentationData.theme, "Passkey", passkeysString))
entries.append(.passkeys(presentationData.theme, presentationData.strings.PrivacySettings_Passkey, passkeysString))
}
if let privacySettings = privacySettings {
@ -1268,9 +1267,9 @@ public func privacyAndSecurityController(
}, openPasskeys: {
Task { @MainActor in
let initialPasskeysData = await (passkeysDataValue.get() |> take(1)).get()
let passkeysScreen = await PasskeysScreen(context: context, initialPasskeysData: initialPasskeysData, passkeysDataUpdated: { passkeysData in
let passkeysScreen = PasskeysScreen(context: context, displaySkip: false, initialPasskeysData: initialPasskeysData, passkeysDataUpdated: { passkeysData in
passkeysDataValue.set(.single(passkeysData))
})
}, completion: {}, cancel: {})
pushControllerImpl?(passkeysScreen, true)
}
}, openActiveSessions: {

View file

@ -20,6 +20,7 @@ public enum ServerProvidedSuggestion: Equatable {
case setupPhoto
case setupLoginEmail
case setupLoginEmailBlocking
case setupPasskey
case link(id: String, url: String, title: ServerSuggestionInfo.Item.Text, subtitle: ServerSuggestionInfo.Item.Text)
init?(string: String) {
@ -56,6 +57,8 @@ public enum ServerProvidedSuggestion: Equatable {
self = .setupLoginEmail
case "SETUP_LOGIN_EMAIL_NOSKIP":
self = .setupLoginEmailBlocking
case "SETUP_PASSKEY":
self = .setupPasskey
default:
return nil
}
@ -95,6 +98,8 @@ public enum ServerProvidedSuggestion: Equatable {
return "SETUP_LOGIN_EMAIL"
case .setupLoginEmailBlocking:
return "SETUP_LOGIN_EMAIL_NOSKIP"
case .setupPasskey:
return "SETUP_PASSKEY"
case let .link(id, _, _, _):
return id
}

View file

@ -133,6 +133,15 @@ public final class GiftAuctionContext {
}
}
public var isUpcoming: Bool {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if case let .ongoing(_, startTime, _, _, _, _, _, _, _, _, _, _) = self.auctionState {
return currentTime < startTime
} else {
return false
}
}
public convenience init(account: Account, gift: StarGift) {
self.init(account: account, gift: gift, initialAuctionState: nil, initialMyState: nil, initialTimeout: nil)
}

View file

@ -498,6 +498,7 @@ swift_library(
"//submodules/TelegramUI/Components/AttachmentFileController",
"//submodules/TelegramUI/Components/Contacts/NewContactScreen",
"//submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu",
"//submodules/TelegramUI/Components/Settings/PasskeysScreen",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [],

View file

@ -207,8 +207,7 @@ private func attachmentFileControllerEntries(presentationData: PresentationData,
case .recent:
listTitle = presentationData.strings.Attachment_RecentlySentFiles
case .audio:
//TODO:localize
listTitle = "SHARED AUDIO"
listTitle = presentationData.strings.Attachment_SharedAudio
}
if case .audio = mode {

View file

@ -138,7 +138,6 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode {
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)))
self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition)
//TODO:localize
let searchInputSize = self.searchInput.update(
transition: .immediate,
component: AnyComponent(
@ -147,7 +146,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode {
strings: self.presentationData.strings,
metrics: layout.metrics,
safeInsets: layout.safeInsets,
placeholder: self.mode == .audio ? "Search shared audio" : self.presentationData.strings.Attachment_FilesSearchPlaceholder,
placeholder: self.mode == .audio ? self.presentationData.strings.Attachment_FilesSearchPlaceholder : self.presentationData.strings.Attachment_FilesSearchPlaceholder,
updated: { [weak self] query in
guard let self else {
return

View file

@ -2835,16 +2835,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let buttonApprove = MemoryBuffer(data: Data(bytes: &buttonApproveValue, count: 1))
let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [
buttonDecline: ChatMessageActionButtonsNode.CustomInfo(
isEnabled: true,
icon: .suggestedPostReject
),
buttonApprove: ChatMessageActionButtonsNode.CustomInfo(
isEnabled: true,
icon: .suggestedPostApprove
),
buttonDecline: ChatMessageActionButtonsNode.CustomInfo(
isEnabled: true,
icon: .suggestedPostReject
)
]
//TODO:localize
let (minWidth, buttonsLayout) = actionButtonsLayout(
item.context,
item.presentationData.theme,
@ -2854,8 +2853,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
ReplyMarkupMessageAttribute(
rows: [
ReplyMarkupRow(buttons: [
ReplyMarkupButton(title: "Reject", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
ReplyMarkupButton(title: "Accept", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Reject, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)),
ReplyMarkupButton(title: item.presentationData.strings.Chat_GiftPurchaseOffer_Accept, titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove))
])
],
flags: [],

View file

@ -39,6 +39,8 @@ public final class GiftCompositionComponent: Component {
let animationOffset: CGPoint?
let animationScale: CGFloat?
let displayAnimationStars: Bool
let animateScaleOnTransition: Bool
let alwaysAnimateTransition: Bool
let revealedAttributes: Set<StarGift.UniqueGift.Attribute.AttributeType>
let externalState: ExternalState?
let requestUpdate: (ComponentTransition) -> Void
@ -50,6 +52,8 @@ public final class GiftCompositionComponent: Component {
animationOffset: CGPoint? = nil,
animationScale: CGFloat? = nil,
displayAnimationStars: Bool = false,
animateScaleOnTransition: Bool = true,
alwaysAnimateTransition: Bool = false,
revealedAttributes: Set<StarGift.UniqueGift.Attribute.AttributeType> = Set(),
externalState: ExternalState? = nil,
requestUpdate: @escaping (ComponentTransition) -> Void = { _ in }
@ -60,6 +64,8 @@ public final class GiftCompositionComponent: Component {
self.animationOffset = animationOffset
self.animationScale = animationScale
self.displayAnimationStars = displayAnimationStars
self.animateScaleOnTransition = animateScaleOnTransition
self.alwaysAnimateTransition = alwaysAnimateTransition
self.revealedAttributes = revealedAttributes
self.externalState = externalState
self.requestUpdate = requestUpdate
@ -84,6 +90,9 @@ public final class GiftCompositionComponent: Component {
if lhs.displayAnimationStars != rhs.displayAnimationStars {
return false
}
if lhs.animateScaleOnTransition != rhs.animateScaleOnTransition {
return false
}
if lhs.revealedAttributes != rhs.revealedAttributes {
return false
}
@ -718,12 +727,12 @@ public final class GiftCompositionComponent: Component {
animationFile = file
component.externalState?.previewModel = self.previewModels[Int(self.previewModelIndex)]
}
if case let .pattern(_, file, _) = self.previewPatterns[Int(self.previewPatternIndex)] {
if !self.previewPatterns.isEmpty, case let .pattern(_, file, _) = self.previewPatterns[Int(self.previewPatternIndex)] {
patternFile = file
files[file.fileId.id] = file
component.externalState?.previewSymbol = self.previewPatterns[Int(self.previewPatternIndex)]
}
if case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, _, _) = self.previewBackdrops[Int(self.previewBackdropIndex)] {
if !self.previewBackdrops.isEmpty, case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, _, _) = self.previewBackdrops[Int(self.previewBackdropIndex)] {
backgroundColor = UIColor(rgb: UInt32(bitPattern: outerColorValue))
secondBackgroundColor = UIColor(rgb: UInt32(bitPattern: innerColorValue))
patternColor = UIColor(rgb: UInt32(bitPattern: patternColorValue))
@ -767,7 +776,9 @@ public final class GiftCompositionComponent: Component {
}
var animateTransition = false
if self.animatePreviewTransition {
if component.alwaysAnimateTransition {
animateTransition = true
} else if self.animatePreviewTransition {
animateTransition = true
self.animatePreviewTransition = false
} else if let previousComponent, case .preview = previousComponent.subject, case .unique = component.subject {
@ -788,6 +799,9 @@ public final class GiftCompositionComponent: Component {
bounce = self.previewPatternIndex == -1
background = false
}
if !component.animateScaleOnTransition {
bounce = false
}
backgroundView.animateTransition(background: background, bounce: bounce)
}
if animateBackdropSwipe {

View file

@ -167,6 +167,8 @@ public final class GiftItemComponent: Component {
let isPinned: Bool
let isEditing: Bool
let isDateLocked: Bool
let isPlaceholder: Bool
let animateChanges: Bool
let mode: Mode
let action: (() -> Void)?
let contextAction: ((UIView, ContextGesture) -> Void)?
@ -191,6 +193,8 @@ public final class GiftItemComponent: Component {
isPinned: Bool = false,
isEditing: Bool = false,
isDateLocked: Bool = false,
isPlaceholder: Bool = false,
animateChanges: Bool = false,
mode: Mode = .generic,
action: (() -> Void)? = nil,
contextAction: ((UIView, ContextGesture) -> Void)? = nil
@ -214,6 +218,8 @@ public final class GiftItemComponent: Component {
self.isPinned = isPinned
self.isEditing = isEditing
self.isDateLocked = isDateLocked
self.isPlaceholder = isPlaceholder
self.animateChanges = animateChanges
self.mode = mode
self.action = action
self.contextAction = contextAction
@ -277,6 +283,12 @@ public final class GiftItemComponent: Component {
if lhs.isDateLocked != rhs.isDateLocked {
return false
}
if lhs.isPlaceholder != rhs.isPlaceholder {
return false
}
if lhs.animateChanges != rhs.animateChanges {
return false
}
if lhs.mode != rhs.mode {
return false
}
@ -636,12 +648,22 @@ public final class GiftItemComponent: Component {
}
var animationTransition = transition
var animateBackgroundChange = false
if self.animationLayer == nil || self.animationFile?.fileId != animationFile?.fileId, let emoji {
animationTransition = .immediate
self.animationFile = animationFile
var animateAppearance = false
if let animationLayer = self.animationLayer {
self.animationLayer = nil
animationLayer.removeFromSuperlayer()
if component.animateChanges {
animateAppearance = true
animateBackgroundChange = true
animationLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
animationLayer.removeFromSuperlayer()
})
} else {
animationLayer.removeFromSuperlayer()
}
}
let animationLayer = InlineStickerItemLayer(
context: .account(component.context),
@ -664,6 +686,9 @@ public final class GiftItemComponent: Component {
} else {
self.layer.insertSublayer(animationLayer, above: self.backgroundLayer)
}
if animateAppearance {
animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
}
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)
@ -672,6 +697,11 @@ public final class GiftItemComponent: Component {
}
if let backgroundColor {
if let backgroundView = self.patternView.view as? PeerInfoCoverComponent.View {
if animateBackgroundChange {
backgroundView.animateTransition(background: true, bounce: false)
}
}
let _ = self.patternView.update(
transition: .immediate,
component: AnyComponent(PeerInfoCoverComponent(
@ -688,7 +718,7 @@ public final class GiftItemComponent: Component {
environment: {},
containerSize: backgroundSize
)
if let backgroundView = self.patternView.view {
if let backgroundView = self.patternView.view as? PeerInfoCoverComponent.View {
if backgroundView.superview == nil {
backgroundView.layer.cornerRadius = cornerRadius
if #available(iOS 13.0, *) {
@ -917,7 +947,7 @@ public final class GiftItemComponent: Component {
let buttonSize = self.button.update(
transition: transition,
component: AnyComponent(
ButtonContentComponent(
StarsButtonContentComponent(
context: component.context,
text: price,
color: buttonColor,
@ -1468,28 +1498,34 @@ public final class GiftItemComponent: Component {
}
}
private final class ButtonContentComponent: Component {
public final class StarsButtonContentComponent: Component {
let context: AccountContext
let text: String
let color: UIColor
let tinted: Bool
let starsColor: UIColor?
let font: UIFont
let height: CGFloat
public init(
context: AccountContext,
text: String,
color: UIColor,
tinted: Bool = false,
starsColor: UIColor? = nil
starsColor: UIColor? = nil,
font: UIFont = Font.semibold(11.0),
height: CGFloat = 30.0
) {
self.context = context
self.text = text
self.color = color
self.tinted = tinted
self.starsColor = starsColor
self.font = font
self.height = height
}
public static func ==(lhs: ButtonContentComponent, rhs: ButtonContentComponent) -> Bool {
public static func ==(lhs: StarsButtonContentComponent, rhs: StarsButtonContentComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
@ -1505,11 +1541,17 @@ private final class ButtonContentComponent: Component {
if lhs.starsColor != rhs.starsColor {
return false
}
if lhs.font != rhs.font {
return false
}
if lhs.height != rhs.height {
return false
}
return true
}
public final class View: UIView {
private var component: ButtonContentComponent?
private var component: StarsButtonContentComponent?
private weak var componentState: EmptyComponentState?
private let backgroundLayer = SimpleLayer()
@ -1528,7 +1570,7 @@ private final class ButtonContentComponent: Component {
fatalError("init(coder:) has not been implemented")
}
func update(component: ButtonContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
func update(component: StarsButtonContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.componentState = state
@ -1537,7 +1579,7 @@ private final class ButtonContentComponent: Component {
textColor = .white
}
let attributedText = NSMutableAttributedString(string: component.text, font: Font.semibold(11.0), textColor: textColor)
let attributedText = NSMutableAttributedString(string: component.text, font: component.font, textColor: textColor)
let range = (attributedText.string as NSString).range(of: "#")
if range.location != NSNotFound {
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: component.tinted)), range: range)
@ -1563,7 +1605,7 @@ private final class ButtonContentComponent: Component {
)
let padding: CGFloat = 9.0
let size = CGSize(width: titleSize.width + padding * 2.0, height: 30.0)
let size = CGSize(width: titleSize.width + padding * 2.0, height: component.height)
if let starsColor = component.starsColor {
let starsLayer: StarsButtonEffectLayer
@ -1574,8 +1616,10 @@ private final class ButtonContentComponent: Component {
self.layer.addSublayer(starsLayer)
self.starsLayer = starsLayer
}
starsLayer.masksToBounds = true
starsLayer.frame = CGRect(origin: .zero, size: size)
starsLayer.update(color: starsColor, size: size)
starsLayer.cornerRadius = size.height * 0.5
} else {
self.starsLayer?.removeFromSuperlayer()
self.starsLayer = nil
@ -1600,7 +1644,7 @@ private final class ButtonContentComponent: Component {
}
}
self.backgroundLayer.backgroundColor = backgroundColor.cgColor
transition.setBackgroundColor(layer: self.backgroundLayer, color: backgroundColor)
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: size))
self.backgroundLayer.cornerRadius = size.height / 2.0

View file

@ -416,15 +416,31 @@ final class GiftOptionsScreenComponent: Component {
let giftController = component.context.sharedContext.makeGiftAuctionViewScreen(
context: component.context,
auctionContext: auctionContext,
completion: { [weak mainController] acquiredGifts in
let controller = GiftSetupScreen(
context: context,
peerId: component.peerId,
subject: .starGift(gift, nil),
auctionAcquiredGifts: acquiredGifts,
completion: nil
)
mainController?.push(controller)
completion: { [weak mainController] acquiredGifts, upgradeAttributes in
if component.peerId == context.account.peerId, let upgradeAttributes, let navigationController = mainController?.navigationController as? NavigationController {
let controller = context.sharedContext.makeGiftAuctionWearPreviewScreen(context: context, auctionContext: auctionContext, acquiredGifts: acquiredGifts, attributes: upgradeAttributes, completion: {
let controller = context.sharedContext.makeGiftAuctionBidScreen(
context: context,
toPeerId: context.account.peerId,
text: "",
entities: [],
hideName: true,
auctionContext: auctionContext,
acquiredGifts: acquiredGifts
)
navigationController.pushViewController(controller)
})
mainController?.push(controller)
} else {
let controller = GiftSetupScreen(
context: context,
peerId: component.peerId,
subject: .starGift(gift, nil),
auctionAcquiredGifts: acquiredGifts,
completion: nil
)
mainController?.push(controller)
}
}
)
mainController?.push(giftController)
@ -676,7 +692,7 @@ final class GiftOptionsScreenComponent: Component {
action = environment.strings.Gift_Options_Gift_ViewAuction
}
subject = .starGift(gift: gift, price: action)
} else if let availability = gift.availability, availability.remains == 0, let minResaleStars = availability.minResaleStars {
} else if let availability = gift.availability, availability.remains == 0, availability.resale > 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)")

View file

@ -0,0 +1,29 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "GiftRemainingCountComponent",
module_name = "GiftRemainingCountComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/Components/MultilineTextComponent",
],
visibility = [
"//visibility:public",
],
)

View file

@ -6,15 +6,11 @@ import TelegramCore
import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import PresentationDataUtils
import ComponentFlow
import MultilineTextComponent
import Markdown
import TextFormat
import RoundedRectWithTailPath
import GlassBackgroundComponent
public class RemainingCountComponent: Component {
public class GiftRemainingCountComponent: Component {
private let inactiveColor: UIColor
private let activeColors: [UIColor]
private let inactiveTitle: String
@ -62,7 +58,7 @@ public class RemainingCountComponent: Component {
self.groupingSeparator = groupingSeparator
}
public static func ==(lhs: RemainingCountComponent, rhs: RemainingCountComponent) -> Bool {
public static func ==(lhs: GiftRemainingCountComponent, rhs: GiftRemainingCountComponent) -> Bool {
if lhs.inactiveColor != rhs.inactiveColor {
return false
}
@ -109,7 +105,7 @@ public class RemainingCountComponent: Component {
}
public final class View: UIView {
private var component: RemainingCountComponent?
private var component: GiftRemainingCountComponent?
private let container: UIView
private let inactiveBackground: SimpleLayer
@ -162,7 +158,7 @@ public class RemainingCountComponent: Component {
}
var previousAvailableSize: CGSize?
func update(component: RemainingCountComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
func update(component: GiftRemainingCountComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
self.component = component
self.inactiveBackground.backgroundColor = component.inactiveColor.cgColor
self.activeBackground.backgroundColor = component.activeColors.last?.cgColor

View file

@ -51,6 +51,7 @@ swift_library(
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
"//submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent",
],
visibility = [
"//visibility:public",

View file

@ -38,6 +38,7 @@ import EdgeEffect
import AnimatedTextComponent
import GlassBarButtonComponent
import MessageInputPanelComponent
import GiftRemainingCountComponent
private final class GiftSetupScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -1719,7 +1720,7 @@ private final class GiftSetupScreenComponent: Component {
let sold = total - remains
let remainingCountSize = self.remainingCount.update(
transition: transition,
component: AnyComponent(RemainingCountComponent(
component: AnyComponent(GiftRemainingCountComponent(
inactiveColor: theme.list.itemBlocksBackgroundColor,
activeColors: [UIColor(rgb: 0x72d6ff), UIColor(rgb: 0x32a0f9)],
inactiveTitle: environment.strings.Gift_Send_Remains(remains),

View file

@ -62,6 +62,7 @@ swift_library(
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/BotPaymentsUI",
"//submodules/TelegramUI/Components/SegmentControlComponent",
"//submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent",
],
visibility = [
"//visibility:public",

View file

@ -612,7 +612,7 @@ private final class PeerComponent: Component {
var color = component.theme.list.itemSecondaryTextColor
switch component.status {
case .winning:
color = component.theme.list.itemDisclosureActions.constructive.fillColor
color = UIColor(rgb: 0x53a939)
case .outbid, .returned:
color = component.theme.list.itemDestructiveColor
default:
@ -2061,7 +2061,7 @@ private final class GiftAuctionBidScreenComponent: Component {
if case .finished = auctionState?.auctionState, let controller = self.environment?.controller() {
if let navigationController = controller.navigationController as? NavigationController {
controller.dismiss()
let auctionController = context.sharedContext.makeGiftAuctionViewScreen(context: context, auctionContext: auctionContext, completion: { _ in })
let auctionController = context.sharedContext.makeGiftAuctionViewScreen(context: context, auctionContext: auctionContext, completion: { _, _ in })
navigationController.pushViewController(auctionController)
}
}
@ -2197,8 +2197,12 @@ private final class GiftAuctionBidScreenComponent: Component {
var topBidsTitleComponent: AnyComponent<Empty>?
var topBidsComponents: [(EnginePeer.Id, AnyComponent<Empty>)] = []
var isUpcoming = false
let place: Int32
if let giftAuctionState = self.giftAuctionState, case let .ongoing(_, _, _, _, bidLevels, topBidders, _, _, _, _, _, lastGiftNumber) = giftAuctionState.auctionState {
if let giftAuctionState = self.giftAuctionState, case let .ongoing(_, startDate, _, _, bidLevels, topBidders, _, _, _, _, _, lastGiftNumber) = giftAuctionState.auctionState {
if currentTime < startDate {
isUpcoming = true
}
var myBidAmount = Int64(self.amount.realValue)
var myBidDate = currentTime
var isBiddingUp = true
@ -2220,6 +2224,9 @@ private final class GiftAuctionBidScreenComponent: Component {
if isBiddingUp {
bidTitleColor = environment.theme.list.itemSecondaryTextColor
bidTitle = environment.strings.Gift_AuctionBid_BidPreview
} else if isUpcoming {
bidTitleColor = environment.theme.list.itemSecondaryTextColor
bidTitle = environment.strings.Gift_AuctionBid_UpcomingBid
} else if giftAuctionState.myState.isReturned {
bidTitle = environment.strings.Gift_AuctionBid_Outbid
bidTitleColor = environment.theme.list.itemDestructiveColor
@ -2688,10 +2695,18 @@ private final class GiftAuctionBidScreenComponent: Component {
var initialContentHeight = contentHeight
let clippingY: CGFloat
let titleString: String
if isUpcoming {
//TODO:localize
titleString = "Place an Early Bid"
} else {
titleString = environment.strings.Gift_AuctionBid_Title
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: environment.strings.Gift_AuctionBid_Title, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
@ -2708,13 +2723,13 @@ private final class GiftAuctionBidScreenComponent: Component {
let subtitleSize = self.subtitle.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: subtitleString, font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor))
text: .plain(NSAttributedString(string: isUpcoming ? "" : subtitleString, font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor))
)),
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: 19.0), size: titleSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: isUpcoming ? 27.0 : 19.0), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.navigationBarContainer.addSubview(titleView)

View file

@ -1,427 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import PresentationDataUtils
import ComponentFlow
import ViewControllerComponent
import SheetComponent
import MultilineTextComponent
import MultilineTextWithEntitiesComponent
import BundleIconComponent
import ButtonComponent
import Markdown
import BalancedTextComponent
import AvatarNode
import TextFormat
import TelegramStringFormatting
import StarsAvatarComponent
import EmojiTextAttachmentView
import EmojiStatusComponent
import UndoUI
import ConfettiEffect
import PlainButtonComponent
import CheckComponent
import TooltipUI
import LottieComponent
import ContextUI
import TelegramNotices
import PremiumLockButtonSubtitleComponent
import StarsBalanceOverlayComponent
import BalanceNeededScreen
import GiftItemComponent
import GiftAnimationComponent
import ChatThemeScreen
import ProfileLevelRatingBarComponent
import AnimatedTextComponent
private final class GiftAuctionUpgradePreviewSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let gift: StarGift
let animateOut: ActionSlot<Action<()>>
let getController: () -> ViewController?
init(
context: AccountContext,
gift: StarGift,
animateOut: ActionSlot<Action<()>>,
getController: @escaping () -> ViewController?
) {
self.context = context
self.gift = gift
self.animateOut = animateOut
self.getController = getController
}
static func ==(lhs: GiftAuctionUpgradePreviewSheetContent, rhs: GiftAuctionUpgradePreviewSheetContent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.gift != rhs.gift {
return false
}
return true
}
final class State: ComponentState {
private let context: AccountContext
private var peerDisposable: Disposable?
var peerMap: [EnginePeer.Id: EnginePeer] = [:]
var cachedChevronImage: (UIImage, PresentationTheme)?
var cachedSmallChevronImage: (UIImage, PresentationTheme)?
var upgradePreviewTimer: SwiftSignalKit.Timer?
init(
context: AccountContext
) {
self.context = context
super.init()
let peerIds: [EnginePeer.Id] = [context.account.peerId]
self.peerDisposable = (
context.engine.data.get(EngineDataMap(
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in
return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
}
))
|> deliverOnMainQueue
).startStrict(next: { [weak self] peers in
if let strongSelf = self {
var peersMap: [EnginePeer.Id: EnginePeer] = [:]
for (peerId, maybePeer) in peers {
if let peer = maybePeer {
peersMap[peerId] = peer
}
}
strongSelf.peerMap = peersMap
strongSelf.updated(transition: .immediate)
}
})
self.upgradePreviewTimer = SwiftSignalKit.Timer(timeout: 3.0, repeat: true, completion: { [weak self] in
self?.upgradePreviewTimerTick()
}, queue: Queue.mainQueue())
self.upgradePreviewTimer?.start()
self.upgradePreviewTimerTick()
}
deinit {
self.peerDisposable?.dispose()
self.upgradePreviewTimer?.invalidate()
}
private func upgradePreviewTimerTick() {
}
}
func makeState() -> State {
return State(context: self.context)
}
static var body: Body {
let animation = Child(GiftCompositionComponent.self)
let avatar = Child(AvatarComponent.self)
let peerName = Child(MultilineTextComponent.self)
//let description = Child(MultilineTextComponent.self)
//let initialGift = Child(GiftItemComponent.self)
//let upgradedGift = Child(GiftItemComponent.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 theme = environment.theme
let strings = environment.strings
let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder
let controller = environment.controller
let state = context.state
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
var contentHeight: CGFloat = 0.0
let headerHeight: CGFloat = 226.0
let headerSubject: GiftCompositionComponent.Subject = .preview([])
var peerNameString = ""
if let peer = state.peerMap[component.context.account.peerId] {
peerNameString = peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
let peerName = peerName.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: peerNameString,
font: Font.bold(20.0),
textColor: .white,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let animationOffset: CGPoint? = CGPoint(x: peerName.size.width / 2.0 + 20.0 - 12.0, y: 79.0)
let animationScale: CGFloat = 0.19
let animation = animation.update(
component: GiftCompositionComponent(
context: component.context,
theme: environment.theme,
subject: headerSubject,
animationOffset: animationOffset,
animationScale: animationScale,
displayAnimationStars: true,
externalState: giftCompositionExternalState,
requestUpdate: { [weak state] transition in
state?.updated(transition: transition)
}
),
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))
)
contentHeight += headerHeight
if let peer = state.peerMap[component.context.account.peerId] {
let avatar = avatar.update(
component: AvatarComponent(
context: component.context,
theme: theme,
peer: peer
),
environment: {},
availableSize: CGSize(width: 100.0, height: 100.0),
transition: context.transition
)
context.add(avatar
.position(CGPoint(x: context.availableSize.width / 2.0, y: 86.0))
)
}
context.add(peerName
.position(CGPoint(x: context.availableSize.width / 2.0 - 12.0, y: 167.0))
)
// let vibrantColor: UIColor
// if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
// vibrantColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
// } else {
// vibrantColor = UIColor.white.withAlphaComponent(0.6)
// }
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 button = 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: {
if let controller = controller() as? GiftAuctionUpgradePreviewScreen {
controller.dismissAnimated()
}
}),
availableSize: buttonSize,
transition: context.transition
)
let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: button.size)
context.add(button
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
)
contentHeight += button.size.height
contentHeight += 7.0
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
return CGSize(width: context.availableSize.width, height: contentHeight + 5.0 + effectiveBottomInset)
}
}
}
final class GiftAuctionUpgradePreviewSheetComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let gift: StarGift
init(
context: AccountContext,
gift: StarGift
) {
self.context = context
self.gift = gift
}
static func ==(lhs: GiftAuctionUpgradePreviewSheetComponent, rhs: GiftAuctionUpgradePreviewSheetComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.gift != rhs.gift {
return false
}
return true
}
static var body: Body {
let sheet = Child(SheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let sheet = sheet.update(
component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(GiftAuctionUpgradePreviewSheetContent(
context: context.component.context,
gift: context.component.gift,
animateOut: animateOut,
getController: controller
)),
style: .glass,
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
followContentSizeChanges: true,
clipsContent: true,
hasDimView: false,
autoAnimateOut: false,
externalState: sheetExternalState,
animateOut: animateOut,
onPan: {
},
willDismiss: {
if let controller = controller() as? GiftAuctionUpgradePreviewScreen {
controller.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.3).withUserData(ViewControllerComponentContainer.AnimateOutTransition()))
}
}
),
environment: {
environment
SheetComponentEnvironment(
isDisplaying: environment.value.isVisible,
isCentered: environment.metrics.widthClass == .regular,
hasInputHeight: !environment.inputHeight.isZero,
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
dismiss: { animated in
if animated {
if let controller = controller() as? GiftAuctionUpgradePreviewScreen {
controller.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.3).withUserData(ViewControllerComponentContainer.AnimateOutTransition()))
animateOut.invoke(Action { _ in
controller.dismiss(completion: nil)
})
}
} else {
if let controller = controller() as? GiftAuctionUpgradePreviewScreen {
controller.dismiss(completion: nil)
}
}
}
)
},
availableSize: context.availableSize,
transition: context.transition
)
context.add(sheet
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
var sideInset: CGFloat = 0.0
var bottomInset: CGFloat = max(environment.safeInsets.bottom, sheetExternalState.contentHeight)
if case .regular = environment.metrics.widthClass {
sideInset = floor((context.availableSize.width - 430.0) / 2.0) - 12.0
bottomInset = (context.availableSize.height - sheetExternalState.contentHeight) / 2.0 + sheetExternalState.contentHeight
}
let layout = ContainerViewLayout(
size: context.availableSize,
metrics: environment.metrics,
deviceMetrics: environment.deviceMetrics,
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0),
safeInsets: UIEdgeInsets(top: 0.0, left: max(sideInset, environment.safeInsets.left), bottom: 0.0, right: max(sideInset, environment.safeInsets.right)),
additionalInsets: .zero,
statusBarHeight: environment.statusBarHeight,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
)
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
}
return context.availableSize
}
}
}
public class GiftAuctionUpgradePreviewScreen: ViewControllerComponentContainer {
private let context: AccountContext
public init(
context: AccountContext,
gift: StarGift
) {
self.context = context
super.init(
context: context,
component: GiftAuctionUpgradePreviewSheetComponent(context: context, gift: gift),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
theme: .default
)
self.navigationPresentation = .flatModal
self.automaticallyControlPresentationContextLayout = false
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
self.view.disablesInteractiveModalDismiss = true
}
public func dismissAnimated() {
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
view.dismissAnimated()
}
}
}

View file

@ -72,8 +72,15 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
private var giftAuctionAcquiredGiftsPromise = ValuePromise<[GiftAuctionAcquiredGift]>()
private var giftAuctionAcquiredGiftsDisposable = MetaDisposable()
private(set) var giftValueInfo: StarGift.UniqueGift.ValueInfo?
private var giftValueInfoDisposable: Disposable?
private(set) var giftUpgradeAttributes: [StarGift.UniqueGift.Attribute]?
private var previewTimer: SwiftSignalKit.Timer?
private(set) var previewModelIndex: Int = -1
private(set) var previewBackdropIndex: Int = -1
private(set) var previewSymbolIndex: Int = -1
private(set) var previewModels: [StarGift.UniqueGift.Attribute] = []
private(set) var previewBackdrops: [StarGift.UniqueGift.Attribute] = []
private(set) var previewSymbols: [StarGift.UniqueGift.Attribute] = []
var cachedStarImage: (UIImage, PresentationTheme)?
@ -105,30 +112,87 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
if let acquiredCount = auctionState?.myState.acquiredCount, acquiredCount > (previousState?.myState.acquiredCount ?? 0) {
self.loadAcquiredGifts()
}
if !"".isEmpty, self.giftValueInfoDisposable == nil, let auctionState, case let .generic(gift) = auctionState.gift, case .finished = auctionState.auctionState, let slug = gift.auctionSlug {
self.giftValueInfoDisposable = (self.context.engine.payments.getUniqueStarGiftValueInfo(slug: "\(slug)-1")
|> deliverOnMainQueue).start(next: { [weak self] valueInfo in
guard let self else {
return
}
self.giftValueInfo = valueInfo
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()
if case let .generic(gift) = auctionContext.gift, let upgradeVariantsCount = gift.upgradeVariantsCount, upgradeVariantsCount > 0 {
let _ = (context.engine.payments.getStarGiftUpgradeAttributes(giftId: gift.id)
|> deliverOnMainQueue).start(next: { [weak self] attributes in
guard let self, let attributes else {
return
}
self.giftUpgradeAttributes = attributes
let randomModels = Array(attributes.filter({ attribute in
if case .model = attribute {
return true
} else {
return false
}
}).shuffled().prefix(5))
self.previewModels = randomModels
let randomBackdrops = Array(attributes.filter({ attribute in
if case .backdrop = attribute {
return true
} else {
return false
}
}).shuffled())
self.previewBackdrops = randomBackdrops
let randomSymbols = Array(attributes.filter({ attribute in
if case .pattern = attribute {
return true
} else {
return false
}
}).shuffled().prefix(5))
self.previewSymbols = randomSymbols
self.updated()
})
self.previewTimer = SwiftSignalKit.Timer(timeout: 3.0, repeat: true, completion: { [weak self] in
guard let self else {
return
}
self.previewTimerTick()
}, queue: Queue.mainQueue())
self.previewTimer?.start()
}
}
deinit {
self.disposable?.dispose()
self.giftAuctionAcquiredGiftsDisposable.dispose()
self.giftAuctionTimer?.invalidate()
self.giftValueInfoDisposable?.dispose()
self.previewTimer?.invalidate()
}
private func previewTimerTick() {
guard !self.previewModels.isEmpty else { return }
self.previewModelIndex = (self.previewModelIndex + 1) % self.previewModels.count
let previousSymbolIndex = self.previewSymbolIndex
var randomSymbolIndex = previousSymbolIndex
while randomSymbolIndex == previousSymbolIndex && !self.previewSymbols.isEmpty {
randomSymbolIndex = Int.random(in: 0 ..< self.previewSymbols.count)
}
if !self.previewSymbols.isEmpty { self.previewSymbolIndex = randomSymbolIndex }
let previousBackdropIndex = self.previewBackdropIndex
var randomBackdropIndex = previousBackdropIndex
while randomBackdropIndex == previousBackdropIndex && !self.previewBackdrops.isEmpty {
randomBackdropIndex = Int.random(in: 0 ..< self.previewBackdrops.count)
}
if !self.previewBackdrops.isEmpty { self.previewBackdropIndex = randomBackdropIndex }
self.updated(transition: .easeInOut(duration: 0.25))
}
func loadAcquiredGifts() {
@ -186,7 +250,14 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
self.dismiss(animated: true)
controller.completion(self.giftAuctionAcquiredGiftsPromise.get())
var compactUpgradeAttributes: [StarGift.UniqueGift.Attribute] = []
if !self.previewModels.isEmpty {
compactUpgradeAttributes.append(contentsOf: self.previewModels)
compactUpgradeAttributes.append(contentsOf: self.previewBackdrops)
compactUpgradeAttributes.append(contentsOf: self.previewSymbols)
}
controller.completion(self.giftAuctionAcquiredGiftsPromise.get(), !compactUpgradeAttributes.isEmpty ? compactUpgradeAttributes : nil)
}
func openPeer(_ peer: EnginePeer, dismiss: Bool = true) {
@ -319,7 +390,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
func openGiftFragmentResale() {
guard let controller = self.getController() as? GiftAuctionViewScreen, let navigationController = controller.navigationController as? NavigationController, let url = self.giftValueInfo?.fragmentListedUrl else {
guard let controller = self.getController() as? GiftAuctionViewScreen, let navigationController = controller.navigationController as? NavigationController, case let .finished(_, _, _, _, _, fragmentListedUrl) = self.giftAuctionState?.auctionState, let url = fragmentListedUrl else {
return
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
@ -390,23 +461,27 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
static var body: Body {
let header = Child(GiftCompositionComponent.self)
let closeButton = Child(GlassBarButtonComponent.self)
let moreButton = Child(GlassBarButtonComponent.self)
let animation = Child(GiftItemComponent.self)
let title = Child(MultilineTextComponent.self)
let description = Child(BalancedTextComponent.self)
let description = Child(StarsButtonContentComponent.self)
let learnMore = Child(PlainButtonComponent.self)
let table = Child(TableComponent.self)
let button = Child(ButtonComponent.self)
let variantsButton = Child(PlainButtonComponent.self)
let acquiredButton = Child(PlainButtonComponent.self)
let telegramSaleButton = Child(PlainButtonComponent.self)
let fragmentSaleButton = Child(PlainButtonComponent.self)
let moreButtonPlayOnce = ActionSlot<Void>()
let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
return { context in
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
@ -420,49 +495,81 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
var titleString: String = ""
var giftIconSubject: GiftItemComponent.Subject?
var genericGift: StarGift.Gift?
switch component.auctionContext.gift {
case let .generic(gift):
titleString = gift.title ?? ""
giftIconSubject = .starGift(gift: gift, price: "")
genericGift = gift
default:
break
}
let _ = giftIconSubject
let _ = genericGift
var originY: CGFloat = 0.0
var buttonColor: UIColor = .white.withAlphaComponent(0.1)
var secondaryTextColor: UIColor = .white.withAlphaComponent(0.4)
if let genericGift {
let animation = animation.update(
component: GiftItemComponent(
var attributes: [StarGift.UniqueGift.Attribute] = []
if state.previewModelIndex == -1 {
attributes.append(.model(name: "", file: genericGift.file, rarity: 0))
if let background = genericGift.background {
attributes.append(.backdrop(name: "", id: 0, innerColor: background.centerColor, outerColor: background.edgeColor, patternColor: 0, textColor: 0, rarity: 0))
}
} else if !state.previewModels.isEmpty {
attributes.append(state.previewModels[state.previewModelIndex])
if !state.previewBackdrops.isEmpty {
attributes.append(state.previewBackdrops[state.previewBackdropIndex])
}
if !state.previewSymbols.isEmpty {
attributes.append(state.previewSymbols[state.previewSymbolIndex])
}
}
if let backdropAttribute = attributes.first(where: { attribute in
if case .backdrop = attribute {
return true
} else {
return false
}
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
let topColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04)
let bottomColor = UIColor(rgb: UInt32(bitPattern: outerColor)).withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8).withMultipliedBrightnessBy(1.25)
secondaryTextColor = topColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.5)
}
let header = header.update(
component: GiftCompositionComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
subject: .starGift(gift: genericGift, price: ""),
ribbon: GiftItemComponent.Ribbon(text: strings.Gift_Auction_Auction, color: .orange),
outline: .orange,
mode: .header
theme: theme,
subject: .preview(attributes),
animationOffset: CGPoint(x: 0.0, y: -4.0),
animationScale: nil,
displayAnimationStars: false,
revealedAttributes: Set(),
externalState: giftCompositionExternalState,
requestUpdate: { [weak state] transition in
state?.updated(transition: transition)
}
),
availableSize: CGSize(width: 120.0, height: 120.0),
availableSize: CGSize(width: context.availableSize.width, height: 264.0),
transition: context.transition
)
context.add(animation
.position(CGPoint(x: context.availableSize.width / 2.0, y: 92.0))
context.add(header
.position(CGPoint(x: context.availableSize.width / 2.0, y: header.size.height * 0.5))
)
originY += header.size.height
originY += 16.0
}
originY += 177.0
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: titleString,
font: Font.bold(24.0),
textColor: theme.list.itemPrimaryTextColor,
font: Font.bold(20.0),
textColor: .white,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
@ -472,11 +579,10 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
transition: .immediate
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: 174.0))
.position(CGPoint(x: context.availableSize.width / 2.0, y: 209.0))
)
var descriptionText: String = ""
var descriptionColor = theme.list.itemSecondaryTextColor
let tableFont = Font.regular(15.0)
let tableTextColor = theme.list.itemPrimaryTextColor
@ -502,7 +608,6 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
if isEnded {
descriptionText = strings.Gift_Auction_Ended
descriptionColor = theme.list.itemDestructiveColor
tableItems.append(.init(
id: "firstSale",
@ -572,11 +677,8 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
)
))
} else {
var auctionGiftsPerRound: Int32 = 50
if let auctionGiftsPerRoundValue = gift.auctionGiftsPerRound {
auctionGiftsPerRound = auctionGiftsPerRoundValue
}
descriptionText = strings.Gift_Auction_Description("\(auctionGiftsPerRound)", gift.title ?? "").string
//TODO:localize
descriptionText = isUpcoming ? "Upcoming Auction" : "Gift Auction"
tableItems.append(.init(
id: "start",
@ -670,11 +772,28 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
}
}
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let textColor = descriptionColor
let linkColor = theme.list.itemAccentColor
let description = description.update(
component: StarsButtonContentComponent(
context: component.context,
text: descriptionText,
color: buttonColor,
tinted: true,
starsColor: UIColor(white: 1.0, alpha: 0.5),
font: Font.medium(13.0),
height: 24.0
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
transition: context.transition
)
context.add(description
.position(CGPoint(x: context.availableSize.width / 2.0, y: 167.0 + description.size.height / 2.0))
)
let textFont = Font.regular(13.0)
let boldTextFont = Font.semibold(13.0)
let textColor = UIColor.white
let linkColor = UIColor.white
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)
})
@ -685,48 +804,35 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
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))
//TODO:localize
let learnMoreAttributedString = parseMarkdownIntoAttributedString("Learn more about Telegram Gifts >", attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
if let range = learnMoreAttributedString.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 {
learnMoreAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: learnMoreAttributedString.string))
}
let description = description.update(
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
}
let learnMore = learnMore.update(
component: PlainButtonComponent(
content: AnyComponent(BalancedTextComponent(
text: .plain(learnMoreAttributedString),
maximumNumberOfLines: 1,
tintColor: secondaryTextColor
)),
action: {
let controller = component.context.sharedContext.makeGiftAuctionInfoScreen(
context: component.context,
auctionContext: component.auctionContext,
completion: nil
)
environment.controller()?.push(controller)
},
tapAction: { attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
let controller = component.context.sharedContext.makeGiftAuctionInfoScreen(
context: component.context,
auctionContext: component.auctionContext,
completion: nil
)
environment.controller()?.push(controller)
}
}
animateScale: false
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
availableSize: context.availableSize,
transition: context.transition
)
context.add(description
.position(CGPoint(x: context.availableSize.width / 2.0, y: 198.0 + description.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
context.add(learnMore
.position(CGPoint(x: context.availableSize.width / 2.0, y: 238.0))
)
originY += description.size.height
originY += 42.0
let table = table.update(
component: TableComponent(
@ -738,8 +844,6 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
)
context.add(table
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += table.size.height + 26.0
@ -793,8 +897,8 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
hasAdditionalButtons = true
}
if let giftValueInfo = state.giftValueInfo, case let .generic(gift) = component.auctionContext.gift {
if let listedCount = giftValueInfo.listedCount, listedCount > 0 {
if case let .finished(_, _, _, listedCount, fragmentListedCount, _) = state.giftAuctionState?.auctionState, case let .generic(gift) = component.auctionContext.gift {
if let listedCount = listedCount, listedCount > 0 {
originY += 5.0
let telegramSaleButton = telegramSaleButton.update(
@ -840,7 +944,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
hasAdditionalButtons = true
}
if let listedCount = giftValueInfo.fragmentListedCount, listedCount > 0 {
if let listedCount = fragmentListedCount, listedCount > 0 {
let fragmentSaleButton = fragmentSaleButton.update(
component: PlainButtonComponent(content: AnyComponent(
HStack([
@ -885,6 +989,96 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
}
if case let .generic(gift) = component.auctionContext.gift, let upgradeVariantsCount = gift.upgradeVariantsCount {
originY += 5.0
if !hasAdditionalButtons {
originY -= 13.0
}
var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
if !state.previewModels.isEmpty {
if state.previewModels.count > 0 {
variant1 = .preview(attributes: [state.previewModels[0]], rarity: 0)
}
if state.previewModels.count > 1 {
variant2 = .preview(attributes: [state.previewModels[1]], rarity: 0)
}
if state.previewModels.count > 2 {
variant3 = .preview(attributes: [state.previewModels[2]], rarity: 0)
}
}
let variantsButton = variantsButton.update(
component: PlainButtonComponent(content: AnyComponent(
HStack([
AnyComponentWithIdentity(id: "view", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Auction_ViewVariants, font: Font.regular(13.0), textColor: theme.actionSheet.controlAccentColor)))
)),
AnyComponentWithIdentity(id: "spacing", component: AnyComponent(
Rectangle(color: .clear, width: 7.0, height: 1.0)
)),
AnyComponentWithIdentity(id: "icon1", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant1,
isPlaceholder: state.previewModels.isEmpty,
mode: .tableIcon
)
)),
AnyComponentWithIdentity(id: "icon2", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant2,
isPlaceholder: state.previewModels.isEmpty,
mode: .tableIcon
)
)),
AnyComponentWithIdentity(id: "icon3", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant3,
isPlaceholder: state.previewModels.isEmpty,
mode: .tableIcon
)
)),
AnyComponentWithIdentity(id: "text", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: " \(strings.Gift_Auction_Variants(upgradeVariantsCount))", font: Font.regular(13.0), textColor: theme.actionSheet.controlAccentColor)))
)),
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: theme.actionSheet.controlAccentColor)
))
], spacing: 0.0)
), action: { [weak state] in
guard let state, let attributes = state.giftUpgradeAttributes else {
return
}
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsPreviewScreen(context: component.context, gift: .generic(gift), attributes: attributes)
environment.controller()?.push(variantsController)
}, animateScale: false),
availableSize: CGSize(width: context.availableSize.width - 64.0, height: context.availableSize.height),
transition: context.transition
)
context.add(variantsButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + variantsButton.size.height / 2.0)))
originY += variantsButton.size.height
originY += 12.0
originY -= 15.0
hasAdditionalButtons = true
}
if hasAdditionalButtons {
originY += 21.0
}
@ -1007,13 +1201,13 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
let closeButton = closeButton.update(
component: GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
backgroundColor: buttonColor,
isDark: theme.overallDarkAppearance,
state: .generic,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor
tintColor: .white
)
)),
action: { [weak state] _ in
@ -1024,7 +1218,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
),
availableSize: CGSize(width: 40.0, height: 40.0),
transition: .immediate
transition: context.transition
)
context.add(closeButton
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
@ -1033,15 +1227,15 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
let moreButton = moreButton.update(
component: GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
backgroundColor: buttonColor,
isDark: theme.overallDarkAppearance,
state: .generic,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "more", component: AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(
name: "anim_morewide"
),
color: theme.rootController.navigationBar.glassBarButtonForegroundColor,
color: .white,
size: CGSize(width: 34.0, height: 34.0),
playOnce: moreButtonPlayOnce
)
@ -1055,7 +1249,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
),
availableSize: CGSize(width: 40.0, height: 40.0),
transition: .immediate
transition: context.transition
)
context.add(moreButton
.position(CGPoint(x: context.availableSize.width - 16.0 - moreButton.size.width / 2.0, y: 16.0 + moreButton.size.height / 2.0))
@ -1181,12 +1375,12 @@ final class GiftAuctionViewSheetComponent: CombinedComponent {
}
public final class GiftAuctionViewScreen: ViewControllerComponentContainer {
fileprivate let completion: (Signal<[GiftAuctionAcquiredGift], NoError>) -> Void
fileprivate let completion: (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void
public init(
context: AccountContext,
auctionContext: GiftAuctionContext,
completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>) -> Void
completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void
) {
self.completion = completion

View file

@ -0,0 +1,701 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import PresentationDataUtils
import ComponentFlow
import ViewControllerComponent
import SheetComponent
import MultilineTextComponent
import MultilineTextWithEntitiesComponent
import BundleIconComponent
import ButtonComponent
import Markdown
import BalancedTextComponent
import AvatarNode
import TextFormat
import TelegramStringFormatting
import PlainButtonComponent
import GiftItemComponent
import GiftAnimationComponent
import GlassBarButtonComponent
import GiftRemainingCountComponent
private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let auctionContext: GiftAuctionContext
let attributes: [StarGift.UniqueGift.Attribute]
let animateOut: ActionSlot<Action<()>>
let getController: () -> ViewController?
init(
context: AccountContext,
auctionContext: GiftAuctionContext,
attributes: [StarGift.UniqueGift.Attribute],
animateOut: ActionSlot<Action<()>>,
getController: @escaping () -> ViewController?
) {
self.context = context
self.auctionContext = auctionContext
self.attributes = attributes
self.animateOut = animateOut
self.getController = getController
}
static func ==(lhs: GiftAuctionWearPreviewSheetContent, rhs: GiftAuctionWearPreviewSheetContent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
final class State: ComponentState {
private let context: AccountContext
private var peerDisposable: Disposable?
var peerMap: [EnginePeer.Id: EnginePeer] = [:]
var cachedSmallChevronImage: (UIImage, PresentationTheme)?
private var previewTimer: SwiftSignalKit.Timer?
private(set) var previewModelIndex: Int = 0
private(set) var previewBackdropIndex: Int = 0
private(set) var previewSymbolIndex: Int = 0
private(set) var previewModels: [StarGift.UniqueGift.Attribute] = []
private(set) var previewBackdrops: [StarGift.UniqueGift.Attribute] = []
private(set) var previewSymbols: [StarGift.UniqueGift.Attribute] = []
init(
context: AccountContext,
attributes: [StarGift.UniqueGift.Attribute]
) {
self.context = context
super.init()
for attribute in attributes {
switch attribute {
case .model:
self.previewModels.append(attribute)
case .backdrop:
self.previewBackdrops.append(attribute)
case .pattern:
self.previewSymbols.append(attribute)
default:
break
}
}
let peerIds: [EnginePeer.Id] = [context.account.peerId]
self.peerDisposable = (
context.engine.data.get(EngineDataMap(
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in
return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
}
))
|> deliverOnMainQueue
).startStrict(next: { [weak self] peers in
if let strongSelf = self {
var peersMap: [EnginePeer.Id: EnginePeer] = [:]
for (peerId, maybePeer) in peers {
if let peer = maybePeer {
peersMap[peerId] = peer
}
}
strongSelf.peerMap = peersMap
strongSelf.updated(transition: .immediate)
}
})
self.previewTimer = SwiftSignalKit.Timer(timeout: 3.0, repeat: true, completion: { [weak self] in
guard let self else {
return
}
self.previewTimerTick()
}, queue: Queue.mainQueue())
self.previewTimer?.start()
}
deinit {
self.peerDisposable?.dispose()
self.previewTimer?.invalidate()
}
private func previewTimerTick() {
guard !self.previewModels.isEmpty else { return }
self.previewModelIndex = (self.previewModelIndex + 1) % self.previewModels.count
let previousSymbolIndex = self.previewSymbolIndex
var randomSymbolIndex = previousSymbolIndex
while randomSymbolIndex == previousSymbolIndex && !self.previewSymbols.isEmpty {
randomSymbolIndex = Int.random(in: 0 ..< self.previewSymbols.count)
}
if !self.previewSymbols.isEmpty { self.previewSymbolIndex = randomSymbolIndex }
let previousBackdropIndex = self.previewBackdropIndex
var randomBackdropIndex = previousBackdropIndex
while randomBackdropIndex == previousBackdropIndex && !self.previewBackdrops.isEmpty {
randomBackdropIndex = Int.random(in: 0 ..< self.previewBackdrops.count)
}
if !self.previewBackdrops.isEmpty { self.previewBackdropIndex = randomBackdropIndex }
self.updated(transition: .easeInOut(duration: 0.25))
}
}
func makeState() -> State {
return State(context: self.context, attributes: self.attributes)
}
static var body: Body {
let closeButton = Child(GlassBarButtonComponent.self)
let animation = Child(GiftCompositionComponent.self)
let avatar = Child(AvatarComponent.self)
let peerName = Child(MultilineTextComponent.self)
let learnMore = Child(PlainButtonComponent.self)
let initialGift = Child(GiftItemComponent.self)
let upgradedGift = Child(GiftItemComponent.self)
let arrow = Child(BundleIconComponent.self)
let upgradeLabel = Child(MultilineTextComponent.self)
let remainingCount = Child(GiftRemainingCountComponent.self)
let auctionFooter = Child(MultilineTextComponent.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 theme = environment.theme
let strings = environment.strings
let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder
let controller = environment.controller
let state = context.state
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
var contentHeight: CGFloat = 0.0
let headerHeight: CGFloat = 226.0
var peerNameString = ""
if let peer = state.peerMap[component.context.account.peerId] {
peerNameString = peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
let peerName = peerName.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: peerNameString,
font: Font.bold(20.0),
textColor: .white,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let animationOffset: CGPoint? = CGPoint(x: peerName.size.width / 2.0 + 20.0 - 12.0, y: 79.0)
let animationScale: CGFloat = 0.19
var attributes: [StarGift.UniqueGift.Attribute] = []
if !state.previewModels.isEmpty {
attributes.append(state.previewModels[state.previewModelIndex])
if !state.previewBackdrops.isEmpty {
attributes.append(state.previewBackdrops[state.previewBackdropIndex])
}
if !state.previewSymbols.isEmpty {
attributes.append(state.previewSymbols[state.previewSymbolIndex])
}
}
let animation = animation.update(
component: GiftCompositionComponent(
context: component.context,
theme: environment.theme,
subject: .preview(attributes),
animationOffset: animationOffset,
animationScale: animationScale,
displayAnimationStars: true,
animateScaleOnTransition: false,
externalState: giftCompositionExternalState,
requestUpdate: { [weak state] transition in
state?.updated(transition: transition)
}
),
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))
)
contentHeight += headerHeight
if let peer = state.peerMap[component.context.account.peerId] {
let avatar = avatar.update(
component: AvatarComponent(
context: component.context,
theme: theme,
peer: peer
),
environment: {},
availableSize: CGSize(width: 100.0, height: 100.0),
transition: context.transition
)
context.add(avatar
.position(CGPoint(x: context.availableSize.width / 2.0, y: 86.0))
)
}
context.add(peerName
.position(CGPoint(x: context.availableSize.width / 2.0 - 12.0, y: 167.0))
)
var buttonColor: UIColor = .white.withAlphaComponent(0.1)
var secondaryTextColor: UIColor = .white.withAlphaComponent(0.4)
if let backdropAttribute = attributes.first(where: { attribute in
if case .backdrop = attribute {
return true
} else {
return false
}
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
let topColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04)
let bottomColor = UIColor(rgb: UInt32(bitPattern: outerColor)).withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8)
secondaryTextColor = topColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.5)
}
let closeButton = closeButton.update(
component: GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: buttonColor.withMultipliedBrightnessBy(1.2),
isDark: theme.overallDarkAppearance,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: .white
)
)),
action: { _ in
(controller() as? GiftAuctionWearPreviewScreen)?.dismissAnimated()
}
),
availableSize: CGSize(width: 40.0, height: 40.0),
transition: .immediate
)
context.add(closeButton
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
)
let textFont = Font.regular(13.0)
let boldTextFont = Font.semibold(13.0)
let textColor = UIColor.white
let linkColor = UIColor.white
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.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme)
}
//TODO:localize
let learnMoreAttributedString = parseMarkdownIntoAttributedString("Learn more about wearing Telegram Gifts >", attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
if let range = learnMoreAttributedString.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 {
learnMoreAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: learnMoreAttributedString.string))
}
let learnMore = learnMore.update(
component: PlainButtonComponent(
content: AnyComponent(BalancedTextComponent(
text: .plain(learnMoreAttributedString),
maximumNumberOfLines: 1,
tintColor: secondaryTextColor
)),
action: {
let controller = component.context.sharedContext.makeGiftWearPreviewScreen(context: component.context, gift: component.auctionContext.gift, attributes: component.attributes)
environment.controller()?.push(controller)
},
animateScale: false
),
availableSize: context.availableSize,
transition: context.transition
)
context.add(learnMore
.position(CGPoint(x: context.availableSize.width / 2.0, y: 198.0))
)
if case let .generic(gift) = component.auctionContext.gift {
let initialGift = initialGift.update(
component: GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
subject: .starGift(gift: gift, price: ""),
ribbon: GiftItemComponent.Ribbon(text: "limited", color: .blue),
mode: .thumbnail
),
availableSize: CGSize(width: 120.0, height: 120.0),
transition: context.transition
)
context.add(initialGift
.position(CGPoint(x: sideInset + initialGift.size.width * 0.5, y: contentHeight + 76.0))
)
}
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
for attribute in attributes {
if case let .backdrop(_, _, innerColor, outerColor, _, _, _) = attribute {
ribbonColor = .custom(outerColor, innerColor)
break
}
}
let upgradedGift = upgradedGift.update(
component: GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
subject: .preview(attributes: attributes, rarity: 0),
ribbon: GiftItemComponent.Ribbon(text: "upgraded", color: ribbonColor),
animateChanges: true,
mode: .thumbnail
),
availableSize: CGSize(width: 120.0, height: 120.0),
transition: context.transition
)
context.add(upgradedGift
.position(CGPoint(x: context.availableSize.width - sideInset - upgradedGift.size.width * 0.5, y: contentHeight + 76.0))
)
let arrow = arrow.update(
component: BundleIconComponent(name: "Premium/Auction/Upgrade", tintColor: theme.list.itemSecondaryTextColor),
availableSize: context.availableSize,
transition: context.transition
)
context.add(arrow
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + 56.0))
)
let upgradeLabel = upgradeLabel.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(string: "Free\nUpgrade", font: Font.medium(13.0), textColor: theme.list.itemSecondaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 2,
lineSpacing: 0.1
),
availableSize: context.availableSize,
transition: context.transition
)
context.add(upgradeLabel
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + 94.0))
)
contentHeight += 137.0
if case let .generic(gift) = component.auctionContext.gift, let availability = gift.availability {
let remains: Int32 = availability.remains
// if let auctionState = component.auctionContext.currentState {
// switch auctionState.auctionState {
// case let .ongoing(_, _, _, _, _, _, _, giftsLeft, _, _, _, _):
// remains = giftsLeft
// case .finished:
// remains = 0
// }
// }
let total: Int32 = availability.total
let position = CGFloat(remains) / CGFloat(total)
let remainingCount = remainingCount.update(
component: GiftRemainingCountComponent(
inactiveColor: theme.list.itemBlocksBackgroundColor,
activeColors: [UIColor(rgb: 0x72d6ff), UIColor(rgb: 0x32a0f9)],
inactiveTitle: strings.Gift_Send_Remains(remains),
inactiveValue: "",
inactiveTitleColor: theme.list.itemSecondaryTextColor,
activeTitle: "",
activeValue: "",
activeTitleColor: .white,
badgeText: "",
badgePosition: position,
badgeGraphPosition: position,
invertProgress: true,
leftString: "",
groupingSeparator: environment.dateTimeFormat.groupingSeparator
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height),
transition: context.transition
)
context.add(remainingCount
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight))
)
if let giftsPerRound = gift.auctionGiftsPerRound {
let footerAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.list.freeTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.list.freeTextColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.list.itemAccentColor),
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
let parsedString = parseMarkdownIntoAttributedString(strings.Gift_Setup_AuctionInfo(environment.strings.Gift_Setup_AuctionInfo_Gifts(giftsPerRound), strings.Gift_Setup_AuctionInfo_Bidders(giftsPerRound)).string, attributes: footerAttributes)
let auctionFooterText = NSMutableAttributedString(attributedString: parsedString)
if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: theme.list.itemAccentColor)!, environment.theme)
}
if let range = auctionFooterText.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 {
auctionFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: auctionFooterText.string))
}
let auctionFooter = auctionFooter.update(
component: MultilineTextComponent(
text: .plain(auctionFooterText),
maximumNumberOfLines: 0,
highlightColor: 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: { _, _ in
guard let controller = controller() else {
return
}
let infoController = component.context.sharedContext.makeGiftAuctionInfoScreen(context: component.context, auctionContext: component.auctionContext, completion: nil)
controller.push(infoController)
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 16.0 * 2.0, height: 10000.0),
transition: context.transition
)
context.add(auctionFooter
.position(CGPoint(x: sideInset + 16.0 + auctionFooter.size.width * 0.5, y: contentHeight + 52.0 + auctionFooter.size.height * 0.5))
)
contentHeight += auctionFooter.size.height
}
}
contentHeight += 80.0
var buttonTitle = strings.Gift_Auction_Join
if component.auctionContext.isUpcoming {
buttonTitle = strings.Gift_Auction_EarlyBid
}
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 button = button.update(
component: ButtonComponent(
background: buttonBackground,
content: AnyComponentWithIdentity(
id: AnyHashable("ok"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
),
isEnabled: true,
displaysProgress: false,
action: {
if let controller = controller() as? GiftAuctionWearPreviewScreen {
controller.completion()
controller.dismissAnimated()
}
}),
availableSize: buttonSize,
transition: context.transition
)
let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: button.size)
context.add(button
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
)
contentHeight += button.size.height
contentHeight += 7.0
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
return CGSize(width: context.availableSize.width, height: contentHeight + 5.0 + effectiveBottomInset)
}
}
}
final class GiftAuctionWearPreviewSheetComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let auctionContext: GiftAuctionContext
let attributes: [StarGift.UniqueGift.Attribute]
init(
context: AccountContext,
auctionContext: GiftAuctionContext,
attributes: [StarGift.UniqueGift.Attribute]
) {
self.context = context
self.auctionContext = auctionContext
self.attributes = attributes
}
static func ==(lhs: GiftAuctionWearPreviewSheetComponent, rhs: GiftAuctionWearPreviewSheetComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
static var body: Body {
let sheet = Child(SheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let theme = environment.theme.withModalBlocksBackground()
let sheet = sheet.update(
component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(GiftAuctionWearPreviewSheetContent(
context: context.component.context,
auctionContext: context.component.auctionContext,
attributes: context.component.attributes,
animateOut: animateOut,
getController: controller
)),
style: .glass,
backgroundColor: .color(theme.list.blocksBackgroundColor),
followContentSizeChanges: true,
clipsContent: true,
hasDimView: false,
autoAnimateOut: false,
externalState: sheetExternalState,
animateOut: animateOut,
onPan: {
},
willDismiss: {
if let controller = controller() as? GiftAuctionWearPreviewScreen {
controller.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.3).withUserData(ViewControllerComponentContainer.AnimateOutTransition()))
}
}
),
environment: {
environment
SheetComponentEnvironment(
isDisplaying: environment.value.isVisible,
isCentered: environment.metrics.widthClass == .regular,
hasInputHeight: !environment.inputHeight.isZero,
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
dismiss: { animated in
if animated {
if let controller = controller() as? GiftAuctionWearPreviewScreen {
controller.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.3).withUserData(ViewControllerComponentContainer.AnimateOutTransition()))
animateOut.invoke(Action { _ in
controller.dismiss(completion: nil)
})
}
} else {
if let controller = controller() as? GiftAuctionWearPreviewScreen {
controller.dismiss(completion: nil)
}
}
}
)
},
availableSize: context.availableSize,
transition: context.transition
)
context.add(sheet
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
var sideInset: CGFloat = 0.0
var bottomInset: CGFloat = max(environment.safeInsets.bottom, sheetExternalState.contentHeight)
if case .regular = environment.metrics.widthClass {
sideInset = floor((context.availableSize.width - 430.0) / 2.0) - 12.0
bottomInset = (context.availableSize.height - sheetExternalState.contentHeight) / 2.0 + sheetExternalState.contentHeight
}
let layout = ContainerViewLayout(
size: context.availableSize,
metrics: environment.metrics,
deviceMetrics: environment.deviceMetrics,
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0),
safeInsets: UIEdgeInsets(top: 0.0, left: max(sideInset, environment.safeInsets.left), bottom: 0.0, right: max(sideInset, environment.safeInsets.right)),
additionalInsets: .zero,
statusBarHeight: environment.statusBarHeight,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
)
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
}
return context.availableSize
}
}
}
public class GiftAuctionWearPreviewScreen: ViewControllerComponentContainer {
private let context: AccountContext
fileprivate let completion: () -> Void
public init(
context: AccountContext,
auctionContext: GiftAuctionContext,
attributes: [StarGift.UniqueGift.Attribute],
completion: @escaping () -> Void
) {
self.context = context
self.completion = completion
super.init(
context: context,
component: GiftAuctionWearPreviewSheetComponent(context: context, auctionContext: auctionContext, attributes: attributes),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
theme: .default
)
self.navigationPresentation = .flatModal
self.automaticallyControlPresentationContextLayout = false
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
self.view.disablesInteractiveModalDismiss = true
}
public func dismissAnimated() {
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
view.dismissAnimated()
}
}
}

View file

@ -103,10 +103,9 @@ private final class GiftUpgradePreviewScreenComponent: Component {
private let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
private var previewTimer: SwiftSignalKit.Timer?
private var isPlaying = true
private var showRandomizeTip = false
private var previewTimer: SwiftSignalKit.Timer?
private var previewModelIndex: Int = 0
private var previewBackdropIndex: Int = 0
private var previewSymbolIndex: Int = 0
@ -272,7 +271,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
}
private func timerTick() {
private func previewTimerTick() {
guard !self.previewModels.isEmpty else { return }
self.previewModelIndex = (self.previewModelIndex + 1) % self.previewModels.count
@ -299,7 +298,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
guard let self else {
return
}
self.timerTick()
self.previewTimerTick()
}, queue: Queue.mainQueue())
self.previewTimer?.start()
} else {
@ -309,15 +308,11 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}
private var effectiveGifts: [[StarGift.UniqueGift.Attribute]] = []
private func updateEffectiveGifts() {
guard let component = self.component else {
return
}
private func updateEffectiveGifts(attributes: [StarGift.UniqueGift.Attribute]) {
var effectiveGifts: [[StarGift.UniqueGift.Attribute]] = []
switch self.selectedSection {
case .models:
let models = Array(component.attributes.filter({ attribute in
let models = Array(attributes.filter({ attribute in
if case .model = attribute {
return true
} else {
@ -330,7 +325,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
case .backdrops:
let selectedModel = self.selectedModel ?? self.previewModels[self.previewModelIndex]
let selectedSymbol = self.selectedSymbol ?? self.previewSymbols[self.previewSymbolIndex]
let backdrops = Array(component.attributes.filter({ attribute in
let backdrops = Array(attributes.filter({ attribute in
if case .backdrop = attribute {
return true
} else {
@ -346,7 +341,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}
case .symbols:
let selectedBackdrop = self.selectedBackdrop ?? self.previewBackdrops[self.previewBackdropIndex]
let symbols = Array(component.attributes.filter({ attribute in
let symbols = Array(attributes.filter({ attribute in
if case .pattern = attribute {
return true
} else {
@ -568,7 +563,6 @@ private final class GiftUpgradePreviewScreenComponent: Component {
let sideInset: CGFloat = rawSideInset + 16.0
if self.component == nil {
var modelCount: Int32 = 0
var backdropCount: Int32 = 0
var symbolCount: Int32 = 0
@ -615,7 +609,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}).shuffled().prefix(15))
self.previewSymbols = randomSymbols
self.updateEffectiveGifts()
self.updateEffectiveGifts(attributes: component.attributes)
}
self.component = component
@ -671,7 +665,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
let topColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04)
let bottomColor = UIColor(rgb: UInt32(bitPattern: outerColor)).withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8).withMultipliedBrightnessBy(1.25)
secondaryTextColor = topColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
}
@ -686,6 +680,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
animationOffset: CGPoint(x: 0.0, y: 20.0),
animationScale: nil,
displayAnimationStars: false,
alwaysAnimateTransition: true,
revealedAttributes: Set(),
externalState: self.giftCompositionExternalState,
requestUpdate: { [weak state] transition in
@ -803,13 +798,13 @@ private final class GiftUpgradePreviewScreenComponent: Component {
],
selectedId: "models",
action: { [weak self] id in
guard let self, let id = id.base as? SelectedSection else {
guard let self, let component = self.component, let id = id.base as? SelectedSection else {
return
}
self.selectedSection = id
self.isPlaying = false
self.updateEffectiveGifts()
self.updateEffectiveGifts(attributes: component.attributes)
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))) //.withUserData(AnimationHint(value: .modeChanged)))
})),
environment: {},
@ -950,10 +945,10 @@ private final class GiftUpgradePreviewScreenComponent: Component {
self.glassContainerView.frame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: 64.0))
let closeButtonSize = self.closeButton.update(
transition: .immediate,
transition: transition,
component: AnyComponent(GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: secondaryTextColor,
backgroundColor: buttonColor,
isDark: false,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
@ -1009,7 +1004,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
transition: transition,
component: AnyComponent(GlassBarButtonComponent(
size: playbackSize,
backgroundColor: secondaryTextColor,
backgroundColor: buttonColor,
isDark: false,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "content", component: AnyComponent(
@ -1036,7 +1031,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
self.showRandomizeTip = false
self.timerTick()
self.previewTimerTick()
}
self.state?.updated(transition: .easeInOut(duration: 0.25))
}

View file

@ -19,17 +19,26 @@ final class PasskeysScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let displaySkip: Bool
let initialPasskeysData: [TelegramPasskey]?
let passkeysDataUpdated: ([TelegramPasskey]) -> Void
let completion: () -> Void
let cancel: () -> Void
init(
context: AccountContext,
displaySkip: Bool,
initialPasskeysData: [TelegramPasskey]?,
passkeysDataUpdated: @escaping ([TelegramPasskey]) -> Void
passkeysDataUpdated: @escaping ([TelegramPasskey]) -> Void,
completion: @escaping () -> Void,
cancel: @escaping () -> Void
) {
self.context = context
self.displaySkip = displaySkip
self.initialPasskeysData = initialPasskeysData
self.passkeysDataUpdated = passkeysDataUpdated
self.completion = completion
self.cancel = cancel
}
static func ==(lhs: PasskeysScreenComponent, rhs: PasskeysScreenComponent) -> Bool {
@ -39,6 +48,7 @@ final class PasskeysScreenComponent: Component {
class View: UIView, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
private var intro: ComponentView<Empty>?
private var list: ComponentView<Empty>?
private var activityIndicator: UIActivityIndicatorView?
private var component: PasskeysScreenComponent?
private var environment: EnvironmentType?
@ -159,6 +169,8 @@ final class PasskeysScreenComponent: Component {
authController.delegate = self
authController.presentationContextProvider = self
authController.performRequests()
component.completion()
}
}
}
@ -168,9 +180,8 @@ final class PasskeysScreenComponent: Component {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
//TODO:localize
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Passkey?", text: "Once deleted, this passkey can't be used to log in.\n\nDon't forget to remove it from your password manager too.", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}), TextAlertAction(type: .destructiveAction, title: "Delete", action: { [weak self] in
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: environment.strings.Passkeys_DeleteAlert_Title, text: environment.strings.Passkeys_DeleteAlert_Text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {
}), TextAlertAction(type: .destructiveAction, title: environment.strings.Passkeys_DeleteAlert_Action, action: { [weak self] in
guard let self else {
return
}
@ -254,12 +265,21 @@ final class PasskeysScreenComponent: Component {
component: AnyComponent(PasskeysScreenIntroComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
insets: UIEdgeInsets(top: environment.statusBarHeight + environment.navigationHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0),
displaySkip: component.displaySkip,
createPasskeyAction: { [weak self] in
guard let self else {
return
}
self.createPasskey()
},
skipAction: { [weak self] in
guard let self, let component = self.component else {
return
}
component.cancel()
self.environment?.controller()?.dismiss()
}
)),
environment: {},
@ -337,6 +357,27 @@ final class PasskeysScreenComponent: Component {
}
}
if self.passkeysData == nil {
let activityIndicator: UIActivityIndicatorView
if let current = self.activityIndicator {
activityIndicator = current
} else {
activityIndicator = UIActivityIndicatorView(style: .large)
self.activityIndicator = activityIndicator
self.addSubview(activityIndicator)
}
activityIndicator.tintColor = environment.theme.list.itemPrimaryTextColor
let indicatorSize = activityIndicator.bounds.size
activityIndicator.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - indicatorSize.width) / 2.0), y: floor((availableSize.height - indicatorSize.height) / 2.0)), size: indicatorSize)
if !activityIndicator.isAnimating {
activityIndicator.startAnimating()
}
} else if let activityIndicator = self.activityIndicator {
self.activityIndicator = nil
activityIndicator.removeFromSuperview()
}
return availableSize
}
}
@ -353,10 +394,10 @@ final class PasskeysScreenComponent: Component {
public final class PasskeysScreen: ViewControllerComponentContainer {
private let context: AccountContext
public init(context: AccountContext, initialPasskeysData: [TelegramPasskey]?, passkeysDataUpdated: @escaping ([TelegramPasskey]) -> Void) async {
public init(context: AccountContext, displaySkip: Bool, initialPasskeysData: [TelegramPasskey]?, passkeysDataUpdated: @escaping ([TelegramPasskey]) -> Void, completion: @escaping () -> Void, cancel: @escaping () -> Void) {
self.context = context
super.init(context: context, component: PasskeysScreenComponent(context: context, initialPasskeysData: initialPasskeysData, passkeysDataUpdated: passkeysDataUpdated), navigationBarAppearance: .transparent)
super.init(context: context, component: PasskeysScreenComponent(context: context, displaySkip: displaySkip, initialPasskeysData: initialPasskeysData, passkeysDataUpdated: passkeysDataUpdated, completion: completion, cancel: cancel), navigationBarAppearance: .transparent)
}
required public init(coder aDecoder: NSCoder) {

View file

@ -13,19 +13,28 @@ import BundleIconComponent
final class PasskeysScreenIntroComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let insets: UIEdgeInsets
let displaySkip: Bool
let createPasskeyAction: () -> Void
let skipAction: () -> Void
init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
insets: UIEdgeInsets,
createPasskeyAction: @escaping () -> Void
displaySkip: Bool,
createPasskeyAction: @escaping () -> Void,
skipAction: @escaping () -> Void
) {
self.context = context
self.theme = theme
self.strings = strings
self.insets = insets
self.displaySkip = displaySkip
self.createPasskeyAction = createPasskeyAction
self.skipAction = skipAction
}
static func ==(lhs: PasskeysScreenIntroComponent, rhs: PasskeysScreenIntroComponent) -> Bool {
@ -35,9 +44,15 @@ final class PasskeysScreenIntroComponent: Component {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.insets != rhs.insets {
return false
}
if lhs.displaySkip != rhs.displaySkip {
return false
}
return true
}
@ -64,6 +79,7 @@ final class PasskeysScreenIntroComponent: Component {
private let title = ComponentView<Empty>()
private let subtitle = ComponentView<Empty>()
private let actionButton = ComponentView<Empty>()
private var skipButton: ComponentView<Empty>?
private var items: [Item] = []
@ -110,7 +126,6 @@ final class PasskeysScreenIntroComponent: Component {
var contentHeight: CGFloat = 0.0
//TODO:localize
let iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(LottieComponent(
@ -135,7 +150,7 @@ final class PasskeysScreenIntroComponent: Component {
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Protect your account", font: Font.bold(27.0), textColor: component.theme.list.itemPrimaryTextColor)),
text: .plain(NSAttributedString(string: component.strings.Passkeys_Into_Title, font: Font.bold(27.0), textColor: component.theme.list.itemPrimaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
@ -156,7 +171,7 @@ final class PasskeysScreenIntroComponent: Component {
let subtitleSize = self.subtitle.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(NSAttributedString(string: "Log in safely and keep your account secure.", font: Font.regular(16.0), textColor: component.theme.list.itemPrimaryTextColor)),
text: .plain(NSAttributedString(string: component.strings.Passkeys_Subtitle, font: Font.regular(16.0), textColor: component.theme.list.itemPrimaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
@ -183,18 +198,18 @@ final class PasskeysScreenIntroComponent: Component {
let itemDescs: [ItemDesc] = [
ItemDesc(
icon: "Settings/Passkeys/Intro1",
title: "Create a Passkey",
text: "Make a passkey to sign in easily and safely."
title: component.strings.Passkeys_Into_Title0,
text: component.strings.Passkeys_Into_Text0
),
ItemDesc(
icon: "Settings/Passkeys/Intro2",
title: "Log in with Face ID",
text: "Use Face ID, Touch ID, or your passcode to sign in."
title: component.strings.Passkeys_Into_Title1,
text: component.strings.Passkeys_Into_Text1
),
ItemDesc(
icon: "Settings/Passkeys/Intro3",
title: "Store Passkey Securely",
text: "Your passkey is safely kept in your iCloud Keychain."
title: component.strings.Passkeys_Into_Title2,
text: component.strings.Passkeys_Into_Text2
)
]
for i in 0 ..< itemDescs.count {
@ -280,7 +295,7 @@ final class PasskeysScreenIntroComponent: Component {
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Create Passkey", font: Font.semibold(17.0), textColor: component.theme.list.itemCheckColors.foregroundColor))))
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: component.strings.Passkeys_ButtonCreate, font: Font.semibold(17.0), textColor: component.theme.list.itemCheckColors.foregroundColor))))
),
action: { [weak self] in
guard let self, let component = self.component else {
@ -292,8 +307,55 @@ final class PasskeysScreenIntroComponent: Component {
environment: {},
containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0)
)
var skipButtonSize: CGSize?
if component.displaySkip {
let skipButton: ComponentView<Empty>
if let current = self.skipButton {
skipButton = current
} else {
skipButton = ComponentView()
self.skipButton = skipButton
}
skipButtonSize = skipButton.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
style: .glass,
color: component.theme.chatList.unreadBadgeInactiveBackgroundColor,
foreground: component.theme.list.itemCheckColors.foregroundColor,
pressedColor: component.theme.chatList.unreadBadgeInactiveBackgroundColor.withMultipliedAlpha(0.9),
cornerRadius: 52.0 * 0.5
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: component.strings.Passkeys_ButtonSkip, font: Font.semibold(17.0), textColor: component.theme.list.itemCheckColors.foregroundColor))))
),
action: { [weak self] in
guard let self, let component = self.component else {
return
}
component.skipAction()
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0)
)
} else if let skipButton = self.skipButton {
self.skipButton = nil
skipButton.view?.removeFromSuperview()
}
let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: availableSize.height - buttonInsets.bottom - actionButtonSize.height), size: actionButtonSize)
var buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: availableSize.height - buttonInsets.bottom - actionButtonSize.height), size: actionButtonSize)
if let skipButtonSize, let skipButtonView = self.skipButton?.view {
let skipButtonFrame = buttonFrame
buttonFrame = buttonFrame.offsetBy(dx: 0.0, dy: -8.0 - skipButtonSize.height)
if skipButtonView.superview == nil {
self.addSubview(skipButtonView)
}
transition.setFrame(view: skipButtonView, frame: skipButtonFrame)
}
if let actionButtonView = self.actionButton.view {
if actionButtonView.superview == nil {

View file

@ -123,7 +123,6 @@ final class PasskeysScreenListComponent: Component {
contentHeight += component.insets.top
contentHeight += 8.0
//TODO:localize
let iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(LottieComponent(
@ -148,7 +147,7 @@ final class PasskeysScreenListComponent: Component {
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Passkeys", font: Font.bold(27.0), textColor: component.theme.list.itemPrimaryTextColor)),
text: .plain(NSAttributedString(string: component.strings.Passkeys_Title, font: Font.bold(27.0), textColor: component.theme.list.itemPrimaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
@ -169,7 +168,7 @@ final class PasskeysScreenListComponent: Component {
let subtitleSize = self.subtitle.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(NSAttributedString(string: "Log in safely and keep your account secure.", font: Font.regular(16.0), textColor: component.theme.list.itemPrimaryTextColor)),
text: .plain(NSAttributedString(string: component.strings.Passkeys_Subtitle, font: Font.regular(16.0), textColor: component.theme.list.itemPrimaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
@ -229,11 +228,12 @@ final class PasskeysScreenListComponent: Component {
)
}
//TODO:localize
var subtitleString = "created \(dateString)"
let subtitleString: String
if let lastUsageDate = passkey.lastUsageDate {
let lastUsedDateString = dateFormatter.string(from: Date(timeIntervalSince1970: Double(lastUsageDate)))
subtitleString.append(" • used \(lastUsedDateString)")
subtitleString = component.strings.Passkeys_PasskeyCreatedAndUsedPattern(dateString, lastUsedDateString).string
} else {
subtitleString = component.strings.Passkeys_PasskeyCreatedPattern(dateString).string
}
listSectionItems.append(AnyComponentWithIdentity(id: passkey.id, component: AnyComponent(ListActionItemComponent(
@ -241,7 +241,7 @@ final class PasskeysScreenListComponent: Component {
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: passkey.name.isEmpty ? "Passkey" : passkey.name, //TODO:localize
string: passkey.name.isEmpty ? component.strings.Passkeys_EmptyName : passkey.name,
font: Font.regular(17.0),
textColor: component.theme.list.itemPrimaryTextColor
)),
@ -283,7 +283,7 @@ final class PasskeysScreenListComponent: Component {
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Create Passkey", //TODO:localize
string: component.strings.Passkeys_AddPasskey,
font: Font.regular(17.0),
textColor: component.theme.list.itemAccentColor
)),
@ -305,7 +305,6 @@ final class PasskeysScreenListComponent: Component {
))))
}
//TODO:localize
let listSectionSize = self.listSection.update(
transition: transition,
component: AnyComponent(ListSectionComponent(
@ -314,7 +313,7 @@ final class PasskeysScreenListComponent: Component {
header: nil,
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Your passkeys are stored securely in your password manager.",
string: component.strings.Passkeys_ListFooter,
font: Font.regular(13.0),
textColor: component.theme.list.freeTextColor
)),

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "arrow (7).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -1515,15 +1515,31 @@ func openResolvedUrlImpl(
let controller = context.sharedContext.makeGiftAuctionViewScreen(
context: context,
auctionContext: auctionContext,
completion: { [weak navigationController] acquiredGifts in
let controller = GiftSetupScreen(
context: context,
peerId: context.account.peerId,
subject: .starGift(gift, nil),
auctionAcquiredGifts: acquiredGifts,
completion: nil
)
navigationController?.pushViewController(controller)
completion: { [weak navigationController] acquiredGifts, upgradeAttributes in
if let upgradeAttributes {
let controller = context.sharedContext.makeGiftAuctionWearPreviewScreen(context: context, auctionContext: auctionContext, acquiredGifts: acquiredGifts, attributes: upgradeAttributes, completion: {
let controller = context.sharedContext.makeGiftAuctionBidScreen(
context: context,
toPeerId: context.account.peerId,
text: "",
entities: [],
hideName: true,
auctionContext: auctionContext,
acquiredGifts: acquiredGifts
)
navigationController?.pushViewController(controller)
})
navigationController?.pushViewController(controller)
} else {
let controller = GiftSetupScreen(
context: context,
peerId: context.account.peerId,
subject: .starGift(gift, nil),
auctionAcquiredGifts: acquiredGifts,
completion: nil
)
navigationController?.pushViewController(controller)
}
}
)
navigationController?.pushViewController(controller)

View file

@ -90,6 +90,7 @@ import ForumCreateTopicScreen
import GlassBackgroundComponent
import AttachmentFileController
import NewContactScreen
import PasskeysScreen
private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>()
@ -3855,7 +3856,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return GiftAuctionBidScreen(context: context, toPeerId: toPeerId, text: text, entities: entities, hideName: hideName, auctionContext: auctionContext, acquiredGifts: acquiredGifts)
}
public func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>) -> Void) -> ViewController {
public func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController {
return GiftAuctionViewScreen(context: context, auctionContext: auctionContext, completion: completion)
}
@ -3871,6 +3872,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return GiftUpgradePreviewScreen(context: context, gift: gift, attributes: attributes)
}
public func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController {
return GiftAuctionWearPreviewScreen(context: context, auctionContext: auctionContext, attributes: attributes, completion: completion)
}
public func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController {
let editorSubject: Signal<MediaEditorScreenImpl.Subject?, NoError>
switch subject {
@ -4063,6 +4068,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public func makeLoginEmailSetupController(context: AccountContext, blocking: Bool, emailPattern: String?, canAutoDismissIfNeeded: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController {
return loginEmailSetupController(context: context, blocking: blocking, emailPattern: emailPattern, canAutoDismissIfNeeded: canAutoDismissIfNeeded, navigationController: navigationController, completion: completion, dismiss: dismiss)
}
public func makePasskeySetupController(context: AccountContext, displaySkip: Bool, navigationController: NavigationController?, completion: @escaping () -> Void, dismiss: @escaping () -> Void) -> ViewController {
return PasskeysScreen(context: context, displaySkip: displaySkip, initialPasskeysData: nil, passkeysDataUpdated: { _ in }, completion: completion, cancel: dismiss)
}
}
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {