mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '6db5077642' into experimental-3
This commit is contained in:
commit
1969b1b280
46 changed files with 2802 additions and 1203 deletions
|
|
@ -15506,3 +15506,120 @@ Error: %8$@";
|
|||
"Gift.Auction.Extension" = "+ %1$@ for late bids in top %2$@";
|
||||
|
||||
"ChatList.Auctions.UpcomingAuction" = "Upcoming Auction";
|
||||
|
||||
"Gift.AuctionBid.UpcomingBid" = "You've placed an early bid";
|
||||
"ChatList.Auctions.Status.UpcomingBid" = "You've placed an early bid.";
|
||||
"Gift.ActiveAuctions.UpcomingBid" = "You've 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";
|
||||
"Chat.GiftPurchaseOffer.RejectConfirmation.Title" = "Reject Offer";
|
||||
"Chat.GiftPurchaseOffer.RejectConfirmation.Text" = "Are you sure you want to reject the offer from **%@**?";
|
||||
"Chat.GiftPurchaseOffer.RejectConfirmation.Reject" = "Reject";
|
||||
|
||||
"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.";
|
||||
|
||||
"Gift.Demo.Title" = "Unique Collectibles";
|
||||
"Gift.Demo.Description" = "Telegram Gifts are collectible items you can trade or showcase on your profile.";
|
||||
"Gift.Demo.Unique.Title" = "Unique";
|
||||
"Gift.Demo.Unique.Text" = "Upgrade your gifts to get a unique number, model, backdrop and symbol.";
|
||||
"Gift.Demo.Tradable.Title" = "Tradable";
|
||||
"Gift.Demo.Tradable.Text" = "Sell your gift on Telegram or on third-party NFT marketplaces.";
|
||||
"Gift.Demo.Wearable.Title" = "Wearable";
|
||||
"Gift.Demo.Wearable.Text" = "Display gifts on your page and set them as profile covers or statuses.";
|
||||
"Gift.Demo.Understood" = "Understood";
|
||||
|
||||
"Gift.Variants.RandomTraits" = "Random Traits";
|
||||
"Gift.Variants.SelectedTraits" = "Selected Traits";
|
||||
"Gift.Variants.Randomize" = "Randomize Traits";
|
||||
"Gift.Variants.Model" = "model";
|
||||
"Gift.Variants.Backdrop" = "backdrop";
|
||||
"Gift.Variants.Symbol" = "symbol";
|
||||
"Gift.Variants.Models" = "Models";
|
||||
"Gift.Variants.Backdrops" = "Backdrops";
|
||||
"Gift.Variants.Symbols" = "Symbols";
|
||||
|
||||
"Gift.Variants.CollectionInfo" = "This collection features %@";
|
||||
"Gift.Variants.CollectionInfo.Model_1" = "**%@** unique model";
|
||||
"Gift.Variants.CollectionInfo.Model_any" = "**%@** unique models";
|
||||
"Gift.Variants.CollectionInfo.Backdrop_1" = "**%@** unique backdrop";
|
||||
"Gift.Variants.CollectionInfo.Backdrop_any" = "**%@** unique backdrops";
|
||||
"Gift.Variants.CollectionInfo.Symbol_1" = "**%@** unique symbol";
|
||||
"Gift.Variants.CollectionInfo.Symbol_any" = "**%@** unique symbols";
|
||||
|
||||
"Gift.Auction.GiftAuction" = "Gift Auction";
|
||||
"Gift.Auction.UpcomingAuction" = "Upcoming Auction";
|
||||
"Gift.Auction.LearnMore" = "Learn more about Telegram Gifts >";
|
||||
|
||||
"Gift.AuctionBid.UpcomingTitle" = "Place an Early Bid";
|
||||
|
||||
"Gift.View.Context.BuyOffer" = "Offer to Buy";
|
||||
|
||||
"Gift.Offer.Title" = "Offer to Buy";
|
||||
"Gift.Offer.OfferStars" = "Offer Stars";
|
||||
"Gift.Offer.OfferTon" = "Offer TON";
|
||||
"Gift.Offer.PriceSectionStars" = "STARS TO OFFER";
|
||||
"Gift.Offer.PriceSectionTon" = "TON TO OFFER";
|
||||
"Gift.Offer.PricePlaceholder" = "Price";
|
||||
"Gift.Offer.PriceDescriptionStars" = "Enter the number of Stars you'd like to offer for **%@**.";
|
||||
"Gift.Offer.PriceDescriptionTon" = "Enter the number of TON you'd like to offer for **%@**.";
|
||||
"Gift.Offer.Duration" = "Offer Duration";
|
||||
"Gift.Offer.DurationDescriptionStars" = "Choose how long **%@** can accept your offer. When the time expires, your Stars will be automatically refunded.";
|
||||
"Gift.Offer.DurationDescriptionTon" = "Choose how long **%@** can accept your offer. When the time expires, your TON will be automatically refunded.";
|
||||
"Gift.Offer.Duration.Hours_1" = "%@ Hour";
|
||||
"Gift.Offer.Duration.Hours_any" = "%@ Hours";
|
||||
"Gift.Offer.Offer" = "Offer";
|
||||
"Gift.Offer.GiftMinAmountToast.Text" = "You cannot offer less than %@ for this gift";
|
||||
|
||||
"Gift.WearPreview.Limited" = "limited";
|
||||
"Gift.WearPreview.Upgraded" = "upgraded";
|
||||
"Gift.WearPreview.FreeUpgrade" = "Free\nUpgrade";
|
||||
"Gift.WearPreview.LearnMore" = "Learn more about wearing Telegram Gifts >";
|
||||
|
||||
"Notification.StarGift.Sold" = "sold";
|
||||
"Notification.StarGiftOffer.Offer" = "**%1$@** offered you **%2$@** for your gift **%3$@**.";
|
||||
"Notification.StarGiftOffer.OfferYou" = "You offered **%1$@** **%2$@** for their gift **%3$@**.";
|
||||
"Notification.StarGiftOffer.Offer.Stars_1" = "%@ Star";
|
||||
"Notification.StarGiftOffer.Offer.Stars_any" = "%@ Stars";
|
||||
|
||||
"Notification.StarGiftOffer.Status.Accepted" = "This offer was accepted.";
|
||||
"Notification.StarGiftOffer.Status.Expired" = "This offer has expired.";
|
||||
"Notification.StarGiftOffer.Status.Expires" = "This offer expires in %@";
|
||||
"Notification.StarGiftOffer.Status.Rejected" = "This offer was rejected.";
|
||||
|
||||
"Notification.StarGiftOffer.Expiration.Hours_1" = "%@ h";
|
||||
"Notification.StarGiftOffer.Expiration.Hours_any" = "%@ h";
|
||||
"Notification.StarGiftOffer.Expiration.Minutes_1" = "%@ m";
|
||||
"Notification.StarGiftOffer.Expiration.Minutes_any" = "%@ m";
|
||||
"Notification.StarGiftOffer.Expiration.Delimiter" = "";
|
||||
|
||||
"Chat.GiftPurchaseOffer.AcceptConfirmation.Title" = "Confirm Sale";
|
||||
"Chat.GiftPurchaseOffer.AcceptConfirmation.Text" = "Do you want to sell **%1$@** to %2$@ for **%3$@**? You'll receive **%4$@** after fees.";
|
||||
"Chat.GiftPurchaseOffer.AcceptConfirmation.Text.Stars_1" = "%@ Star";
|
||||
"Chat.GiftPurchaseOffer.AcceptConfirmation.Text.Stars_any" = "%@ Stars";
|
||||
"Chat.GiftPurchaseOffer.AcceptConfirmation.BadValue" = "The value of this gift is **%@** higher than the offer.";
|
||||
"Chat.GiftPurchaseOffer.AcceptConfirmation.Confirm" = "Confirm Sale";
|
||||
|
||||
|
|
|
|||
|
|
@ -1273,6 +1273,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?)
|
||||
func openChatMessage(_ params: OpenChatMessageParams) -> Bool
|
||||
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tag: HistoryViewInputTag?) -> Signal<(MessageIndex?, Bool), NoError>
|
||||
|
||||
func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController
|
||||
func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController?
|
||||
func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController?
|
||||
|
|
@ -1356,54 +1357,34 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeInstantPageController(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController?
|
||||
func makeInstantPageController(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController
|
||||
func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void)
|
||||
|
||||
func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController
|
||||
|
||||
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController
|
||||
|
||||
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||
func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, proceed: (() -> Void)?) -> ViewController
|
||||
|
||||
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController
|
||||
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController
|
||||
|
||||
func makeStarsGiftController(context: AccountContext, birthdays: [EnginePeer.Id: TelegramBirthday]?, completion: @escaping (([EnginePeer.Id]) -> Void)) -> ViewController
|
||||
func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Signal<Never, TransferStarGiftError>)?) -> ViewController
|
||||
func makeGiftOptionsController(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption], hasBirthday: Bool, completion: (() -> Void)?) -> ViewController
|
||||
func makeGiftStoreController(context: AccountContext, peerId: EnginePeer.Id, gift: StarGift.Gift) -> ViewController
|
||||
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController
|
||||
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController
|
||||
|
||||
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], actionTitle: String?, isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, actionPerformed: ((Bool) -> Void)?) -> ViewController
|
||||
|
||||
func makeCameraScreen(context: AccountContext, mode: CameraScreenMode, cameraHolder: Any?, transitionIn: CameraScreenTransitionIn?, transitionOut: @escaping (Bool) -> CameraScreenTransitionOut?, completion: @escaping (Any, @escaping () -> Void) -> Void, transitionedOut: (() -> Void)?) -> ViewController
|
||||
|
||||
func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController
|
||||
|
||||
func makeStoryMediaEditorScreen(context: AccountContext, source: Any?, text: String?, link: (url: String, name: String?)?, remainingCount: Int32, completion: @escaping ([MediaEditorScreenResult], MediaEditorTransitionOutExternalState, @escaping (@escaping () -> Void) -> Void) -> Void) -> ViewController
|
||||
|
||||
func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, transitionOut: @escaping () -> BotPreviewEditorTransitionOut?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeStickerEditorScreen(context: AccountContext, source: Any?, intro: Bool, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (FileMediaReference) -> Void) -> ViewController
|
||||
|
||||
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController
|
||||
|
||||
func makeDataAndStorageController(context: AccountContext, sensitiveContent: Bool) -> ViewController
|
||||
|
||||
func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController
|
||||
|
||||
func makeChannelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: EnginePeer.Id, boosts: Bool, boostStatus: ChannelBoostStatus?) -> ViewController
|
||||
func makeMessagesStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, messageId: EngineMessage.Id) -> ViewController
|
||||
func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController
|
||||
|
||||
func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController
|
||||
func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, targetPeerId: EnginePeer.Id?, customTheme: PresentationTheme?, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, extendedMedia: [TelegramExtendedMedia], inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController
|
||||
|
|
@ -1426,37 +1407,28 @@ 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 makeGiftDemoScreen(context: AccountContext) -> ViewController
|
||||
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController
|
||||
|
||||
func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?)
|
||||
|
||||
func makeShareController(context: AccountContext, subject: ShareControllerSubject, forceExternal: Bool, shareStory: (() -> Void)?, enqueued: (([PeerId], [Int64]) -> Void)?, actionCompleted: (() -> Void)?) -> ViewController
|
||||
|
||||
func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal<MiniAppListScreenInitialData, NoError>
|
||||
func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController
|
||||
|
||||
func makeIncomingMessagePrivacyScreen(context: AccountContext, value: GlobalPrivacySettings.NonContactChatsPrivacy, exceptions: SelectivePrivacySettings, update: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void) -> ViewController
|
||||
|
||||
func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?, verifyAgeCompletion: ((Int) -> Void)?)
|
||||
|
||||
func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal<AffiliateProgramSetupScreenInitialData, NoError>
|
||||
func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController
|
||||
func makeAffiliateProgramJoinScreen(context: AccountContext, sourcePeer: EnginePeer, commissionPermille: Int32, programDuration: Int32?, revenuePerUser: Double, mode: JoinAffiliateProgramScreenMode) -> ViewController
|
||||
|
||||
func makeJoinSubjectScreen(context: AccountContext, mode: JoinSubjectScreenMode) -> ViewController
|
||||
|
||||
func makeOldChannelsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, intent: OldChannelsControllerIntent, completed: @escaping (Bool) -> Void) -> ViewController
|
||||
|
||||
func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController
|
||||
|
||||
func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
|
||||
func makeSendInviteLinkScreen(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme?) -> ViewController
|
||||
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
func makePostSuggestionsSettingsScreen(context: AccountContext, peerId: EnginePeer.Id) async -> ViewController
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -354,15 +354,15 @@ protocol PhoneDemoDecorationView: UIView {
|
|||
func resetAnimation()
|
||||
}
|
||||
|
||||
final class PhoneDemoComponent: Component {
|
||||
typealias EnvironmentType = DemoPageEnvironment
|
||||
public final class PhoneDemoComponent: Component {
|
||||
public typealias EnvironmentType = DemoPageEnvironment
|
||||
|
||||
enum Position {
|
||||
public enum Position {
|
||||
case top
|
||||
case bottom
|
||||
}
|
||||
|
||||
enum BackgroundDecoration {
|
||||
public enum BackgroundDecoration {
|
||||
case none
|
||||
case dataRain
|
||||
case swirlStars
|
||||
|
|
@ -375,7 +375,7 @@ final class PhoneDemoComponent: Component {
|
|||
case todo
|
||||
}
|
||||
|
||||
enum Model {
|
||||
public enum Model {
|
||||
case notch
|
||||
case island
|
||||
}
|
||||
|
|
@ -419,11 +419,11 @@ final class PhoneDemoComponent: Component {
|
|||
return true
|
||||
}
|
||||
|
||||
final class View: UIView, ComponentTaggedView {
|
||||
final class Tag {
|
||||
public final class View: UIView, ComponentTaggedView {
|
||||
public final class Tag {
|
||||
}
|
||||
|
||||
func matches(tag: Any) -> Bool {
|
||||
public func matches(tag: Any) -> Bool {
|
||||
if let _ = tag as? Tag, self.isCentral {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ public final class PremiumGradientBackgroundComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
final class DemoPageEnvironment: Equatable {
|
||||
public final class DemoPageEnvironment: Equatable {
|
||||
public let isDisplaying: Bool
|
||||
public let isCentral: Bool
|
||||
public let position: CGFloat
|
||||
|
|
@ -1134,13 +1134,13 @@ private final class DemoSheetContent: CombinedComponent {
|
|||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .glass,
|
||||
backgroundColor: UIColor(rgb: 0x7f76f4),
|
||||
isDark: false,
|
||||
state: .tintedGlass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor
|
||||
tintColor: .white
|
||||
)
|
||||
)),
|
||||
action: { _ in
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -114,8 +114,21 @@ final class GiftAuctionAccessoryPanel: ASDisplayNode {
|
|||
var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = []
|
||||
|
||||
if self.states.count == 1, let auctionState = self.states.first {
|
||||
var isUpcoming = false
|
||||
var startTime = currentTime
|
||||
var endTime = currentTime
|
||||
if case let .ongoing(_, startDate, _, _, _, _, nextRoundDate, _, _, _, _, _) = auctionState.auctionState {
|
||||
startTime = startDate
|
||||
endTime = nextRoundDate
|
||||
if currentTime < startDate {
|
||||
isUpcoming = true
|
||||
}
|
||||
}
|
||||
|
||||
let place = auctionState.place ?? 1
|
||||
if case let .generic(gift) = auctionState.gift, let auctionGiftsPerRound = gift.auctionGiftsPerRound, place > auctionGiftsPerRound {
|
||||
if isUpcoming {
|
||||
subtitleText = self.strings.ChatList_Auctions_Status_UpcomingBid
|
||||
} else if case let .generic(gift) = auctionState.gift, let auctionGiftsPerRound = gift.auctionGiftsPerRound, place > auctionGiftsPerRound {
|
||||
subtitleText = self.strings.ChatList_Auctions_Status_Single_Outbid
|
||||
subtitleTextColor = self.theme.list.itemDestructiveColor
|
||||
isOutbid = true
|
||||
|
|
@ -135,13 +148,6 @@ final class GiftAuctionAccessoryPanel: ASDisplayNode {
|
|||
subtitleText = self.strings.ChatList_Auctions_Status_Single_Winning(placeText).string
|
||||
}
|
||||
|
||||
var startTime = currentTime
|
||||
var endTime = currentTime
|
||||
if case let .ongoing(_, startDate, _, _, _, _, nextRoundDate, _, _, _, _, _) = auctionState.auctionState {
|
||||
startTime = startDate
|
||||
endTime = nextRoundDate
|
||||
}
|
||||
|
||||
let endTimeout: Int32
|
||||
if currentTime < startTime {
|
||||
endTimeout = max(0, startTime - currentTime)
|
||||
|
|
|
|||
|
|
@ -1075,6 +1075,15 @@ public extension StarGift {
|
|||
}
|
||||
}
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case let .generic(gift):
|
||||
return gift.title ?? ""
|
||||
case let .unique(gift):
|
||||
return gift.title
|
||||
}
|
||||
}
|
||||
|
||||
var releasedBy: EnginePeer.Id? {
|
||||
switch self {
|
||||
case let .generic(gift):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -508,6 +508,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode",
|
||||
"//submodules/TelegramUI/Components/Settings/PasskeysScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftDemoScreen",
|
||||
] + 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: [],
|
||||
|
|
|
|||
|
|
@ -514,6 +514,7 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
|||
}
|
||||
titleNode.frame = CGRect(origin: CGPoint(x: leftOffset, y: 0.0), size: titleLayout.size)
|
||||
|
||||
var nameFrame = CGRect()
|
||||
if let (nameLayout, nameApply) = nameLayoutAndApply {
|
||||
let nameNode = nameApply()
|
||||
if node.nameNode == nil {
|
||||
|
|
@ -521,7 +522,8 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
|||
node.nameNode = nameNode
|
||||
node.addSubnode(nameNode)
|
||||
}
|
||||
nameNode.frame = CGRect(origin: CGPoint(x: leftOffset + authorAvatarInset, y: titleLayout.size.height + titleAuthorSpacing), size: nameLayout.size)
|
||||
nameFrame = CGRect(origin: CGPoint(x: leftOffset + authorAvatarInset, y: titleLayout.size.height + titleAuthorSpacing), size: nameLayout.size)
|
||||
nameNode.frame = nameFrame
|
||||
|
||||
if authorAvatarInset != 0.0 {
|
||||
let avatarNode: AvatarNode
|
||||
|
|
@ -599,7 +601,7 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
|||
node.credibilityIconNode = credibilityIconNode
|
||||
node.addSubnode(credibilityIconNode)
|
||||
}
|
||||
credibilityIconNode.frame = CGRect(origin: CGPoint(x: titleLayout.size.width + 4.0, y: 16.0), size: credibilityIconImage.size)
|
||||
credibilityIconNode.frame = CGRect(origin: CGPoint(x: nameFrame.maxX + 4.0, y: 17.0), size: credibilityIconImage.size)
|
||||
credibilityIconNode.image = credibilityIconImage
|
||||
} else {
|
||||
node.credibilityIconNode?.removeFromSupernode()
|
||||
|
|
|
|||
|
|
@ -717,8 +717,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
}
|
||||
text = isStoryEntity ? "**\(item.presentationData.strings.Notification_StarGift_Collectible) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: item.presentationData.dateTimeFormat))**" : "**\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: item.presentationData.dateTimeFormat))**"
|
||||
if fromOffer {
|
||||
//TODO:localize
|
||||
ribbonTitle = incoming ? "" : "sold"
|
||||
ribbonTitle = incoming ? "" : item.presentationData.strings.Notification_StarGift_Sold
|
||||
customRibbonColors = [UIColor(rgb: 0xd9433a), UIColor(rgb: 0xff645b)]
|
||||
} else {
|
||||
ribbonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_Gift
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ public class ChatMessageGiftOfferBubbleContentNode: ChatMessageBubbleContentNode
|
|||
let priceString: String
|
||||
switch amount.currency {
|
||||
case .stars:
|
||||
priceString = "\(amount.amount) Stars"
|
||||
priceString = item.presentationData.strings.Notification_StarGiftOffer_Offer_Stars(Int32(clamping: amount.amount.value))
|
||||
case .ton:
|
||||
priceString = "\(amount.amount) TON"
|
||||
}
|
||||
|
|
@ -143,41 +143,35 @@ public class ChatMessageGiftOfferBubbleContentNode: ChatMessageBubbleContentNode
|
|||
giftTitle = ""
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
if incoming {
|
||||
text = "**\(peerName)** offered you **\(priceString)** for your gift **\(giftTitle)**."
|
||||
text = item.presentationData.strings.Notification_StarGiftOffer_Offer(peerName, priceString, giftTitle).string
|
||||
} else {
|
||||
text = "You offered **\(peerName)** **\(priceString)** for their gift **\(giftTitle)**."
|
||||
text = item.presentationData.strings.Notification_StarGiftOffer_OfferYou(peerName, priceString, giftTitle).string
|
||||
}
|
||||
|
||||
if isAccepted {
|
||||
additionalText = "This offer was accepted."
|
||||
additionalText = item.presentationData.strings.Notification_StarGiftOffer_Status_Accepted
|
||||
} else if isDeclined {
|
||||
additionalText = "This offer was rejected."
|
||||
additionalText = item.presentationData.strings.Notification_StarGiftOffer_Status_Rejected
|
||||
} else if expireDate > currentTimestamp {
|
||||
func textForTimeout(_ value: Int32) -> String {
|
||||
if value < 3600 {
|
||||
let minutes = value / 60
|
||||
//let seconds = value % 60
|
||||
//let secondsPadding = seconds < 10 ? "0" : ""
|
||||
return "\(minutes)m" //\(secondsPadding)\(seconds)"
|
||||
return item.presentationData.strings.Notification_StarGiftOffer_Expiration_Minutes(minutes)
|
||||
} else {
|
||||
let hours = value / 3600
|
||||
let minutes = (value % 3600) / 60
|
||||
let minutesPadding = minutes < 10 ? "0" : ""
|
||||
//let seconds = value % 60
|
||||
//let secondsPadding = seconds < 10 ? "0" : ""
|
||||
return "\(hours)h \(minutesPadding)\(minutes)m" //:\(secondsPadding)\(seconds)"
|
||||
return item.presentationData.strings.Notification_StarGiftOffer_Expiration_Hours(hours) + item.presentationData.strings.Notification_StarGiftOffer_Expiration_Delimiter + item.presentationData.strings.Notification_StarGiftOffer_Expiration_Minutes(minutes)
|
||||
}
|
||||
}
|
||||
let delta = expireDate - currentTimestamp
|
||||
additionalText = "This offer expires in \(textForTimeout(delta))."
|
||||
|
||||
additionalText = item.presentationData.strings.Notification_StarGiftOffer_Status_Expires(textForTimeout(delta)).string
|
||||
|
||||
if incoming {
|
||||
hasActionButtons = true
|
||||
}
|
||||
} else {
|
||||
additionalText = "This offer has expired."
|
||||
additionalText = item.presentationData.strings.Notification_StarGiftOffer_Status_Expired
|
||||
}
|
||||
} else {
|
||||
text = ""
|
||||
|
|
@ -277,8 +271,6 @@ public class ChatMessageGiftOfferBubbleContentNode: ChatMessageBubbleContentNode
|
|||
)
|
||||
if let giftIconView = strongSelf.giftIcon.view {
|
||||
if giftIconView.superview == nil {
|
||||
// backgroundView.layer.cornerRadius = 20.0
|
||||
//backgroundView.clipsToBounds = true
|
||||
strongSelf.view.addSubview(giftIconView)
|
||||
}
|
||||
giftIconView.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY + 17.0), size: iconSize)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
42
submodules/TelegramUI/Components/Gifts/GiftDemoScreen/BUILD
Normal file
42
submodules/TelegramUI/Components/Gifts/GiftDemoScreen/BUILD
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "GiftDemoScreen",
|
||||
module_name = "GiftDemoScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SheetComponent",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/ScrollComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/PremiumUI",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/Gifts/InfoParagraphComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,584 @@
|
|||
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 BalancedTextComponent
|
||||
import BundleIconComponent
|
||||
import ButtonComponent
|
||||
import Markdown
|
||||
import GlassBarButtonComponent
|
||||
import PremiumUI
|
||||
import ScrollComponent
|
||||
import LottieComponent
|
||||
import EdgeEffect
|
||||
import InfoParagraphComponent
|
||||
|
||||
final class PageComponent<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
typealias EnvironmentType = ChildEnvironment
|
||||
|
||||
private let content: AnyComponent<ChildEnvironment>
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let textColor: UIColor
|
||||
|
||||
init(
|
||||
content: AnyComponent<ChildEnvironment>,
|
||||
title: String,
|
||||
text: String,
|
||||
textColor: UIColor
|
||||
) {
|
||||
self.content = content
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.textColor = textColor
|
||||
}
|
||||
|
||||
static func ==(lhs: PageComponent<ChildEnvironment>, rhs: PageComponent<ChildEnvironment>) -> Bool {
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.textColor != rhs.textColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let children = ChildMap(environment: ChildEnvironment.self, keyedBy: AnyHashable.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let text = Child(MultilineTextComponent.self)
|
||||
|
||||
return { context in
|
||||
let availableSize = context.availableSize
|
||||
let component = context.component
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
let textSideInset: CGFloat = 24.0
|
||||
|
||||
let textColor = component.textColor
|
||||
let textFont = Font.regular(17.0)
|
||||
let boldTextFont = Font.semibold(17.0)
|
||||
|
||||
let content = children["main"].update(
|
||||
component: component.content,
|
||||
environment: {
|
||||
context.environment[ChildEnvironment.self]
|
||||
},
|
||||
availableSize: CGSize(width: availableSize.width, height: availableSize.width),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.title,
|
||||
font: boldTextFont,
|
||||
textColor: component.textColor,
|
||||
paragraphAlignment: .center
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let markdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
linkAttribute: { _ in
|
||||
return nil
|
||||
}
|
||||
)
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(text: component.text, attributes: markdownAttributes),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.0
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 40.0))
|
||||
)
|
||||
context.add(text
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 60.0 + text.size.height / 2.0))
|
||||
)
|
||||
context.add(content
|
||||
.position(CGPoint(x: content.size.width / 2.0, y: content.size.height / 2.0))
|
||||
)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class DemoSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let action: () -> Void
|
||||
let dismiss: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
action: @escaping () -> Void,
|
||||
dismiss: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.action = action
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
||||
static func ==(lhs: DemoSheetContent, rhs: DemoSheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
private let context: AccountContext
|
||||
|
||||
private var disposable: Disposable?
|
||||
private(set) var promoConfiguration: PremiumPromoConfiguration?
|
||||
|
||||
fileprivate let playButtonAnimation = ActionSlot<Void>()
|
||||
private var didPlayAnimation = false
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
||||
super.init()
|
||||
|
||||
self.disposable = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.PremiumPromo())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] promoConfiguration in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.promoConfiguration = promoConfiguration
|
||||
self.updated(transition: .immediate)
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
func playAnimationIfNeeded() {
|
||||
if !self.didPlayAnimation {
|
||||
self.didPlayAnimation = true
|
||||
self.playButtonAnimation.invoke(Void())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let closeButton = Child(GlassBarButtonComponent.self)
|
||||
let background = Child(PremiumGradientBackgroundComponent.self)
|
||||
let demo = Child(PhoneDemoComponent.self)
|
||||
let scroll = Child(ScrollComponent<Empty>.self)
|
||||
let bottomEdgeEffect = Child(EdgeEffectComponent.self)
|
||||
let button = Child(ButtonComponent.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let component = context.component
|
||||
let theme = environment.theme
|
||||
let strings = environment.strings
|
||||
|
||||
let state = context.state
|
||||
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: context.availableSize.width)
|
||||
|
||||
let remainingHeight = 365.0
|
||||
let scroll = scroll.update(
|
||||
component: ScrollComponent<Empty>(
|
||||
content: AnyComponent(
|
||||
GiftDemoListComponent(
|
||||
context: context.component.context,
|
||||
theme: environment.theme
|
||||
)
|
||||
),
|
||||
contentInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 80.0, right: 0.0),
|
||||
contentOffsetUpdated: { _, _ in },
|
||||
contentOffsetWillCommit: { _ in }
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: remainingHeight),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(scroll
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + scroll.size.height / 2.0))
|
||||
)
|
||||
|
||||
let background = background.update(
|
||||
component: PremiumGradientBackgroundComponent(colors: [
|
||||
UIColor(rgb: 0x0077ff),
|
||||
UIColor(rgb: 0x6b93ff),
|
||||
UIColor(rgb: 0x8878ff),
|
||||
UIColor(rgb: 0xe46ace)
|
||||
]),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.width),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(background
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0))
|
||||
)
|
||||
|
||||
let demo = demo.update(
|
||||
component: PhoneDemoComponent(
|
||||
context: component.context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: state.promoConfiguration?.videos["gifts"],
|
||||
decoration: .badgeStars
|
||||
),
|
||||
environment: { DemoPageEnvironment(isDisplaying: true, isCentral: true, position: 0.0) },
|
||||
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.width),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(demo
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: demo.size.height / 2.0))
|
||||
)
|
||||
|
||||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
backgroundColor: UIColor(rgb: 0x7f76f4),
|
||||
isDark: false,
|
||||
state: .tintedGlass,
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Close",
|
||||
tintColor: .white
|
||||
)
|
||||
)),
|
||||
action: { _ in
|
||||
component.dismiss()
|
||||
}
|
||||
),
|
||||
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))
|
||||
)
|
||||
|
||||
contentSize.height += remainingHeight
|
||||
|
||||
let bottomEdgeEffectHeight = 108.0
|
||||
let bottomEdgeEffect = bottomEdgeEffect.update(
|
||||
component: EdgeEffectComponent(
|
||||
color: .clear,
|
||||
blur: true,
|
||||
alpha: 1.0,
|
||||
size: CGSize(width: context.availableSize.width, height: bottomEdgeEffectHeight),
|
||||
edge: .bottom,
|
||||
edgeSize: bottomEdgeEffectHeight
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: bottomEdgeEffectHeight),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(bottomEdgeEffect
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height - bottomEdgeEffect.size.height / 2.0))
|
||||
)
|
||||
|
||||
var buttonTitle: [AnyComponentWithIdentity<Empty>] = []
|
||||
buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: "anim_ok"),
|
||||
color: theme.list.itemCheckColors.foregroundColor,
|
||||
startingPosition: .begin,
|
||||
size: CGSize(width: 28.0, height: 28.0),
|
||||
playOnce: state.playButtonAnimation
|
||||
))))
|
||||
buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent(
|
||||
text: strings.Gift_Demo_Understood,
|
||||
badge: 0,
|
||||
textColor: theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: theme.list.itemCheckColors.foregroundColor,
|
||||
badgeForeground: theme.list.itemCheckColors.fillColor
|
||||
))))
|
||||
let button = button.update(
|
||||
component: ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
|
||||
cornerRadius: 10.0,
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(HStack(buttonTitle, spacing: 2.0))
|
||||
),
|
||||
action: {
|
||||
component.dismiss()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - 30.0 * 2.0, height: 52.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(button
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height - button.size.height / 2.0 - 30.0))
|
||||
)
|
||||
|
||||
state.playAnimationIfNeeded()
|
||||
|
||||
return contentSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class DemoSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let action: () -> Void
|
||||
|
||||
init(context: AccountContext, action: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: DemoSheetComponent, rhs: DemoSheetComponent) -> 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)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
|
||||
let controller = environment.controller
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(DemoSheetContent(
|
||||
context: context.component.context,
|
||||
action: context.component.action,
|
||||
dismiss: {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
)),
|
||||
style: .glass,
|
||||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
animateOut: animateOut
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
SheetComponentEnvironment(
|
||||
isDisplaying: environment.value.isVisible,
|
||||
isCentered: environment.metrics.widthClass == .regular,
|
||||
hasInputHeight: !environment.inputHeight.isZero,
|
||||
regularMetricsSize: nil,
|
||||
dismiss: { animated in
|
||||
if animated {
|
||||
animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if let controller = controller() {
|
||||
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))
|
||||
)
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GiftDemoScreen: ViewControllerComponentContainer {
|
||||
public init(context: AccountContext, action: @escaping () -> Void = {}) {
|
||||
super.init(context: context, component: DemoSheetComponent(context: context, action: action), navigationBarAppearance: .none, theme: .default)
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class GiftDemoListComponent: CombinedComponent {
|
||||
typealias EnvironmentType = (Empty, ScrollChildEnvironment)
|
||||
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
}
|
||||
|
||||
static func ==(lhs: GiftDemoListComponent, rhs: GiftDemoListComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let description = Child(BalancedTextComponent.self)
|
||||
let list = Child(List<Empty>.self)
|
||||
|
||||
return { context in
|
||||
let theme = context.component.theme
|
||||
let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||
|
||||
let titleColor = theme.list.itemPrimaryTextColor
|
||||
let textColor = theme.list.itemSecondaryTextColor
|
||||
let iconColor = theme.list.itemAccentColor
|
||||
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 32.0)
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Demo_Title, font: Font.bold(25.0), textColor: titleColor))),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height * 0.5))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
contentSize.height += 9.0
|
||||
|
||||
let description = description.update(
|
||||
component: BalancedTextComponent(
|
||||
text: .plain(NSAttributedString(string: strings.Gift_Demo_Description, font: Font.regular(15.0), textColor: textColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(description
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + description.size.height * 0.5))
|
||||
)
|
||||
contentSize.height += description.size.height
|
||||
contentSize.height += 26.0
|
||||
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "unique",
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Demo_Unique_Title,
|
||||
titleColor: titleColor,
|
||||
text: strings.Gift_Demo_Unique_Text,
|
||||
textColor: textColor,
|
||||
accentColor: iconColor,
|
||||
iconName: "Premium/Collectible/Unique",
|
||||
iconColor: iconColor
|
||||
))
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "tradable",
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Demo_Tradable_Title,
|
||||
titleColor: titleColor,
|
||||
text: strings.Gift_Demo_Tradable_Text,
|
||||
textColor: textColor,
|
||||
accentColor: iconColor,
|
||||
iconName: "Premium/Collectible/Transferable",
|
||||
iconColor: iconColor
|
||||
))
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "wearable",
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Demo_Wearable_Title,
|
||||
titleColor: titleColor,
|
||||
text: strings.Gift_Demo_Wearable_Text,
|
||||
textColor: textColor,
|
||||
accentColor: iconColor,
|
||||
iconName: "Premium/Collectible/Tradable",
|
||||
iconColor: iconColor
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
let list = list.update(
|
||||
component: List(items),
|
||||
availableSize: CGSize(width: context.availableSize.width - 32.0 * 2.0, height: 10000.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(list
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += list.size.height
|
||||
contentSize.height += 88.0
|
||||
|
||||
return CGSize(width: context.availableSize.width, height: contentSize.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -589,7 +601,9 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
case let .preview(attributes, _):
|
||||
animationOffset = 16.0
|
||||
explicitAnimationOffset = -4.0
|
||||
if component.mode != .tableIcon {
|
||||
explicitAnimationOffset = -4.0
|
||||
}
|
||||
for attribute in attributes {
|
||||
switch attribute {
|
||||
case let .model(_, file, _):
|
||||
|
|
@ -636,12 +650,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 +688,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 +699,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 +720,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 +949,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 +1500,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 +1543,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 +1572,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 +1581,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 +1607,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 +1618,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 +1646,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
|
||||
|
|
@ -1718,14 +1719,14 @@ 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),
|
||||
inactiveValue: "",
|
||||
inactiveTitleColor: theme.list.itemSecondaryTextColor,
|
||||
activeTitle: "",
|
||||
activeValue: environment.strings.Gift_Send_Sold(sold),
|
||||
activeValue: sold > 0 ? environment.strings.Gift_Send_Sold(sold) : "",
|
||||
activeTitleColor: .white,
|
||||
badgeText: "",
|
||||
badgePosition: position,
|
||||
|
|
@ -1828,22 +1829,30 @@ private final class GiftSetupScreenComponent: Component {
|
|||
|
||||
var buttonTitleItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
if let _ = self.giftAuction {
|
||||
let buttonAttributedString = NSMutableAttributedString(string: environment.strings.Gift_Setup_PlaceBid, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
buttonTitleItems.append(AnyComponentWithIdentity(id: "bid", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(buttonAttributedString))
|
||||
)))
|
||||
var isUpcoming = false
|
||||
if let giftAuctionState = self.giftAuctionState {
|
||||
switch giftAuctionState.auctionState {
|
||||
case let .ongoing(_, _, endTime, _, _, _, _, _, _, _, _, _):
|
||||
case let .ongoing(_, startTime, endTime, _, _, _, _, _, _, _, _, _):
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
|
||||
let endTimeout = max(0, endTime - currentTime)
|
||||
let endTimeout: Int32
|
||||
if currentTime < startTime {
|
||||
endTimeout = max(0, startTime - currentTime)
|
||||
isUpcoming = true
|
||||
} else {
|
||||
endTimeout = max(0, endTime - currentTime)
|
||||
}
|
||||
|
||||
let hours = Int(endTimeout / 3600)
|
||||
let minutes = Int((endTimeout % 3600) / 60)
|
||||
let seconds = Int(endTimeout % 60)
|
||||
|
||||
let rawString = hours > 0 ? environment.strings.Gift_Auction_TimeLeftHours : environment.strings.Gift_Auction_TimeLeftMinutes
|
||||
let rawString: String
|
||||
if isUpcoming {
|
||||
rawString = hours > 0 ? environment.strings.Gift_Auction_StartsInHours : environment.strings.Gift_Auction_StartsInMinutes
|
||||
} else {
|
||||
rawString = hours > 0 ? environment.strings.Gift_Auction_TimeLeftHours : environment.strings.Gift_Auction_TimeLeftMinutes
|
||||
}
|
||||
|
||||
var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = []
|
||||
var startIndex = rawString.startIndex
|
||||
while true {
|
||||
|
|
@ -1883,6 +1892,10 @@ private final class GiftSetupScreenComponent: Component {
|
|||
buttonIsEnabled = false
|
||||
}
|
||||
}
|
||||
let buttonAttributedString = NSMutableAttributedString(string: isUpcoming ? environment.strings.Gift_Auction_EarlyBid : environment.strings.Gift_Setup_PlaceBid, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
buttonTitleItems.insert(AnyComponentWithIdentity(id: "bid", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(buttonAttributedString))
|
||||
)), at: 0)
|
||||
} else {
|
||||
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/BotPaymentsUI",
|
||||
"//submodules/TelegramUI/Components/SegmentControlComponent",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent",
|
||||
"//submodules/TelegramUI/Components/Gifts/InfoParagraphComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -663,11 +663,13 @@ private final class ActiveAuctionComponent: Component {
|
|||
var titleText: String = ""
|
||||
var subtitleText: String = ""
|
||||
var subtitleTextColor = component.theme.list.itemPrimaryTextColor
|
||||
if case let .ongoing(_, _, _, _, _, _, nextRoundDate, _, currentRound, totalRound, _, _) = component.state.auctionState, let myBid = component.state.myState.bidAmount {
|
||||
if case let .ongoing(_, startDate, _, _, _, _, nextRoundDate, _, currentRound, totalRound, _, _) = component.state.auctionState, let myBid = component.state.myState.bidAmount {
|
||||
titleText = component.strings.Gift_ActiveAuctions_Round("\(currentRound)", "\(totalRound)").string
|
||||
|
||||
let bidString = "#\(presentationStringsFormattedNumber(Int32(clamping: myBid), component.dateTimeFormat.groupingSeparator))"
|
||||
if let place = component.state.place, case let .generic(gift) = component.state.gift, let auctionGiftsPerRound = gift.auctionGiftsPerRound, place > auctionGiftsPerRound {
|
||||
if component.currentTime < startDate {
|
||||
subtitleText = component.strings.Gift_ActiveAuctions_UpcomingBid
|
||||
} else if let place = component.state.place, case let .generic(gift) = component.state.gift, let auctionGiftsPerRound = gift.auctionGiftsPerRound, place > auctionGiftsPerRound {
|
||||
subtitleText = component.strings.Gift_ActiveAuctions_Outbid(bidString).string
|
||||
subtitleTextColor = component.theme.list.itemDestructiveColor
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -1870,7 +1870,7 @@ private final class GiftAuctionBidScreenComponent: Component {
|
|||
maxBidAmount = max(maxBidAmount, Int64(Double(firstLevel.amount) * 1.5))
|
||||
}
|
||||
}
|
||||
var currentValue = max(Int(minBidAmount), 100)
|
||||
var currentValue = Int(minBidAmount)
|
||||
var minAllowedRealValue: Int64 = minBidAmount
|
||||
if let myBidAmount = state.myState.bidAmount {
|
||||
if let component, let bidPeerId = state.myState.bidPeerId, bidPeerId != component.toPeerId || forceMinimum, let myMinBidAmount = state.myState.minBidAmount {
|
||||
|
|
@ -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
|
||||
|
|
@ -2486,7 +2493,19 @@ private final class GiftAuctionBidScreenComponent: Component {
|
|||
untilNextRoundAnimatedItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
|
||||
}
|
||||
|
||||
dropsLeftAnimatedItems = [AnimatedTextComponent.Item(id: "drops", content: .number(Int(dropsLeft), minDigits: 1))]
|
||||
if dropsLeft >= 10000 {
|
||||
var compactString = compactNumericCountString(Int(dropsLeft), decimalSeparator: ".", showDecimalPart: false)
|
||||
let suffix = String(compactString.suffix(1))
|
||||
compactString.removeLast()
|
||||
if let value = Int(compactString) {
|
||||
dropsLeftAnimatedItems = [
|
||||
AnimatedTextComponent.Item(id: "drops", content: .number(value, minDigits: 1)),
|
||||
AnimatedTextComponent.Item(id: "suffix", content: .text(suffix))
|
||||
]
|
||||
}
|
||||
} else {
|
||||
dropsLeftAnimatedItems = [AnimatedTextComponent.Item(id: "drops", content: .number(Int(dropsLeft), minDigits: 1))]
|
||||
}
|
||||
}
|
||||
|
||||
auctionStats.append((
|
||||
|
|
@ -2688,10 +2707,17 @@ private final class GiftAuctionBidScreenComponent: Component {
|
|||
var initialContentHeight = contentHeight
|
||||
let clippingY: CGFloat
|
||||
|
||||
let titleString: String
|
||||
if isUpcoming {
|
||||
titleString = environment.strings.Gift_AuctionBid_UpcomingTitle
|
||||
} 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 +2734,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,17 @@ 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 var disposables = DisposableSet()
|
||||
private var fetchedFiles = Set<Int64>()
|
||||
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 +114,98 @@ 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
|
||||
|
||||
for case let .model(_, file, _) in self.previewModels where !self.fetchedFiles.contains(file.fileId.id) {
|
||||
self.disposables.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start())
|
||||
self.fetchedFiles.insert(file.fileId.id)
|
||||
}
|
||||
|
||||
for case let .pattern(_, file, _) in self.previewSymbols where !self.fetchedFiles.contains(file.fileId.id) {
|
||||
self.disposables.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start())
|
||||
self.fetchedFiles.insert(file.fileId.id)
|
||||
}
|
||||
|
||||
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.disposables.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 +263,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 +403,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 +474,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,63 +508,22 @@ 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
|
||||
|
||||
if let genericGift {
|
||||
let animation = animation.update(
|
||||
component: GiftItemComponent(
|
||||
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
|
||||
),
|
||||
availableSize: CGSize(width: 120.0, height: 120.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(animation
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 92.0))
|
||||
)
|
||||
}
|
||||
originY += 177.0
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: titleString,
|
||||
font: Font.bold(24.0),
|
||||
textColor: theme.list.itemPrimaryTextColor,
|
||||
paragraphAlignment: .center
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 174.0))
|
||||
)
|
||||
|
||||
var buttonColor: UIColor = .white.withAlphaComponent(0.1)
|
||||
var secondaryTextColor: UIColor = .white.withAlphaComponent(0.4)
|
||||
|
||||
var descriptionText: String = ""
|
||||
var descriptionColor = theme.list.itemSecondaryTextColor
|
||||
|
||||
let tableFont = Font.regular(15.0)
|
||||
let tableTextColor = theme.list.itemPrimaryTextColor
|
||||
|
|
@ -502,7 +549,6 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
|
||||
if isEnded {
|
||||
descriptionText = strings.Gift_Auction_Ended
|
||||
descriptionColor = theme.list.itemDestructiveColor
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "firstSale",
|
||||
|
|
@ -572,11 +618,7 @@ 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
|
||||
descriptionText = isUpcoming ? strings.Gift_Auction_UpcomingAuction : strings.Gift_Auction_GiftAuction
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "start",
|
||||
|
|
@ -670,11 +712,103 @@ 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
|
||||
|
||||
if let genericGift {
|
||||
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 headerHeight: CGFloat = 264.0
|
||||
|
||||
let header = header.update(
|
||||
component: GiftCompositionComponent(
|
||||
context: component.context,
|
||||
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: context.availableSize.width, height: headerHeight),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(header
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: header.size.height * 0.5))
|
||||
)
|
||||
originY += header.size.height
|
||||
originY += 16.0
|
||||
}
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: titleString,
|
||||
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
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 209.0))
|
||||
)
|
||||
|
||||
let description = description.update(
|
||||
component: StarsButtonContentComponent(
|
||||
context: component.context,
|
||||
text: descriptionText,
|
||||
color: buttonColor,
|
||||
tinted: true,
|
||||
starsColor: isEnded ? .clear : 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 +819,32 @@ 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))
|
||||
let learnMoreAttributedString = parseMarkdownIntoAttributedString(strings.Gift_Auction_LearnMore, 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.makeGiftDemoScreen(
|
||||
context: component.context
|
||||
)
|
||||
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 +856,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 +909,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 +956,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 +1001,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
|
||||
}
|
||||
|
|
@ -963,7 +1169,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
component: ButtonComponent(
|
||||
background: buttonBackground,
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable("buy"),
|
||||
id: AnyHashable("bid"),
|
||||
component: AnyComponent(VStack(items, spacing: 1.0))
|
||||
),
|
||||
isEnabled: true,
|
||||
|
|
@ -1007,13 +1213,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,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
backgroundColor: buttonColor,
|
||||
isDark: false,
|
||||
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 +1230,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 +1239,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,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .generic,
|
||||
backgroundColor: buttonColor,
|
||||
isDark: false,
|
||||
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 +1261,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 +1387,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,788 @@
|
|||
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
|
||||
import AnimatedTextComponent
|
||||
|
||||
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 disposable: Disposable?
|
||||
private(set) var giftAuctionState: GiftAuctionContext.State?
|
||||
private var giftAuctionTimer: SwiftSignalKit.Timer?
|
||||
|
||||
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,
|
||||
auctionContext: GiftAuctionContext,
|
||||
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()
|
||||
|
||||
self.disposable = (auctionContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] auctionState in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.giftAuctionState = auctionState
|
||||
self.updated()
|
||||
})
|
||||
|
||||
self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
|
||||
self?.updated()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.giftAuctionTimer?.start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
self.giftAuctionTimer?.invalidate()
|
||||
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, auctionContext: self.auctionContext, 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)
|
||||
}
|
||||
|
||||
let learnMoreAttributedString = parseMarkdownIntoAttributedString(strings.Gift_WearPreview_LearnMore, 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: strings.Gift_WearPreview_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: strings.Gift_WearPreview_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: strings.Gift_WearPreview_FreeUpgrade, 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 {
|
||||
var remains: Int32 = availability.remains
|
||||
if let auctionState = state.giftAuctionState {
|
||||
switch auctionState.auctionState {
|
||||
case let .ongoing(_, _, _, _, _, _, _, giftsLeft, _, _, _, _):
|
||||
remains = giftsLeft
|
||||
case .finished:
|
||||
remains = 0
|
||||
}
|
||||
}
|
||||
let total = availability.total
|
||||
let position = CGFloat(remains) / CGFloat(total)
|
||||
let sold = total - remains
|
||||
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: sold > 0 ? strings.Gift_Send_Sold(sold) : "",
|
||||
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
|
||||
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
var startTime = currentTime
|
||||
var endTime = currentTime
|
||||
var isUpcoming = false
|
||||
|
||||
if let auctionState = state.giftAuctionState {
|
||||
startTime = auctionState.startDate
|
||||
endTime = auctionState.endDate
|
||||
}
|
||||
|
||||
var buttonTitle = strings.Gift_Auction_Join
|
||||
let endTimeout: Int32
|
||||
if currentTime < startTime {
|
||||
isUpcoming = true
|
||||
endTimeout = max(0, startTime - currentTime)
|
||||
} else {
|
||||
endTimeout = max(0, endTime - currentTime)
|
||||
}
|
||||
|
||||
let hours = Int(endTimeout / 3600)
|
||||
let minutes = Int((endTimeout % 3600) / 60)
|
||||
let seconds = Int(endTimeout % 60)
|
||||
|
||||
let rawString: String
|
||||
if isUpcoming {
|
||||
buttonTitle = strings.Gift_Auction_EarlyBid
|
||||
rawString = hours > 0 ? strings.Gift_Auction_StartsInHours : strings.Gift_Auction_StartsInMinutes
|
||||
} else {
|
||||
rawString = hours > 0 ? strings.Gift_Auction_TimeLeftHours : strings.Gift_Auction_TimeLeftMinutes
|
||||
}
|
||||
|
||||
var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = []
|
||||
var startIndex = rawString.startIndex
|
||||
while true {
|
||||
if let range = rawString.range(of: "{", range: startIndex ..< rawString.endIndex) {
|
||||
if range.lowerBound != startIndex {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "prefix_\(buttonAnimatedTitleItems.count)", content: .text(String(rawString[startIndex ..< range.lowerBound]))))
|
||||
}
|
||||
|
||||
startIndex = range.upperBound
|
||||
if let endRange = rawString.range(of: "}", range: startIndex ..< rawString.endIndex) {
|
||||
let controlString = rawString[range.upperBound ..< endRange.lowerBound]
|
||||
if controlString == "h" {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "h", content: .number(hours, minDigits: 2)))
|
||||
} else if controlString == "m" {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "m", content: .number(minutes, minDigits: 2)))
|
||||
} else if controlString == "s" {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
|
||||
}
|
||||
|
||||
startIndex = endRange.upperBound
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if startIndex != rawString.endIndex {
|
||||
buttonAnimatedTitleItems.append(AnimatedTextComponent.Item(id: "suffix_\(buttonAnimatedTitleItems.count)", content: .text(String(rawString[startIndex ..< rawString.endIndex]))))
|
||||
}
|
||||
|
||||
let buttonAttributedString = NSMutableAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
let items: [AnyComponentWithIdentity<Empty>] = [
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(AnimatedTextComponent(
|
||||
font: Font.with(size: 12.0, weight: .medium, traits: .monospacedNumbers),
|
||||
color: theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7),
|
||||
items: buttonAnimatedTitleItems,
|
||||
noDelay: true
|
||||
)))
|
||||
]
|
||||
|
||||
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("bid"),
|
||||
component: AnyComponent(VStack(items, spacing: 1.0))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: {
|
||||
if let controller = controller() as? GiftAuctionWearPreviewScreen {
|
||||
controller.completion()
|
||||
controller.dismissAnimated()
|
||||
}
|
||||
}),
|
||||
availableSize: buttonSize,
|
||||
transition: .spring(duration: 0.2)
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import ChatMessagePaymentAlertController
|
|||
import ActivityIndicator
|
||||
import TooltipUI
|
||||
import MultilineTextComponent
|
||||
import BalancedTextComponent
|
||||
import TelegramStringFormatting
|
||||
|
||||
private final class GiftOfferAlertContentNode: AlertContentNode {
|
||||
|
|
@ -24,6 +25,7 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
|
|||
private var presentationTheme: PresentationTheme
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let amount: CurrencyAmount
|
||||
private let gift: StarGift.UniqueGift
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
|
|
@ -32,6 +34,7 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
|
|||
private let arrowNode: ASImageNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let tableView = ComponentView<Empty>()
|
||||
private let valueDelta = ComponentView<Empty>()
|
||||
|
||||
private let modelButtonTag = GenericComponentViewTag()
|
||||
private let backdropButtonTag = GenericComponentViewTag()
|
||||
|
|
@ -68,6 +71,7 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
|
|||
peer: EnginePeer,
|
||||
title: String,
|
||||
text: String,
|
||||
amount: CurrencyAmount,
|
||||
actions: [TextAlertAction]
|
||||
) {
|
||||
self.context = context
|
||||
|
|
@ -75,6 +79,7 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
|
|||
self.presentationTheme = ptheme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.amount = amount
|
||||
self.gift = gift
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
|
|
@ -339,17 +344,6 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
|
|||
}
|
||||
}
|
||||
|
||||
if let valueAmount = self.gift.valueAmount, let valueCurrency = self.gift.valueCurrency {
|
||||
tableItems.append(.init(
|
||||
id: "fiatValue",
|
||||
title: strings.Gift_Unique_Value,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "~\(formatCurrencyAmount(valueAmount, currency: valueCurrency))", font: tableFont, textColor: tableTextColor)))
|
||||
),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
|
||||
))
|
||||
}
|
||||
|
||||
let tableSize = self.tableView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
|
|
@ -370,7 +364,57 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
|
|||
view.frame = tableFrame
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + 40.0 + insets.top + insets.bottom)
|
||||
var valueDeltaHeight: CGFloat = 0.0
|
||||
if let valueAmount = self.gift.valueUsdAmount {
|
||||
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
let usdRate: Double
|
||||
switch self.amount.currency {
|
||||
case .stars:
|
||||
usdRate = Double(resaleConfiguration.usdWithdrawRate) / 1000.0 / 100.0
|
||||
case .ton:
|
||||
usdRate = Double(resaleConfiguration.tonUsdRate) / 1000.0 / 1000000.0
|
||||
}
|
||||
let offerUsdValue = Double(self.amount.amount.value) * usdRate
|
||||
let giftUsdValue = Double(valueAmount) / 100.0
|
||||
|
||||
let fraction = giftUsdValue / offerUsdValue
|
||||
let percentage = Int(fraction * 100) - 100
|
||||
|
||||
if percentage > 20 {
|
||||
let textColor = self.presentationTheme.list.itemDestructiveColor
|
||||
let markdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
)
|
||||
let valueDeltaSize = self.valueDelta.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
BalancedTextComponent(
|
||||
text: .markdown(text: strings.Chat_GiftPurchaseOffer_AcceptConfirmation_BadValue("\(percentage)%").string, attributes: markdownAttributes),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: contentWidth - 32.0, height: size.height)
|
||||
)
|
||||
let valueDeltaFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - valueDeltaSize.width) / 2.0), y: avatarSize.height + titleSize.height + textSize.height + 73.0 + tableSize.height), size: valueDeltaSize)
|
||||
if let view = self.valueDelta.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = valueDeltaFrame
|
||||
}
|
||||
valueDeltaHeight += valueDeltaSize.height + 10.0
|
||||
}
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + valueDeltaHeight + 40.0 + insets.top + insets.bottom)
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
|
|
@ -380,14 +424,14 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
|
|||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
do {
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
do {
|
||||
currentActionWidth = resultSize.width
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
|
|
@ -433,13 +477,13 @@ public func giftOfferAlertController(
|
|||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
let title = "Confirm Sale"
|
||||
let buttonText: String = "Confirm Sale"
|
||||
let title = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Title
|
||||
let buttonText: String = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Confirm
|
||||
|
||||
let priceString: String
|
||||
switch amount.currency {
|
||||
case .stars:
|
||||
priceString = "\(amount.amount) Stars"
|
||||
priceString = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text_Stars(Int32(clamping: amount.amount.value))
|
||||
case .ton:
|
||||
priceString = "\(amount.amount) TON"
|
||||
}
|
||||
|
|
@ -449,15 +493,14 @@ public func giftOfferAlertController(
|
|||
switch amount.currency {
|
||||
case .stars:
|
||||
let starsValue = Int32(floor(Float(amount.amount.value) * Float(resaleConfiguration.starGiftCommissionStarsPermille) / 1000.0))
|
||||
finalPriceString = "\(starsValue) Stars"
|
||||
finalPriceString = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text_Stars(starsValue)
|
||||
case .ton:
|
||||
let tonValue = Int64(Float(amount.amount.value) * Float(resaleConfiguration.starGiftCommissionTonPermille) / 1000.0)
|
||||
finalPriceString = formatTonAmountText(tonValue, dateTimeFormat: presentationData.dateTimeFormat, maxDecimalPositions: 3) + " TON"
|
||||
}
|
||||
|
||||
let giftTitle = "\(gift.title) #\(formatCollectibleNumber(gift.number, dateTimeFormat: presentationData.dateTimeFormat))"
|
||||
let text = "Do you want to sell **\(giftTitle)** to \(peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)) for **\(priceString)**? You'll receive **\(finalPriceString)** after fees."
|
||||
|
||||
let text = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text(giftTitle, peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), priceString, finalPriceString).string
|
||||
|
||||
var contentNode: GiftOfferAlertContentNode?
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
|
|
@ -468,7 +511,7 @@ public func giftOfferAlertController(
|
|||
dismissImpl?(true)
|
||||
})]
|
||||
|
||||
contentNode = GiftOfferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
|
||||
contentNode = GiftOfferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, amount: amount, actions: actions)
|
||||
|
||||
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: nil, chatPeerId: context.account.peerId, showBalance: false)
|
||||
contentNode?.getController = { [weak controller] in
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
|
||||
private var attributeInfos: [ComponentView<Empty>] = []
|
||||
|
||||
private let topEdgeSolidView = UIView()
|
||||
private let topEdgeEffectView: EdgeEffectView
|
||||
private let segmentControl = ComponentView<Empty>()
|
||||
private let descriptionText = ComponentView<Empty>()
|
||||
|
||||
|
|
@ -103,10 +105,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
|
||||
|
|
@ -147,6 +148,9 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
|
||||
self.navigationBarContainer = SparseContainerView()
|
||||
|
||||
self.topEdgeEffectView = EdgeEffectView()
|
||||
self.topEdgeEffectView.alpha = 0.0
|
||||
|
||||
self.glassContainerView = GlassBackgroundContainerView()
|
||||
|
||||
self.scrollView = ScrollView()
|
||||
|
|
@ -236,6 +240,8 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
var topOffsetFraction = self.scrollView.bounds.minY / 100.0
|
||||
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
|
||||
|
||||
self.topEdgeEffectView.alpha = max(0.0, min(1.0, self.scrollView.bounds.minY / 8.0))
|
||||
|
||||
let minScale: CGFloat = (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width
|
||||
let minScaledTranslation: CGFloat = (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0
|
||||
let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius
|
||||
|
|
@ -272,7 +278,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 +305,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.timerTick()
|
||||
self.previewTimerTick()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.previewTimer?.start()
|
||||
} else {
|
||||
|
|
@ -309,15 +315,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 +332,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 +348,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 +570,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 +616,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
}).shuffled().prefix(15))
|
||||
self.previewSymbols = randomSymbols
|
||||
|
||||
self.updateEffectiveGifts()
|
||||
self.updateEffectiveGifts(attributes: component.attributes)
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
|
@ -671,7 +672,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 +687,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
|
||||
|
|
@ -790,6 +792,14 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
let edgeEffectHeight: CGFloat = 44.0
|
||||
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight + 44.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
|
||||
let edgeSolidFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: availableSize.width, height: 44.0))
|
||||
transition.setFrame(view: self.topEdgeSolidView, frame: edgeSolidFrame)
|
||||
transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame)
|
||||
self.topEdgeSolidView.backgroundColor = theme.list.blocksBackgroundColor
|
||||
self.topEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
|
||||
|
||||
contentHeight += 16.0
|
||||
|
||||
let segmentedSize = self.segmentControl.update(
|
||||
|
|
@ -797,20 +807,20 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
component: AnyComponent(SegmentControlComponent(
|
||||
theme: environment.theme,
|
||||
items: [
|
||||
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.models), title: "Models"),
|
||||
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.backdrops), title: "Backgrounds"),
|
||||
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.symbols), title: "Symbols")
|
||||
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.models), title: environment.strings.Gift_Variants_Models),
|
||||
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.backdrops), title: environment.strings.Gift_Variants_Backdrops),
|
||||
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.symbols), title: environment.strings.Gift_Variants_Symbols)
|
||||
],
|
||||
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.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))) //.withUserData(AnimationHint(value: .modeChanged)))
|
||||
self.updateEffectiveGifts(attributes: component.attributes)
|
||||
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
})),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: fillingSize - 8.0 * 2.0, height: 100.0)
|
||||
|
|
@ -818,22 +828,30 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
let segmentedControlFrame = CGRect(origin: CGPoint(x: floor((fillingSize - segmentedSize.width) * 0.5), y: contentHeight), size: segmentedSize)
|
||||
if let segmentedControlComponentView = self.segmentControl.view {
|
||||
if segmentedControlComponentView.superview == nil {
|
||||
self.scrollContentView.addSubview(segmentedControlComponentView)
|
||||
self.navigationBarContainer.addSubview(self.topEdgeSolidView)
|
||||
self.navigationBarContainer.addSubview(self.topEdgeEffectView)
|
||||
self.navigationBarContainer.addSubview(segmentedControlComponentView)
|
||||
}
|
||||
transition.setFrame(view: segmentedControlComponentView, frame: segmentedControlFrame)
|
||||
}
|
||||
contentHeight += segmentedSize.height
|
||||
contentHeight += 18.0
|
||||
|
||||
let itemHeight: CGFloat = 126.0
|
||||
let itemSpacing: CGFloat = 10.0
|
||||
|
||||
let descriptionText: String
|
||||
let itemCount: Int32
|
||||
switch self.selectedSection {
|
||||
case .models:
|
||||
descriptionText = "This collection features **\(self.modelCount)** unique models."
|
||||
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Model(self.modelCount)).string
|
||||
itemCount = self.modelCount
|
||||
case .backdrops:
|
||||
descriptionText = "This collection features **\(self.backdropCount)** unique backdrops."
|
||||
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Backdrop(self.backdropCount)).string
|
||||
itemCount = self.backdropCount
|
||||
case .symbols:
|
||||
descriptionText = "This collection features **\(self.symbolCount)** unique symbols."
|
||||
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Symbol(self.symbolCount)).string
|
||||
itemCount = self.symbolCount
|
||||
}
|
||||
|
||||
let descriptionFont = Font.regular(13.0)
|
||||
|
|
@ -844,7 +862,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
})
|
||||
|
||||
let descriptionSize = self.descriptionText.update(
|
||||
transition: transition,
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .markdown(text: descriptionText, attributes: descriptionMarkdownAttributes)
|
||||
)),
|
||||
|
|
@ -856,85 +874,12 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
if descriptionView.superview == nil {
|
||||
self.scrollContentView.addSubview(descriptionView)
|
||||
}
|
||||
transition.setFrame(view: descriptionView, frame: descriptionFrame)
|
||||
descriptionView.frame = descriptionFrame
|
||||
}
|
||||
contentHeight += descriptionSize.height
|
||||
contentHeight += 26.0
|
||||
|
||||
|
||||
|
||||
|
||||
////
|
||||
//// var validKeys: Set<Int64> = Set()
|
||||
//// for auctionState in self.auctionStates {
|
||||
//// let id = auctionState.gift.giftId
|
||||
//// validKeys.insert(id)
|
||||
////
|
||||
//// let itemView: ComponentView<Empty>
|
||||
//// if let current = self.itemsViews[id] {
|
||||
//// itemView = current
|
||||
//// } else {
|
||||
//// itemView = ComponentView()
|
||||
//// self.itemsViews[id] = itemView
|
||||
//// }
|
||||
////
|
||||
//// let itemSize = itemView.update(
|
||||
//// transition: transition,
|
||||
//// component: AnyComponent(
|
||||
//// ActiveAuctionComponent(
|
||||
//// context: component.context,
|
||||
//// theme: theme,
|
||||
//// strings: environment.strings,
|
||||
//// dateTimeFormat: environment.dateTimeFormat,
|
||||
//// state: auctionState,
|
||||
//// currentTime: currentTime,
|
||||
//// action: { [weak self] in
|
||||
//// guard let self, let component = self.component else {
|
||||
//// return
|
||||
//// }
|
||||
//// if let giftAuctionsManager = component.context.giftAuctionsManager {
|
||||
//// let _ = (giftAuctionsManager.auctionContext(for: .giftId(id))
|
||||
//// |> deliverOnMainQueue).start(next: { [weak self] auction in
|
||||
//// guard let self, let component = self.component, let auction, let controller = environment.controller(), let navigationController = controller.navigationController as? NavigationController else {
|
||||
//// return
|
||||
//// }
|
||||
//// let bidController = component.context.sharedContext.makeGiftAuctionBidScreen(context: component.context, toPeerId: auction.currentBidPeerId ?? component.context.account.peerId, text: nil, entities: nil, hideName: false, auctionContext: auction, acquiredGifts: nil)
|
||||
//// navigationController.pushViewController(bidController)
|
||||
//// })
|
||||
//// }
|
||||
//// }
|
||||
//// )
|
||||
//// ),
|
||||
//// environment: {},
|
||||
//// containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
//// )
|
||||
//// let itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: itemSize)
|
||||
//// if let view = itemView.view {
|
||||
//// if view.superview == nil {
|
||||
//// self.scrollContentView.addSubview(view)
|
||||
//// }
|
||||
//// view.frame = itemFrame
|
||||
//// }
|
||||
//// contentHeight += itemSize.height
|
||||
//// contentHeight += 20.0
|
||||
//// }
|
||||
//// contentHeight -= 10.0
|
||||
//
|
||||
// var removeKeys: [Int64] = []
|
||||
// for (id, item) in self.itemsViews {
|
||||
// if !validKeys.contains(id) {
|
||||
// removeKeys.append(id)
|
||||
//
|
||||
// if let itemView = item.view {
|
||||
// transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in
|
||||
// itemView.removeFromSuperview()
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// for id in removeKeys {
|
||||
// self.itemsViews.removeValue(forKey: id)
|
||||
// }
|
||||
contentHeight += (itemHeight + itemSpacing) * ceil(CGFloat(itemCount) / 3.0)
|
||||
|
||||
if self.backgroundHandleView.image == nil {
|
||||
self.backgroundHandleView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
|
|
@ -950,10 +895,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(
|
||||
|
|
@ -976,44 +921,20 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
if let closeButtonView = self.closeButton.view {
|
||||
if closeButtonView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(self.glassContainerView)
|
||||
self.glassContainerView.contentView.addSubview(closeButtonView)
|
||||
self.navigationBarContainer.addSubview(closeButtonView)
|
||||
}
|
||||
transition.setFrame(view: closeButtonView, frame: closeButtonFrame)
|
||||
}
|
||||
|
||||
var playbackItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
var playbackSize = CGSize(width: 40.0, height: 40.0)
|
||||
if self.isPlaying {
|
||||
playbackItems.append(AnyComponentWithIdentity(id: "pause", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Media Gallery/PictureInPicturePause",
|
||||
tintColor: .white
|
||||
)
|
||||
)))
|
||||
} else {
|
||||
if self.showRandomizeTip {
|
||||
playbackItems.append(AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Randomize Traits", font: Font.semibold(17.0), textColor: .white)))
|
||||
)))
|
||||
playbackSize.width = 186.0
|
||||
}
|
||||
playbackItems.append(AnyComponentWithIdentity(id: "play", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Media Gallery/PlayButton",
|
||||
tintColor: .white
|
||||
)
|
||||
)))
|
||||
}
|
||||
|
||||
|
||||
let playbackButtonSize = self.playbackButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(GlassBarButtonComponent(
|
||||
size: playbackSize,
|
||||
backgroundColor: secondaryTextColor,
|
||||
size: nil,
|
||||
backgroundColor: buttonColor,
|
||||
isDark: false,
|
||||
state: .tintedGlass,
|
||||
component: AnyComponentWithIdentity(id: "content", component: AnyComponent(
|
||||
HStack(playbackItems, spacing: 1.0)
|
||||
PlayButtonComponent(isPlay: !self.isPlaying, title: !self.isPlaying && self.showRandomizeTip ? environment.strings.Gift_Variants_Randomize : nil)
|
||||
)),
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
|
|
@ -1036,26 +957,22 @@ private final class GiftUpgradePreviewScreenComponent: Component {
|
|||
|
||||
self.showRandomizeTip = false
|
||||
|
||||
self.timerTick()
|
||||
self.previewTimerTick()
|
||||
}
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.25))
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: playbackSize
|
||||
containerSize: CGSize(width: 160.0, height: 40.0)
|
||||
)
|
||||
let playbackButtonFrame = CGRect(origin: CGPoint(x: fillingSize - 16.0 - playbackButtonSize.width, y: 16.0), size: playbackButtonSize)
|
||||
if let playbackButtonView = self.playbackButton.view {
|
||||
if playbackButtonView.superview == nil {
|
||||
playbackButtonView.clipsToBounds = true
|
||||
self.glassContainerView.contentView.addSubview(playbackButtonView)
|
||||
}
|
||||
transition.setFrame(view: playbackButtonView, frame: playbackButtonFrame)
|
||||
}
|
||||
|
||||
//TODO:release
|
||||
contentHeight += 126.0 * 17.0
|
||||
|
||||
let containerInset: CGFloat = environment.statusBarHeight + 10.0
|
||||
contentHeight += environment.safeInsets.bottom
|
||||
|
||||
|
|
@ -1261,15 +1178,15 @@ private final class AttributeInfoComponent: Component {
|
|||
switch component.attribute {
|
||||
case let .model(name, _, rarityValue):
|
||||
title = name
|
||||
subtitle = "model"
|
||||
subtitle = component.strings.Gift_Variants_Model
|
||||
rarity = rarityValue
|
||||
case let .backdrop(name, _, _, _, _, _, rarityValue):
|
||||
title = name
|
||||
subtitle = "backdrop"
|
||||
subtitle = component.strings.Gift_Variants_Backdrop
|
||||
rarity = rarityValue
|
||||
case let .pattern(name, _, rarityValue):
|
||||
title = name
|
||||
subtitle = "symbol"
|
||||
subtitle = component.strings.Gift_Variants_Symbol
|
||||
rarity = rarityValue
|
||||
default:
|
||||
title = ""
|
||||
|
|
@ -1364,3 +1281,154 @@ private final class AttributeInfoComponent: Component {
|
|||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class PlayButtonComponent: Component {
|
||||
let isPlay: Bool
|
||||
let title: String?
|
||||
|
||||
public init(
|
||||
isPlay: Bool,
|
||||
title: String?
|
||||
) {
|
||||
self.isPlay = isPlay
|
||||
self.title = title
|
||||
}
|
||||
|
||||
static func ==(lhs: PlayButtonComponent, rhs: PlayButtonComponent) -> Bool {
|
||||
if lhs.isPlay != rhs.isPlay {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var component: PlayButtonComponent?
|
||||
private weak var componentState: EmptyComponentState?
|
||||
|
||||
private let containerView = UIView()
|
||||
private let titleContainerView = UIView()
|
||||
private let title = ComponentView<Empty>()
|
||||
private let play = ComponentView<Empty>()
|
||||
private let pause = ComponentView<Empty>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.containerView.clipsToBounds = true
|
||||
self.containerView.layer.cornerRadius = 20.0
|
||||
self.addSubview(self.containerView)
|
||||
|
||||
self.titleContainerView.clipsToBounds = true
|
||||
self.containerView.addSubview(self.titleContainerView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: PlayButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
var contentSize = CGSize(width: 15.0, height: 21.0)
|
||||
|
||||
var titleSize = CGSize()
|
||||
if let titleString = component.title {
|
||||
titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: .white)))),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: 9.0, y: 10.0), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
titleView.alpha = 1.0
|
||||
if titleView.superview == nil {
|
||||
self.titleContainerView.addSubview(titleView)
|
||||
transition.animateAlpha(view: titleView, from: 0.0, to: 1.0)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
contentSize.width += titleSize.width + 4.0
|
||||
} else if let titleView = self.title.view {
|
||||
transition.setAlpha(view: titleView, alpha: 0.0, completion: { finished in
|
||||
if finished {
|
||||
titleView.removeFromSuperview()
|
||||
}
|
||||
})
|
||||
}
|
||||
transition.setFrame(view: self.titleContainerView, frame: CGRect(origin: .zero, size: CGSize(width: titleSize.width + 14.0, height: 40.0)))
|
||||
|
||||
if component.isPlay {
|
||||
let iconSize = self.play.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(name: "Media Gallery/PlayButton", tintColor: .white)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: contentSize.width - iconSize.width + 21.0, y: 5.0), size: iconSize)
|
||||
if let iconView = self.play.view {
|
||||
iconView.alpha = 1.0
|
||||
if iconView.superview == nil {
|
||||
self.containerView.addSubview(iconView)
|
||||
transition.animateAlpha(view: iconView, from: 0.0, to: 1.0)
|
||||
transition.animateScale(view: iconView, from: 0.01, to: 1.0)
|
||||
}
|
||||
transition.setFrame(view: iconView, frame: iconFrame)
|
||||
}
|
||||
} else if let iconView = self.play.view {
|
||||
transition.setAlpha(view: iconView, alpha: 0.0, completion: { finished in
|
||||
if finished {
|
||||
iconView.removeFromSuperview()
|
||||
}
|
||||
})
|
||||
transition.animateScale(view: iconView, from: 1.0, to: 0.01)
|
||||
}
|
||||
|
||||
if !component.isPlay {
|
||||
let iconSize = self.pause.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(name: "Media Gallery/PictureInPicturePause", tintColor: .white)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: contentSize.width - iconSize.width + 12.0 - UIScreenPixel, y: 13.0 - UIScreenPixel), size: iconSize)
|
||||
if let iconView = self.pause.view {
|
||||
iconView.alpha = 1.0
|
||||
if iconView.superview == nil {
|
||||
self.containerView.addSubview(iconView)
|
||||
transition.animateAlpha(view: iconView, from: 0.0, to: 1.0)
|
||||
transition.animateScale(view: iconView, from: 0.01, to: 1.0)
|
||||
}
|
||||
transition.setFrame(view: iconView, frame: iconFrame)
|
||||
}
|
||||
} else if let iconView = self.pause.view {
|
||||
transition.setAlpha(view: iconView, alpha: 0.0, completion: { finished in
|
||||
if finished {
|
||||
iconView.removeFromSuperview()
|
||||
}
|
||||
})
|
||||
transition.animateScale(view: iconView, from: 1.0, to: 0.01)
|
||||
}
|
||||
|
||||
let containerWidth: CGFloat = contentSize.width + 26.0
|
||||
let containerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((contentSize.width - containerWidth) / 2.0), y: floorToScreenPixels((contentSize.height - 40.0) / 2.0)), size: CGSize(width: containerWidth, height: 40.0))
|
||||
transition.setFrame(view: self.containerView, frame: containerFrame)
|
||||
|
||||
return contentSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import GiftAnimationComponent
|
|||
import ChatThemeScreen
|
||||
import ProfileLevelRatingBarComponent
|
||||
import AnimatedTextComponent
|
||||
import InfoParagraphComponent
|
||||
|
||||
private final class GiftViewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -1437,9 +1438,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
self?.shareGift()
|
||||
})))
|
||||
|
||||
//TODO:localize
|
||||
if case let .unique(uniqueGift) = arguments.gift, case let .peerId(ownerPeerId) = uniqueGift.owner, ownerPeerId != self.context.account.peerId, uniqueGift.minOfferStars != nil {
|
||||
items.append(.action(ContextMenuActionItem(text: "Offer to Buy", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_View_Context_BuyOffer, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Paid"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
c?.dismiss(completion: nil)
|
||||
|
|
@ -2545,7 +2545,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
titleString = strings.Gift_View_UnavailableTitle
|
||||
} else if let arguments = subject.arguments {
|
||||
if let toPeerId = arguments.auctionToPeerId {
|
||||
isSelfGift = arguments.messageId?.peerId.isTelegramNotifications == true && toPeerId == component.context.account.peerId
|
||||
isSelfGift = arguments.messageId?.peerId.isTelegramNotifications == true && toPeerId == component.context.account.peerId
|
||||
} else {
|
||||
isSelfGift = arguments.messageId?.peerId == component.context.account.peerId
|
||||
}
|
||||
|
|
@ -2603,7 +2603,9 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
isMyOwnedUniqueGift = true
|
||||
}
|
||||
|
||||
if isSelfGift {
|
||||
if let number = arguments.giftNumber, let title = genericGift?.title {
|
||||
titleString = "\(title) #\(formatCollectibleNumber(number, dateTimeFormat: environment.dateTimeFormat))"
|
||||
} else if isSelfGift {
|
||||
titleString = strings.Gift_View_Self_Title
|
||||
} else {
|
||||
titleString = incoming ? strings.Gift_View_ReceivedTitle : strings.Gift_View_Title
|
||||
|
|
@ -2885,7 +2887,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "badge",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Wear_Badge_Title,
|
||||
titleColor: textColor,
|
||||
text: isChannelGift ? strings.Gift_Wear_Badge_ChannelText : strings.Gift_Wear_Badge_Text,
|
||||
|
|
@ -2899,7 +2901,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "design",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Wear_Design_Title,
|
||||
titleColor: textColor,
|
||||
text: isChannelGift ? strings.Gift_Wear_Design_ChannelText : strings.Gift_Wear_Design_Text,
|
||||
|
|
@ -2913,7 +2915,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "proof",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Wear_Proof_Title,
|
||||
titleColor: textColor,
|
||||
text: isChannelGift ? strings.Gift_Wear_Proof_ChannelText : strings.Gift_Wear_Proof_Text,
|
||||
|
|
@ -3031,7 +3033,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "unique",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Upgrade_Unique_Title,
|
||||
titleColor: textColor,
|
||||
text: uniqueText,
|
||||
|
|
@ -3045,7 +3047,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "transferable",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Upgrade_Transferable_Title,
|
||||
titleColor: textColor,
|
||||
text: transferableText,
|
||||
|
|
@ -3059,7 +3061,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "tradable",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gift_Upgrade_Tradable_Title,
|
||||
titleColor: textColor,
|
||||
text: tradableText,
|
||||
|
|
@ -6046,184 +6048,6 @@ private struct GiftConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
private final class ParagraphComponent: CombinedComponent {
|
||||
let title: String
|
||||
let titleColor: UIColor
|
||||
let text: String
|
||||
let textColor: UIColor
|
||||
let accentColor: UIColor
|
||||
let iconName: String
|
||||
let iconColor: UIColor
|
||||
let badge: String?
|
||||
let action: () -> Void
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
titleColor: UIColor,
|
||||
text: String,
|
||||
textColor: UIColor,
|
||||
accentColor: UIColor,
|
||||
iconName: String,
|
||||
iconColor: UIColor,
|
||||
badge: String? = nil,
|
||||
action: @escaping () -> Void = {}
|
||||
) {
|
||||
self.title = title
|
||||
self.titleColor = titleColor
|
||||
self.text = text
|
||||
self.textColor = textColor
|
||||
self.accentColor = accentColor
|
||||
self.iconName = iconName
|
||||
self.iconColor = iconColor
|
||||
self.badge = badge
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.titleColor != rhs.titleColor {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.textColor != rhs.textColor {
|
||||
return false
|
||||
}
|
||||
if lhs.accentColor != rhs.accentColor {
|
||||
return false
|
||||
}
|
||||
if lhs.iconName != rhs.iconName {
|
||||
return false
|
||||
}
|
||||
if lhs.iconColor != rhs.iconColor {
|
||||
return false
|
||||
}
|
||||
if lhs.badge != rhs.badge {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let text = Child(MultilineTextComponent.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
let badgeBackground = Child(RoundedRectangle.self)
|
||||
let badgeText = Child(MultilineTextComponent.self)
|
||||
|
||||
return { context in
|
||||
let component = context.component
|
||||
|
||||
let leftInset: CGFloat = 32.0
|
||||
let rightInset: CGFloat = 24.0
|
||||
let textSideInset: CGFloat = leftInset + 8.0
|
||||
let spacing: CGFloat = 5.0
|
||||
|
||||
let textTopInset: CGFloat = 9.0
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.title,
|
||||
font: Font.semibold(15.0),
|
||||
textColor: component.titleColor,
|
||||
paragraphAlignment: .natural
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let textColor = component.textColor
|
||||
let accentColor = component.accentColor
|
||||
let markdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: textFont, textColor: accentColor),
|
||||
linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
}
|
||||
)
|
||||
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(text: component.text, attributes: markdownAttributes),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2,
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { _, _ in
|
||||
component.action()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: component.iconName,
|
||||
tintColor: component.iconColor
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
context.add(title
|
||||
.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
|
||||
)
|
||||
|
||||
if let badge = component.badge {
|
||||
let badgeText = badgeText.update(
|
||||
component: MultilineTextComponent(text: .plain(NSAttributedString(string: badge, font: Font.semibold(11.0), textColor: .white))),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let badgeWidth = badgeText.size.width + 7.0
|
||||
let badgeBackground = badgeBackground.update(
|
||||
component: RoundedRectangle(
|
||||
color: component.accentColor,
|
||||
cornerRadius: 5.0),
|
||||
availableSize: CGSize(width: badgeWidth, height: 16.0),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(badgeBackground
|
||||
.position(CGPoint(x: textSideInset + title.size.width + badgeWidth / 2.0 + 5.0, y: textTopInset + title.size.height / 2.0))
|
||||
)
|
||||
|
||||
context.add(badgeText
|
||||
.position(CGPoint(x: textSideInset + title.size.width + badgeWidth / 2.0 + 5.0, y: textTopInset + title.size.height / 2.0))
|
||||
)
|
||||
}
|
||||
|
||||
context.add(text
|
||||
.position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0))
|
||||
)
|
||||
|
||||
context.add(icon
|
||||
.position(CGPoint(x: 15.0, y: textTopInset + 18.0))
|
||||
)
|
||||
|
||||
return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class GiftViewContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ASDisplayNode
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "InfoParagraphComponent",
|
||||
module_name = "InfoParagraphComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/TextFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import Markdown
|
||||
import TextFormat
|
||||
|
||||
public final class InfoParagraphComponent: CombinedComponent {
|
||||
let title: String
|
||||
let titleColor: UIColor
|
||||
let text: String
|
||||
let textColor: UIColor
|
||||
let accentColor: UIColor
|
||||
let iconName: String
|
||||
let iconColor: UIColor
|
||||
let badge: String?
|
||||
let action: () -> Void
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
titleColor: UIColor,
|
||||
text: String,
|
||||
textColor: UIColor,
|
||||
accentColor: UIColor,
|
||||
iconName: String,
|
||||
iconColor: UIColor,
|
||||
badge: String? = nil,
|
||||
action: @escaping () -> Void = {}
|
||||
) {
|
||||
self.title = title
|
||||
self.titleColor = titleColor
|
||||
self.text = text
|
||||
self.textColor = textColor
|
||||
self.accentColor = accentColor
|
||||
self.iconName = iconName
|
||||
self.iconColor = iconColor
|
||||
self.badge = badge
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public static func ==(lhs: InfoParagraphComponent, rhs: InfoParagraphComponent) -> Bool {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.titleColor != rhs.titleColor {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.textColor != rhs.textColor {
|
||||
return false
|
||||
}
|
||||
if lhs.accentColor != rhs.accentColor {
|
||||
return false
|
||||
}
|
||||
if lhs.iconName != rhs.iconName {
|
||||
return false
|
||||
}
|
||||
if lhs.iconColor != rhs.iconColor {
|
||||
return false
|
||||
}
|
||||
if lhs.badge != rhs.badge {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public static var body: Body {
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let text = Child(MultilineTextComponent.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
let badgeBackground = Child(RoundedRectangle.self)
|
||||
let badgeText = Child(MultilineTextComponent.self)
|
||||
|
||||
return { context in
|
||||
let component = context.component
|
||||
|
||||
let leftInset: CGFloat = 32.0
|
||||
let rightInset: CGFloat = 24.0
|
||||
let textSideInset: CGFloat = leftInset + 8.0
|
||||
let spacing: CGFloat = 5.0
|
||||
|
||||
let textTopInset: CGFloat = 9.0
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.title,
|
||||
font: Font.semibold(15.0),
|
||||
textColor: component.titleColor,
|
||||
paragraphAlignment: .natural
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let textColor = component.textColor
|
||||
let accentColor = component.accentColor
|
||||
let markdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: textFont, textColor: accentColor),
|
||||
linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
}
|
||||
)
|
||||
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(text: component.text, attributes: markdownAttributes),
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2,
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { _, _ in
|
||||
component.action()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: component.iconName,
|
||||
tintColor: component.iconColor
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
context.add(title
|
||||
.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
|
||||
)
|
||||
|
||||
if let badge = component.badge {
|
||||
let badgeText = badgeText.update(
|
||||
component: MultilineTextComponent(text: .plain(NSAttributedString(string: badge, font: Font.semibold(11.0), textColor: .white))),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let badgeWidth = badgeText.size.width + 7.0
|
||||
let badgeBackground = badgeBackground.update(
|
||||
component: RoundedRectangle(
|
||||
color: component.accentColor,
|
||||
cornerRadius: 5.0),
|
||||
availableSize: CGSize(width: badgeWidth, height: 16.0),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(badgeBackground
|
||||
.position(CGPoint(x: textSideInset + title.size.width + badgeWidth / 2.0 + 5.0, y: textTopInset + title.size.height / 2.0))
|
||||
)
|
||||
|
||||
context.add(badgeText
|
||||
.position(CGPoint(x: textSideInset + title.size.width + badgeWidth / 2.0 + 5.0, y: textTopInset + title.size.height / 2.0))
|
||||
)
|
||||
}
|
||||
|
||||
context.add(text
|
||||
.position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0))
|
||||
)
|
||||
|
||||
context.add(icon
|
||||
.position(CGPoint(x: 15.0, y: textTopInset + 18.0))
|
||||
)
|
||||
|
||||
return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -79,6 +79,7 @@ public final class GlassBarButtonComponent: Component {
|
|||
super.init(frame: frame)
|
||||
|
||||
self.containerView.isUserInteractionEnabled = false
|
||||
self.containerView.layer.rasterizationScale = UIScreenScale
|
||||
|
||||
self.addSubview(self.containerView)
|
||||
self.containerView.addSubview(self.genericContainerView)
|
||||
|
|
|
|||
|
|
@ -180,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
|
||||
}
|
||||
|
|
@ -266,6 +265,7 @@ 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
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import BundleIconComponent
|
|||
final class PasskeysScreenIntroComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let insets: UIEdgeInsets
|
||||
let displaySkip: Bool
|
||||
let createPasskeyAction: () -> Void
|
||||
|
|
@ -21,6 +22,7 @@ final class PasskeysScreenIntroComponent: Component {
|
|||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
insets: UIEdgeInsets,
|
||||
displaySkip: Bool,
|
||||
createPasskeyAction: @escaping () -> Void,
|
||||
|
|
@ -28,6 +30,7 @@ final class PasskeysScreenIntroComponent: Component {
|
|||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.insets = insets
|
||||
self.displaySkip = displaySkip
|
||||
self.createPasskeyAction = createPasskeyAction
|
||||
|
|
@ -41,6 +44,9 @@ 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
|
||||
}
|
||||
|
|
@ -120,7 +126,6 @@ final class PasskeysScreenIntroComponent: Component {
|
|||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
//TODO:localize
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(LottieComponent(
|
||||
|
|
@ -145,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
|
||||
)),
|
||||
|
|
@ -166,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
|
||||
|
|
@ -193,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 {
|
||||
|
|
@ -290,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 {
|
||||
|
|
@ -312,7 +317,6 @@ final class PasskeysScreenIntroComponent: Component {
|
|||
skipButton = ComponentView()
|
||||
self.skipButton = skipButton
|
||||
}
|
||||
//TODO:localize
|
||||
skipButtonSize = skipButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
|
|
@ -325,7 +329,7 @@ final class PasskeysScreenIntroComponent: Component {
|
|||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Skip", font: Font.semibold(17.0), textColor: component.theme.list.itemCheckColors.foregroundColor))))
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -66,9 +66,6 @@ private final class SheetContent: CombinedComponent {
|
|||
let timestampSection = Child(ListSectionComponent.self)
|
||||
let onlyTonSection = Child(ListSectionComponent.self)
|
||||
let button = Child(ButtonComponent.self)
|
||||
let balanceTitle = Child(MultilineTextComponent.self)
|
||||
let balanceValue = Child(MultilineTextComponent.self)
|
||||
let balanceIcon = Child(BundleIconComponent.self)
|
||||
let durationPicker = Child(MenuComponent.self)
|
||||
|
||||
let body: (CombinedComponentContext<SheetContent>) -> CGSize = { (context: CombinedComponentContext<SheetContent>) -> CGSize in
|
||||
|
|
@ -88,37 +85,7 @@ private final class SheetContent: CombinedComponent {
|
|||
var contentSize = CGSize(width: context.availableSize.width, height: 18.0)
|
||||
|
||||
let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0
|
||||
|
||||
if case let .suggestedPost(mode, _, _, _) = component.mode {
|
||||
var displayBalance = false
|
||||
switch mode {
|
||||
case let .sender(_, isFromAdmin):
|
||||
displayBalance = !isFromAdmin
|
||||
case .admin:
|
||||
break
|
||||
}
|
||||
|
||||
if displayBalance {
|
||||
let balance = balance.update(
|
||||
component: BalanceComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
currency: state.currency,
|
||||
balance: state.currency == .stars ? state.starsBalance : state.tonBalance,
|
||||
alignment: .right
|
||||
),
|
||||
availableSize: CGSize(width: 200.0, height: 200.0),
|
||||
transition: .immediate
|
||||
)
|
||||
let balanceFrame = CGRect(origin: CGPoint(x: context.availableSize.width - balance.size.width - 15.0, y: floor((56.0 - balance.size.height) * 0.5)), size: balance.size)
|
||||
context.add(balance
|
||||
.anchorPoint(CGPoint(x: 1.0, y: 0.0))
|
||||
.position(CGPoint(x: balanceFrame.maxX, y: balanceFrame.minY))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let closeButton = closeButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: CGSize(width: 40.0, height: 40.0),
|
||||
|
|
@ -240,18 +207,18 @@ private final class SheetContent: CombinedComponent {
|
|||
amountLabel = "~\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
|
||||
}
|
||||
}
|
||||
case .starGiftOffer:
|
||||
titleString = "Offer to Buy"
|
||||
case let .starGiftOffer(_, gift, _):
|
||||
titleString = environment.strings.Gift_Offer_Title
|
||||
switch state.currency {
|
||||
case .stars:
|
||||
amountTitle = "STARS TO OFFER"
|
||||
minAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMinStarsAmount, nanos: 0)
|
||||
amountTitle = environment.strings.Gift_Offer_PriceSectionStars
|
||||
minAmount = StarsAmount(value: gift.minOfferStars ?? resaleConfiguration.starGiftResaleMinStarsAmount, nanos: 0)
|
||||
case .ton:
|
||||
amountTitle = "TON TO OFFER"
|
||||
minAmount = StarsAmount(value: 0, nanos: 0)
|
||||
amountTitle = environment.strings.Gift_Offer_PriceSectionTon
|
||||
minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinTonAmount, nanos: 0)
|
||||
}
|
||||
maxAmount = nil
|
||||
amountPlaceholder = "Price"
|
||||
amountPlaceholder = environment.strings.Gift_Offer_PricePlaceholder
|
||||
|
||||
if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate, let tonUsdRate = withdrawConfiguration.tonUsdRate, let amount = state.amount, amount > StarsAmount.zero {
|
||||
switch state.currency {
|
||||
|
|
@ -276,60 +243,45 @@ private final class SheetContent: CombinedComponent {
|
|||
contentSize.height += title.size.height
|
||||
contentSize.height += 56.0
|
||||
|
||||
let balance: StarsAmount?
|
||||
if case .accountWithdraw = component.mode {
|
||||
balance = state.starsBalance
|
||||
} else if case .reaction = component.mode {
|
||||
balance = state.starsBalance
|
||||
} else if case let .withdraw(starsState, _) = component.mode {
|
||||
balance = starsState.balances.availableBalance.amount
|
||||
} else {
|
||||
balance = nil
|
||||
var displayBalance = false
|
||||
var currentBalance = state.currency == .stars ? state.starsBalance : state.tonBalance
|
||||
switch component.mode {
|
||||
case .accountWithdraw, .reaction:
|
||||
displayBalance = true
|
||||
case let .suggestedPost(mode, _, _, _):
|
||||
switch mode {
|
||||
case let .sender(_, isFromAdmin):
|
||||
displayBalance = !isFromAdmin
|
||||
case .admin:
|
||||
break
|
||||
}
|
||||
case let .withdraw(starsState, _):
|
||||
currentBalance = starsState.balances.availableBalance.amount
|
||||
displayBalance = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let balance {
|
||||
let balanceTitle = balanceTitle.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: environment.strings.Stars_Transfer_Balance,
|
||||
font: Font.regular(14.0),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
|
||||
if displayBalance {
|
||||
let balance = balance.update(
|
||||
component: BalanceComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
currency: state.currency,
|
||||
balance: currentBalance,
|
||||
alignment: .right
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
availableSize: CGSize(width: 200.0, height: 200.0),
|
||||
transition: .immediate
|
||||
)
|
||||
let balanceValue = balanceValue.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: presentationStringsFormattedNumber(balance, environment.dateTimeFormat.groupingSeparator),
|
||||
font: Font.semibold(16.0),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
let balanceIcon = balanceIcon.update(
|
||||
component: BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
let balanceFrame = CGRect(origin: CGPoint(x: context.availableSize.width - balance.size.width - 20.0, y: floor((66.0 - balance.size.height) * 0.5)), size: balance.size)
|
||||
context.add(balance
|
||||
.anchorPoint(CGPoint(x: 1.0, y: 0.0))
|
||||
.position(CGPoint(x: balanceFrame.maxX, y: balanceFrame.minY))
|
||||
)
|
||||
}
|
||||
|
||||
let topBalanceOriginY = 11.0
|
||||
context.add(balanceTitle
|
||||
.position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceTitle.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height / 2.0))
|
||||
)
|
||||
context.add(balanceIcon
|
||||
.position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceIcon.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 + 1.0 + UIScreenPixel))
|
||||
)
|
||||
context.add(balanceValue
|
||||
.position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceIcon.size.width + 3.0 + balanceValue.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 + 2.0 - UIScreenPixel))
|
||||
)
|
||||
}
|
||||
|
||||
var tonBalanceValue: StarsAmount = .zero
|
||||
if let tonBalance = state.tonBalance {
|
||||
tonBalanceValue = tonBalance
|
||||
|
|
@ -358,10 +310,9 @@ private final class SheetContent: CombinedComponent {
|
|||
tonTitle = environment.strings.Chat_PostSuggestion_Suggest_RequestTon
|
||||
}
|
||||
case .starGiftOffer:
|
||||
//TODO:localize
|
||||
displayCurrencySelector = true
|
||||
starsTitle = "Offer Stars"
|
||||
tonTitle = "Offer TON"
|
||||
starsTitle = environment.strings.Gift_Offer_OfferStars
|
||||
tonTitle = environment.strings.Gift_Offer_OfferTon
|
||||
default:
|
||||
starsTitle = ""
|
||||
tonTitle = ""
|
||||
|
|
@ -555,9 +506,9 @@ private final class SheetContent: CombinedComponent {
|
|||
let string: String
|
||||
switch state.currency {
|
||||
case .stars:
|
||||
string = "Enter the number of Stars you'd like to offer for **\(giftTitle)**."
|
||||
string = environment.strings.Gift_Offer_PriceDescriptionStars(giftTitle).string
|
||||
case .ton:
|
||||
string = "Enter the number of TON you'd like to offer for **\(giftTitle)**."
|
||||
string = environment.strings.Gift_Offer_PriceDescriptionTon(giftTitle).string
|
||||
}
|
||||
let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(string, attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
amountFooter = AnyComponent(MultilineTextComponent(
|
||||
|
|
@ -801,9 +752,9 @@ private final class SheetContent: CombinedComponent {
|
|||
let footerString: String
|
||||
switch state.currency {
|
||||
case .stars:
|
||||
footerString = "Choose how long **\(peerName)** can accept your offer. When the time expires, your Stars will be automatically refunded."
|
||||
footerString = environment.strings.Gift_Offer_DurationDescriptionStars(peerName).string
|
||||
case .ton:
|
||||
footerString = "Choose how long **\(peerName)** can accept your offer. When the time expires, your TON will be automatically refunded."
|
||||
footerString = environment.strings.Gift_Offer_DurationDescriptionTon(peerName).string
|
||||
}
|
||||
|
||||
let periodFooterString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(footerString, attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
|
|
@ -813,12 +764,7 @@ private final class SheetContent: CombinedComponent {
|
|||
))
|
||||
|
||||
let hours = state.duration / 3600
|
||||
let durationString: String
|
||||
if hours == 1 {
|
||||
durationString = "1 Hour"
|
||||
} else {
|
||||
durationString = "\(hours) Hours"
|
||||
}
|
||||
let durationString = environment.strings.Gift_Offer_Duration_Hours(hours)
|
||||
|
||||
let periodSection = periodSection.update(
|
||||
component: ListSectionComponent(
|
||||
|
|
@ -834,7 +780,7 @@ private final class SheetContent: CombinedComponent {
|
|||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Offer Duration",
|
||||
string: environment.strings.Gift_Offer_Duration,
|
||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: environment.theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
|
|
@ -930,9 +876,9 @@ private final class SheetContent: CombinedComponent {
|
|||
currencySymbol = "$"
|
||||
currencyAmount = formatTonAmountText(amount.value, dateTimeFormat: environment.dateTimeFormat, maxDecimalPositions: nil)
|
||||
}
|
||||
buttonString = "Offer \(currencySymbol) \(currencyAmount)"
|
||||
buttonString = "\(environment.strings.Gift_Offer_Offer) \(currencySymbol) \(currencyAmount)"
|
||||
} else {
|
||||
buttonString = "Offer"
|
||||
buttonString = environment.strings.Gift_Offer_Offer
|
||||
}
|
||||
} else if let amount = state.amount {
|
||||
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
|
||||
|
|
@ -1085,10 +1031,11 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
|
||||
if state.isPickingDuration {
|
||||
let sourceFrame = CGRect(origin: CGPoint(x: durationFrame.maxX - 52.0, y: durationFrame.minY + 120.0), size: CGSize(width: 52.0, height: 52.0))
|
||||
let durationPicker = durationPicker.update(
|
||||
component: MenuComponent(
|
||||
theme: theme,
|
||||
sourceFrame: durationFrame.offsetBy(dx: 0.0, dy: 120.0),
|
||||
sourceFrame: sourceFrame,
|
||||
component: AnyComponent(DurationMenuComponent(
|
||||
theme: theme,
|
||||
strings: environment.strings,
|
||||
|
|
@ -1147,6 +1094,7 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
fileprivate var component: SheetContent
|
||||
|
||||
fileprivate var forceUpdateAmount = false
|
||||
fileprivate var amount: StarsAmount?
|
||||
fileprivate var currency: CurrencyAmount.Currency = .stars
|
||||
fileprivate var timestamp: Int32?
|
||||
|
|
@ -1246,7 +1194,7 @@ private final class SheetContent: CombinedComponent {
|
|||
self.currency = .stars
|
||||
}
|
||||
} else {
|
||||
let _ = (context.engine.payments.cachedStarGifts()
|
||||
let _ = (self.context.engine.payments.cachedStarGifts()
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] gifts in
|
||||
|
|
@ -1264,6 +1212,7 @@ private final class SheetContent: CombinedComponent {
|
|||
}
|
||||
if case let .generic(genericGift) = matchingGift, let minResaleStars = genericGift.availability?.minResaleStars {
|
||||
self.amount = StarsAmount(value: max(minResaleStars, resaleConfiguration.starGiftResaleMinStarsAmount), nanos: 0)
|
||||
self.forceUpdateAmount = true
|
||||
self.updated()
|
||||
}
|
||||
})
|
||||
|
|
@ -1417,6 +1366,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||
|
||||
if let view = self.node.hostView.findTaggedView(tag: amountTag) as? AmountFieldComponent.View {
|
||||
Queue.mainQueue().after(0.01) {
|
||||
view.resetValue()
|
||||
view.activateInput()
|
||||
view.selectAll()
|
||||
}
|
||||
|
|
@ -1451,6 +1401,14 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||
case .ton:
|
||||
break
|
||||
}
|
||||
} else if case .starGiftOffer = self.mode {
|
||||
switch currency {
|
||||
case .stars:
|
||||
text = presentationData.strings.Gift_Offer_GiftMinAmountToast_Text("\(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(clamping: minAmount)))").string
|
||||
case .ton:
|
||||
let amountString = formatTonAmountText(minAmount, dateTimeFormat: presentationData.dateTimeFormat) + " TON"
|
||||
text = presentationData.strings.Gift_Offer_GiftMinAmountToast_Text(amountString).string
|
||||
}
|
||||
}
|
||||
|
||||
let resultController = UndoOverlayController(
|
||||
|
|
@ -1810,6 +1768,7 @@ public final class AmountFieldComponent: Component {
|
|||
return
|
||||
}
|
||||
self.textField.text = "\(value)"
|
||||
self.placeholderView.view?.isHidden = self.textField.text?.isEmpty ?? false
|
||||
}
|
||||
|
||||
func update(component: AmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
|
|
@ -1829,10 +1788,11 @@ public final class AmountFieldComponent: Component {
|
|||
text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), maxDecimalPositions: nil))"
|
||||
}
|
||||
self.textField.text = text
|
||||
self.didSetValueOnce = true
|
||||
self.placeholderView.view?.isHidden = text.isEmpty
|
||||
} else {
|
||||
self.textField.text = ""
|
||||
}
|
||||
self.didSetValueOnce = true
|
||||
}
|
||||
self.textField.font = Font.regular(17.0)
|
||||
|
||||
|
|
@ -2683,14 +2643,8 @@ private final class DurationMenuComponent: Component {
|
|||
self.itemViews[value] = itemView
|
||||
}
|
||||
|
||||
let repeatString: String
|
||||
let hours = value / 3600
|
||||
//TODO:localize
|
||||
if hours == 1 {
|
||||
repeatString = "1 hour"
|
||||
} else {
|
||||
repeatString = "\(hours) hours"
|
||||
}
|
||||
let repeatString: String = component.strings.Gift_Offer_Duration_Hours(Int32(hours))
|
||||
|
||||
let itemSize = itemView.update(
|
||||
transition: transition,
|
||||
|
|
|
|||
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.
|
|
@ -2401,7 +2401,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
|
||||
switch buttonType {
|
||||
case 0:
|
||||
let _ = strongSelf.context.engine.payments.resolveStarGiftOffer(messageId: message.id, accept: false).startStandalone()
|
||||
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: self.presentationData.strings.Chat_GiftPurchaseOffer_RejectConfirmation_Title, text: self.presentationData.strings.Chat_GiftPurchaseOffer_RejectConfirmation_Text(peer.compactDisplayTitle).string, actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Chat_GiftPurchaseOffer_RejectConfirmation_Reject, action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self.context.engine.payments.resolveStarGiftOffer(messageId: message.id, accept: false).startStandalone()
|
||||
})
|
||||
], parseMarkdown: true), in: .window(.root))
|
||||
})
|
||||
case 1:
|
||||
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ import GlassBackgroundComponent
|
|||
import AttachmentFileController
|
||||
import NewContactScreen
|
||||
import PasskeysScreen
|
||||
import GiftDemoScreen
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
|
|
@ -3856,7 +3857,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)
|
||||
}
|
||||
|
||||
|
|
@ -3872,6 +3873,14 @@ 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 makeGiftDemoScreen(context: AccountContext) -> ViewController {
|
||||
return GiftDemoScreen(context: context)
|
||||
}
|
||||
|
||||
public func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController {
|
||||
let editorSubject: Signal<MediaEditorScreenImpl.Subject?, NoError>
|
||||
switch subject {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue