Hosted gifts

This commit is contained in:
Ilya Laktyushin 2025-09-26 03:16:01 +04:00
parent 7dfd3b1d87
commit deb549bd95
3 changed files with 159 additions and 12 deletions

View file

@ -15104,3 +15104,10 @@ Error: %8$@";
"PeerInfo.ToastNoteCopied" = "Note copied to clipboard.";
"MESSAGE_SUGGEST_BIRTHDAY" = "%@ suggested you your birthday";
"Gift.UnavailableAction.Title" = "Action Locked";
"Gift.UnavailableAction.Text" = "Transfer this gift to your Telegram account on Fragment to unlock this action.";
"Gift.UnavailableAction.OpenFragment" = "Open Fragment";
"Gift.UnavailableAction.OpenFragment_URL" = "https://fragment.com";
"Gift.Unique.Telegram" = "Telegram";

View file

@ -184,6 +184,9 @@ private final class GiftViewSheetContent: CombinedComponent {
if case let .peerId(peerId) = gift.owner {
peerIds.append(peerId)
}
if let peerId = gift.hostPeerId {
peerIds.append(peerId)
}
for attribute in gift.attributes {
if case let .originalInfo(senderPeerId, recipientPeerId, _, _, _) = attribute {
if let senderPeerId {
@ -997,11 +1000,37 @@ private final class GiftViewSheetContent: CombinedComponent {
}
}
func presentActionLockedForHostedGift() {
guard let controller = self.getController() as? GiftViewScreen else {
return
}
let context = self.context
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let alertController = textAlertController(
context: context,
title: presentationData.strings.Gift_UnavailableAction_Title,
text: presentationData.strings.Gift_UnavailableAction_Text,
actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Gift_UnavailableAction_OpenFragment, action: {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.Gift_UnavailableAction_OpenFragment_URL, forceExternal: true, presentationData: presentationData, navigationController: controller.navigationController as? NavigationController, dismissInput: {})
}),
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
],
actionLayout: .vertical
)
controller.present(alertController, in: .window(.root))
}
func transferGift() {
guard let arguments = self.subject.arguments, let controller = self.getController() as? GiftViewScreen, case let .unique(gift) = arguments.gift, let reference = arguments.reference, let transferStars = arguments.transferStars else {
return
}
guard gift.hostPeerId == nil else {
self.presentActionLockedForHostedGift()
return
}
controller.dismissAllTooltips()
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
@ -1062,6 +1091,11 @@ private final class GiftViewSheetContent: CombinedComponent {
return
}
guard gift.hostPeerId == nil else {
self.presentActionLockedForHostedGift()
return
}
let isTablet = controller.validLayout?.metrics.isTablet ?? false
controller.dismissAllTooltips()
@ -2199,6 +2233,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let wearDescription = Child(MultilineTextComponent.self)
let wearPerks = Child(List<Empty>.self)
let hostedDescription = Child(MultilineTextComponent.self)
let hiddenText = Child(MultilineTextComponent.self)
let table = Child(TableComponent.self)
let additionalText = Child(MultilineTextComponent.self)
@ -2253,7 +2288,8 @@ private final class GiftViewSheetContent: CombinedComponent {
var uniqueGift: StarGift.UniqueGift?
var isSelfGift = false
var isChannelGift = false
var isMyUniqueGift = false
var isMyOwnedUniqueGift = false
var isMyHostedUniqueGift = false
var releasedByPeer: EnginePeer?
var canGiftUpgrade = false
@ -2296,6 +2332,9 @@ private final class GiftViewSheetContent: CombinedComponent {
limitTotal = nil
convertStars = nil
uniqueGift = gift
if let hostPeerId = gift.hostPeerId, hostPeerId == component.context.account.peerId {
isMyHostedUniqueGift = true
}
}
savedToProfile = arguments.savedToProfile
if let reference = arguments.reference, case let .peer(peerId, _) = reference, peerId.namespace == Namespaces.Peer.CloudChannel {
@ -2309,8 +2348,8 @@ private final class GiftViewSheetContent: CombinedComponent {
isSelfGift = arguments.messageId?.peerId == component.context.account.peerId
if case let .peerId(peerId) = uniqueGift?.owner, peerId == component.context.account.peerId || isChannelGift {
isMyUniqueGift = true
if case let .peerId(peerId) = uniqueGift?.owner, peerId == component.context.account.peerId {
isMyOwnedUniqueGift = true
}
if isSelfGift {
@ -2384,10 +2423,11 @@ private final class GiftViewSheetContent: CombinedComponent {
let headerHeight: CGFloat
let headerSubject: GiftCompositionComponent.Subject?
if let uniqueGift, !state.inUpgradePreview {
if showWearPreview {
headerHeight = 200.0
} else if case let .peerId(peerId) = uniqueGift.owner, peerId == component.context.account.peerId || isChannelGift {
} else if isMyOwnedUniqueGift || isMyHostedUniqueGift || isChannelGift {
headerHeight = 314.0
} else {
headerHeight = 240.0
@ -3332,6 +3372,27 @@ private final class GiftViewSheetContent: CombinedComponent {
)
))
}
if let peerId = uniqueGift.hostPeerId, let peer = state.peerMap[peerId] {
tableItems.append(.init(
id: "telegram",
title: strings.Gift_Unique_Telegram,
component: AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
strings: strings,
peer: peer
)
),
action: { [weak state] in
state?.openPeer(peer)
}
))
))
}
} else if let peerId = subject.arguments?.fromPeerId, let peer = state.peerMap[peerId] {
var isBot = false
if case let .user(user) = peer, user.botInfo != nil {
@ -3413,10 +3474,11 @@ private final class GiftViewSheetContent: CombinedComponent {
}
if let uniqueGift {
if isMyUniqueGift, case let .peerId(peerId) = uniqueGift.owner {
if isMyOwnedUniqueGift || isMyHostedUniqueGift || isChannelGift {
var canTransfer = true
var canResell = true
if let peer = state.peerMap[peerId], case let .channel(channel) = peer {
if case let .peerId(peerId) = uniqueGift.owner, let peer = state.peerMap[peerId], case let .channel(channel) = peer {
if !channel.flags.contains(.isCreator) {
canTransfer = false
}
@ -3444,7 +3506,8 @@ private final class GiftViewSheetContent: CombinedComponent {
content: AnyComponent(
HeaderButtonComponent(
title: strings.Gift_View_Header_Transfer,
iconName: "Premium/Collectible/Transfer"
iconName: "Premium/Collectible/Transfer",
isLocked: !isMyOwnedUniqueGift
)
),
effectAlignment: .center,
@ -3535,7 +3598,8 @@ private final class GiftViewSheetContent: CombinedComponent {
content: AnyComponent(
HeaderButtonComponent(
title: (uniqueGift.resellAmounts ?? []).isEmpty ? strings.Gift_View_Sell : strings.Gift_View_Unlist,
iconName: (uniqueGift.resellAmounts ?? []).isEmpty ? "Premium/Collectible/Sell" : "Premium/Collectible/Unlist"
iconName: (uniqueGift.resellAmounts ?? []).isEmpty ? "Premium/Collectible/Sell" : "Premium/Collectible/Unlist",
isLocked: !isMyOwnedUniqueGift
)
),
effectAlignment: .center,
@ -3557,7 +3621,64 @@ private final class GiftViewSheetContent: CombinedComponent {
})
}
}
if isMyHostedUniqueGift, let address = uniqueGift.giftAddress {
let textFont = Font.regular(13.0)
let textColor = theme.list.itemSecondaryTextColor
let linkColor = theme.actionSheet.controlAccentColor
if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme)
}
let addressToOpen = address
var descriptionText = strings.Gift_View_TonGiftAddressInfo
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
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.cachedSmallChevronImage?.0 {
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
}
originY += 1.0
let hostedDescription = hostedDescription.update(
component: MultilineTextComponent(
text: .plain(attributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 5,
lineSpacing: 0.2,
insets: UIEdgeInsets(top: 0.0, left: 2.0, bottom: 0.0, right: 2.0),
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
}
},
tapAction: { [weak state] attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
state?.openAddress(addressToOpen)
}
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(hostedDescription
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + hostedDescription.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += hostedDescription.size.height
originY += 14.0
}
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
.model, .pattern, .backdrop, .originalInfo
]
@ -4074,7 +4195,7 @@ private final class GiftViewSheetContent: CombinedComponent {
ownerName = ""
}
descriptionText = strings.Gift_View_SellingGiftInfo(ownerName).string
} else if let uniqueGift, let address = uniqueGift.giftAddress, case .address = uniqueGift.owner {
} else if let uniqueGift, let address = uniqueGift.giftAddress, case .address = uniqueGift.owner, !isMyHostedUniqueGift {
addressToOpen = address
descriptionText = strings.Gift_View_TonGiftAddressInfo
} else {
@ -4464,7 +4585,7 @@ private final class GiftViewSheetContent: CombinedComponent {
availableSize: buttonSize,
transition: context.transition
)
} else if !incoming, let resellAmount, !isMyUniqueGift {
} else if !incoming, let resellAmount, !isMyOwnedUniqueGift {
if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme {
state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme)
}
@ -5784,7 +5905,7 @@ private final class HeaderButtonComponent: CombinedComponent {
var totalTitleWidth = title.size.width
var titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
if component.isLocked {
let titleSpacing: CGFloat = 2.0
let titleSpacing: CGFloat = 3.0
let lockIcon = lockIcon.update(
component: BundleIconComponent(
name: "Chat List/StatusLockIcon",

View file

@ -1326,6 +1326,25 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
return
}
let context = self.context
guard uniqueGift.hostPeerId == nil else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let alertController = textAlertController(
context: context,
title: presentationData.strings.Gift_UnavailableAction_Title,
text: presentationData.strings.Gift_UnavailableAction_Text,
actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Gift_UnavailableAction_OpenFragment, action: {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.Gift_UnavailableAction_OpenFragment_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}),
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
],
actionLayout: .vertical
)
self.parentController?.present(alertController, in: .window(.root))
return
}
let _ = (context.account.stateManager.contactBirthdays
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] birthdays in