mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
6a0763f8fc
31 changed files with 1435 additions and 649 deletions
|
|
@ -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.";
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": [],
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/BotPaymentsUI",
|
||||
"//submodules/TelegramUI/Components/SegmentControlComponent",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)),
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Premium/Auction/Upgrade.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Auction/Upgrade.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "arrow (7).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Auction/Upgrade.imageset/arrow (7).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Auction/Upgrade.imageset/arrow (7).pdf
vendored
Normal file
Binary file not shown.
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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? {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue