mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
0044bde566
commit
6a09d2e4e6
17 changed files with 1014 additions and 266 deletions
|
|
@ -15507,7 +15507,9 @@ Error: %8$@";
|
|||
|
||||
"ChatList.Auctions.UpcomingAuction" = "Upcoming Auction";
|
||||
|
||||
"Gift.AuctionBid.UpcomingBid" = "You placed an early bid";
|
||||
"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";
|
||||
|
|
@ -15536,3 +15538,13 @@ Error: %8$@";
|
|||
"Passkeys.PasskeyCreatedAndUsedPattern" = "created %1$@ • used %2$@";
|
||||
"Passkeys.AddPasskey" = "Create Passkey";
|
||||
"Passkeys.ListFooter" = "Your passkeys are stored securely in your password manager.";
|
||||
|
||||
"Gifts.Demo.Title" = "Unique Collectibles";
|
||||
"Gifts.Demo.Description" = "Telegram Gifts are collectible items you can trade or showcase on your profile.";
|
||||
"Gifts.Demo.Unique.Title" = "Unique";
|
||||
"Gifts.Demo.Unique.Text" = "Upgrade your gifts to get a unique number, model, backdrop and symbol.";
|
||||
"Gifts.Demo.Tradable.Title" = "Tradable";
|
||||
"Gifts.Demo.Tradable.Text" = "Sell your gift on Telegram or on third-party NFT marketplaces.";
|
||||
"Gifts.Demo.Wearable.Title" = "Wearable";
|
||||
"Gifts.Demo.Wearable.Text" = "Display gifts on your page and set them as profile covers or statuses.";
|
||||
"Gifts.Demo.Understood" = "Understood";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1431,33 +1412,23 @@ public protocol SharedAccountContext: AnyObject {
|
|||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -499,6 +499,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Contacts/NewContactScreen",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu",
|
||||
"//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": [],
|
||||
|
|
|
|||
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.Gifts_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.Gifts_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.Gifts_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.Gifts_Demo_Unique_Title,
|
||||
titleColor: titleColor,
|
||||
text: strings.Gifts_Demo_Unique_Text,
|
||||
textColor: textColor,
|
||||
accentColor: iconColor,
|
||||
iconName: "Premium/Collectible/Unique",
|
||||
iconColor: iconColor
|
||||
))
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "tradable",
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gifts_Demo_Tradable_Title,
|
||||
titleColor: titleColor,
|
||||
text: strings.Gifts_Demo_Tradable_Text,
|
||||
textColor: textColor,
|
||||
accentColor: iconColor,
|
||||
iconName: "Premium/Collectible/Transferable",
|
||||
iconColor: iconColor
|
||||
))
|
||||
)
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "wearable",
|
||||
component: AnyComponent(InfoParagraphComponent(
|
||||
title: strings.Gifts_Demo_Wearable_Title,
|
||||
titleColor: titleColor,
|
||||
text: strings.Gifts_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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +63,7 @@ swift_library(
|
|||
"//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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -818,10 +818,8 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
tintColor: secondaryTextColor
|
||||
)),
|
||||
action: {
|
||||
let controller = component.context.sharedContext.makeGiftAuctionInfoScreen(
|
||||
context: component.context,
|
||||
auctionContext: component.auctionContext,
|
||||
completion: nil
|
||||
let controller = component.context.sharedContext.makeGiftDemoScreen(
|
||||
context: component.context
|
||||
)
|
||||
environment.controller()?.push(controller)
|
||||
},
|
||||
|
|
@ -1157,7 +1155,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,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import GiftItemComponent
|
|||
import GiftAnimationComponent
|
||||
import GlassBarButtonComponent
|
||||
import GiftRemainingCountComponent
|
||||
import AnimatedTextComponent
|
||||
|
||||
private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -63,7 +64,11 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
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
|
||||
|
|
@ -75,13 +80,13 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
|
||||
init(
|
||||
context: AccountContext,
|
||||
auctionContext: GiftAuctionContext,
|
||||
attributes: [StarGift.UniqueGift.Attribute]
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
for attribute in attributes {
|
||||
switch attribute {
|
||||
case .model:
|
||||
|
|
@ -123,9 +128,25 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
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()
|
||||
}
|
||||
|
|
@ -154,7 +175,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context, attributes: self.attributes)
|
||||
return State(context: self.context, auctionContext: self.auctionContext, attributes: self.attributes)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
|
|
@ -408,17 +429,18 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
contentHeight += 137.0
|
||||
|
||||
if case let .generic(gift) = component.auctionContext.gift, let availability = gift.availability {
|
||||
let remains: Int32 = availability.remains
|
||||
// if let auctionState = component.auctionContext.currentState {
|
||||
// switch auctionState.auctionState {
|
||||
// case let .ongoing(_, _, _, _, _, _, _, giftsLeft, _, _, _, _):
|
||||
// remains = giftsLeft
|
||||
// case .finished:
|
||||
// remains = 0
|
||||
// }
|
||||
// }
|
||||
let total: Int32 = availability.total
|
||||
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,
|
||||
|
|
@ -427,7 +449,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
inactiveValue: "",
|
||||
inactiveTitleColor: theme.list.itemSecondaryTextColor,
|
||||
activeTitle: "",
|
||||
activeValue: "",
|
||||
activeValue: sold > 0 ? strings.Gift_Send_Sold(sold) : "",
|
||||
activeTitleColor: .white,
|
||||
badgeText: "",
|
||||
badgePosition: position,
|
||||
|
|
@ -494,11 +516,77 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
}
|
||||
contentHeight += 80.0
|
||||
|
||||
var buttonTitle = strings.Gift_Auction_Join
|
||||
if component.auctionContext.isUpcoming {
|
||||
buttonTitle = strings.Gift_Auction_EarlyBid
|
||||
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(
|
||||
|
|
@ -511,8 +599,8 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
component: ButtonComponent(
|
||||
background: buttonBackground,
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable("ok"),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
|
||||
id: AnyHashable("bid"),
|
||||
component: AnyComponent(VStack(items, spacing: 1.0))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
|
|
@ -523,7 +611,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
}
|
||||
}),
|
||||
availableSize: buttonSize,
|
||||
transition: context.transition
|
||||
transition: .spring(duration: 0.2)
|
||||
)
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: button.size)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import GiftAnimationComponent
|
|||
import ChatThemeScreen
|
||||
import ProfileLevelRatingBarComponent
|
||||
import AnimatedTextComponent
|
||||
import InfoParagraphComponent
|
||||
|
||||
private final class GiftViewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -2885,7 +2886,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 +2900,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 +2914,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 +3032,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 +3046,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 +3060,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 +6047,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,6 +91,7 @@ import GlassBackgroundComponent
|
|||
import AttachmentFileController
|
||||
import NewContactScreen
|
||||
import PasskeysScreen
|
||||
import GiftDemoScreen
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
|
|
@ -3876,6 +3877,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
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