Stars giveaways

This commit is contained in:
Ilya Laktyushin 2024-08-27 20:42:47 +04:00
parent 9ec342bc71
commit 7aaebe5d48
8 changed files with 90 additions and 63 deletions

View File

@ -12855,3 +12855,24 @@ Sorry for the inconvenience.";
"Stats.RevenueInTon" = "Revenue in TON";
"Stats.RevenueInStars" = "Revenue in Stars";
"Stats.RevenueInUsd" = "Revenue in USD";
"BoostGift.NewDescriptionGroup" = "Get more boosts and members for\nyour group by giving away prizes.";
"BoostGift.NewDescription" = "Get more boosts and subscribers for\nyour channel by giving away prizes.";
"BoostGift.Prize" = "PRIZE";
"BoostGift.Prize.Premium" = "Telegram Premium";
"BoostGift.Prize.Stars" = "Telegram Stars";
"BoostGift.Stars.Title" = "STARS TO DISTRIBUTE";
"BoostGift.Stars.Boosts_1" = "%@ BOOST";
"BoostGift.Stars.Boosts_any" = "%@ BOOSTS";
"BoostGift.Stars.Stars_1" = "%@ Star";
"BoostGift.Stars.Stars_any" = "%@ Stars";
"BoostGift.Stars.PerUser" = "%@ per user";
"BoostGift.Stars.ShowMoreOptions" = "Show More Options";
"BoostGift.Stars.Info" = "Choose how many stars to give away and how many boosts to receive for 1 year.";
"BoostGift.AdditionalPrizesInfoStarsOff" = "Turn this on if you want to give the winners your own prizes in addition to Stars.";
"BoostGift.Stars.Winners" = "NUMBER OF WINNERS";
"BoostGift.Stars.WinnersInfo" = "Choose how many winners you want to distribute stars among.";
"BoostGift.Group.StarsDateInfo" = "Choose when %@ of your group will be randomly selected to receive Stars.";
"BoostGift.StarsDateInfo" = "Choose when %@ of your channel will be randomly selected to receive Stars.";

View File

@ -88,7 +88,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
case prepaid(PresentationTheme, String, String, PrepaidGiveaway)
case starsHeader(PresentationTheme, String, String)
case stars(Int32, PresentationTheme, Int32, String, String, String, Bool)
case stars(Int32, PresentationTheme, Int32, String, String, String, Bool, Int32)
case starsMore(PresentationTheme, String)
case starsInfo(PresentationTheme, String)
@ -163,7 +163,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
return 3
case .starsHeader:
return 4
case let .stars(_, _, stars, _, _, _, _):
case let .stars(_, _, stars, _, _, _, _, _):
return 5 + stars
case .starsMore:
return 100000
@ -262,8 +262,8 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
} else {
return false
}
case let .stars(lhsIndex, lhsTheme, lhsStars, lhsTitle, lhsSubtitle, lhsLabel, lhsIsSelected):
if case let .stars(rhsIndex, rhsTheme, rhsStars, rhsTitle, rhsSubtitle, rhsLabel, rhsIsSelected) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStars == rhsStars, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel, lhsIsSelected == rhsIsSelected {
case let .stars(lhsIndex, lhsTheme, lhsStars, lhsTitle, lhsSubtitle, lhsLabel, lhsIsSelected, lhsMaxWinners):
if case let .stars(rhsIndex, rhsTheme, rhsStars, rhsTitle, rhsSubtitle, rhsLabel, rhsIsSelected, rhsMaxWinners) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStars == rhsStars, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel, lhsIsSelected == rhsIsSelected, lhsMaxWinners == rhsMaxWinners {
return true
} else {
return false
@ -472,11 +472,12 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity * 4)", subtitle: subtitle, sectionId: self.section, action: nil)
case let .starsHeader(_, text, additionalText):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
case let .stars(_, _, stars, title, subtitle, label, isSelected):
case let .stars(_, _, stars, title, subtitle, label, isSelected, maxWinners):
return GiftOptionItem(presentationData: presentationData, context: arguments.context, title: title, subtitle: subtitle, subtitleFont: .small, label: .generic(label), badge: nil, isSelected: isSelected, stars: Int64(stars), sectionId: self.section, action: {
arguments.updateState { state in
var updatedState = state
updatedState.stars = Int64(stars)
updatedState.winners = min(updatedState.winners, maxWinners)
return updatedState
}
})
@ -753,11 +754,10 @@ private func createGiveawayControllerEntries(
recipientsText = presentationData.strings.BoostGift_CreateGiveawayInfo //presentationData.strings.BoostGift_SelectRecipients
}
//TODO:localize
entries.append(.modeHeader(presentationData.theme, "PRIZE"))
entries.append(.giftPremium(presentationData.theme, "Telegram Premium", recipientsText, state.mode == .giveaway || state.mode == .gift))
entries.append(.modeHeader(presentationData.theme, presentationData.strings.BoostGift_Prize.uppercased()))
entries.append(.giftPremium(presentationData.theme, presentationData.strings.BoostGift_Prize_Premium, recipientsText, state.mode == .giveaway || state.mode == .gift))
entries.append(.giftStars(presentationData.theme, "Telegram Stars", presentationData.strings.BoostGift_CreateGiveawayInfo, state.mode == .starsGiveaway))
entries.append(.giftStars(presentationData.theme, presentationData.strings.BoostGift_Prize_Stars, presentationData.strings.BoostGift_CreateGiveawayInfo, state.mode == .starsGiveaway))
case let .prepaid(prepaidGiveaway):
entries.append(.prepaidHeader(presentationData.theme, presentationData.strings.BoostGift_PrepaidGiveawayTitle))
entries.append(.prepaid(presentationData.theme, presentationData.strings.BoostGift_PrepaidGiveawayCount(prepaidGiveaway.quantity), presentationData.strings.BoostGift_PrepaidGiveawayMonths("\(prepaidGiveaway.months)").string, prepaidGiveaway))
@ -765,30 +765,32 @@ private func createGiveawayControllerEntries(
if case .starsGiveaway = state.mode, !starsGiveawayOptions.isEmpty {
let selectedOption = starsGiveawayOptions.first(where: { $0.giveawayOption.count == state.stars })!
entries.append(.starsHeader(presentationData.theme, "STARS TO DISTRIBUTE".uppercased(), "\(selectedOption.giveawayOption.yearlyBoosts) BOOSTS"))
entries.append(.starsHeader(presentationData.theme, presentationData.strings.BoostGift_Stars_Title.uppercased(), presentationData.strings.BoostGift_Stars_Boosts(selectedOption.giveawayOption.yearlyBoosts).uppercased()))
var i: Int32 = 0
for product in starsGiveawayOptions {
if !state.starsExpanded && product.giveawayOption.isExtended {
continue
}
let giftTitle: String = "\(product.giveawayOption.count) Stars"
let giftTitle: String = presentationData.strings.BoostGift_Stars_Stars(Int32(product.giveawayOption.count))
let winners = product.giveawayOption.winners.first(where: { $0.users == state.winners }) ?? product.giveawayOption.winners.first!
let subtitle = "\(winners.starsPerUser) per user"
let maxWinners = product.giveawayOption.winners.sorted(by: { $0.users < $1.users }).last?.users ?? 1
let subtitle = presentationData.strings.BoostGift_Stars_PerUser("\(winners.starsPerUser)").string
let label = product.storeProduct.price
let isSelected = product.giveawayOption.count == state.stars
entries.append(.stars(i, presentationData.theme, Int32(product.giveawayOption.count), giftTitle, subtitle, label, isSelected))
entries.append(.stars(i, presentationData.theme, Int32(product.giveawayOption.count), giftTitle, subtitle, label, isSelected, maxWinners))
i += 1
}
if !state.starsExpanded {
entries.append(.starsMore(presentationData.theme, "Show More Options"))
entries.append(.starsMore(presentationData.theme, presentationData.strings.BoostGift_Stars_ShowMoreOptions))
}
entries.append(.starsInfo(presentationData.theme, "Choose how many stars to give away and how many boosts to receive for 1 year."))
entries.append(.starsInfo(presentationData.theme, presentationData.strings.BoostGift_Stars_Info))
}
let appendDurationEntries = {
@ -847,9 +849,11 @@ private func createGiveawayControllerEntries(
if let selectedOption = starsGiveawayOptions.first(where: { $0.giveawayOption.count == state.stars }) {
values = selectedOption.giveawayOption.winners.map { $0.users }
}
entries.append(.subscriptionsHeader(presentationData.theme, "NUMBER OF WINNERS", ""))
entries.append(.subscriptions(presentationData.theme, state.winners, values))
entries.append(.subscriptionsInfo(presentationData.theme, "Choose how many winners you want to distribute stars among."))
if values.count > 1 {
entries.append(.subscriptionsHeader(presentationData.theme, presentationData.strings.BoostGift_Stars_Winners, ""))
entries.append(.subscriptions(presentationData.theme, state.winners, values))
entries.append(.subscriptionsInfo(presentationData.theme, presentationData.strings.BoostGift_Stars_WinnersInfo))
}
} else {
if case .generic = subject {
entries.append(.subscriptionsHeader(presentationData.theme, presentationData.strings.BoostGift_QuantityTitle.uppercased(), presentationData.strings.BoostGift_QuantityBoosts(state.subscriptions * 4)))
@ -904,7 +908,7 @@ private func createGiveawayControllerEntries(
}
entries.append(.prizeDescription(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizes, state.showPrizeDescription))
var prizeDescriptionInfoText = state.mode == .starsGiveaway ? "Turn this on if you want to give the winners your own prizes in addition to Stars." : presentationData.strings.BoostGift_AdditionalPrizesInfoOff
var prizeDescriptionInfoText = state.mode == .starsGiveaway ? presentationData.strings.BoostGift_AdditionalPrizesInfoStarsOff : presentationData.strings.BoostGift_AdditionalPrizesInfoOff
if state.showPrizeDescription {
entries.append(.prizeDescriptionText(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizesPlaceholder, state.prizeDescription, state.subscriptions))
@ -926,13 +930,13 @@ private func createGiveawayControllerEntries(
let timeInfoText: String
if isGroup {
if case .starsGiveaway = state.mode {
timeInfoText = "Choose when \(presentationData.strings.BoostGift_Group_DateInfoMembers(Int32(state.winners))) of your group will be randomly selected to receive Stars."
timeInfoText = presentationData.strings.BoostGift_Group_StarsDateInfo(presentationData.strings.BoostGift_Group_DateInfoMembers(Int32(state.winners))).string
} else {
timeInfoText = presentationData.strings.BoostGift_Group_DateInfo(presentationData.strings.BoostGift_Group_DateInfoMembers(Int32(state.subscriptions))).string
}
} else {
if case .starsGiveaway = state.mode {
timeInfoText = "Choose when \(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.winners))) of your channel will be randomly selected to receive Stars."
timeInfoText = presentationData.strings.BoostGift_StarsDateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.winners))).string
} else {
timeInfoText = presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string
}
@ -1142,9 +1146,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
}
let _ = isGroupValue.swap(isGroup)
//TODO:localize
let headerText = isGroup ? "Get more boosts and members for\nyour group by giving away prizes." : "Get more boosts and subscribers for\nyour channel by giving away prizes."
let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.BoostGift_Title, text: headerText, cancel: {
let headerText = isGroup ? presentationData.strings.BoostGift_NewDescriptionGroup : presentationData.strings.BoostGift_NewDescription
let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.BoostGift_Title, text: headerText, isStars: state.mode == .starsGiveaway, cancel: {
dismissImpl?()
})

View File

@ -15,13 +15,15 @@ final class CreateGiveawayHeaderItem: ItemListControllerHeaderItem {
let strings: PresentationStrings
let title: String
let text: String
let isStars: Bool
let cancel: () -> Void
init(theme: PresentationTheme, strings: PresentationStrings, title: String, text: String, cancel: @escaping () -> Void) {
init(theme: PresentationTheme, strings: PresentationStrings, title: String, text: String, isStars: Bool, cancel: @escaping () -> Void) {
self.theme = theme
self.strings = strings
self.title = title
self.text = text
self.isStars = isStars
self.cancel = cancel
}

View File

@ -272,10 +272,21 @@ public func presentGiveawayInfoController(
title = presentationData.strings.Chat_Giveaway_Info_EndedTitle
let intro: String
if isGroup {
intro = presentationData.strings.Chat_Giveaway_Info_Group_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string
if stars > 0 {
let starsString = presentationData.strings.Chat_Giveaway_Info_Stars_Stars(Int32(stars))
if isGroup {
intro = presentationData.strings.Chat_Giveaway_Info_Stars_Group_EndedIntro(peerName, starsString).string
} else {
intro = presentationData.strings.Chat_Giveaway_Info_Stars_EndedIntro(peerName, starsString).string
}
} else {
intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity), presentationData.strings.Chat_Giveaway_Info_Months(months)).string
let subscriptionsString = presentationData.strings.Chat_Giveaway_Info_Subscriptions(quantity)
let monthsString = presentationData.strings.Chat_Giveaway_Info_Months(months)
if isGroup {
intro = presentationData.strings.Chat_Giveaway_Info_Group_EndedIntro(peerName, subscriptionsString, monthsString).string
} else {
intro = presentationData.strings.Chat_Giveaway_Info_EndedIntro(peerName, subscriptionsString, monthsString).string
}
}
var ending: String

View File

@ -255,7 +255,7 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
textNode.isHidden = false
var position = params.leftInset + 18.0 + delta * CGFloat(i)
if i == textNodes.count - 1 {
position -= textSize.width
position -= textSize.width / 2.0 + 2.0
} else if i > 0 {
position -= textSize.width / 2.0
}
@ -290,30 +290,12 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
}
@objc func sliderValueChanged() {
guard let sliderView = self.sliderView else {
guard let sliderView = self.sliderView, let item = self.item else {
return
}
var mappedValue: Int32 = 1
switch Int(sliderView.value) {
case 0:
mappedValue = 1
case 1:
mappedValue = 3
case 2:
mappedValue = 5
case 3:
mappedValue = 7
case 4:
mappedValue = 10
case 5:
mappedValue = 25
case 6:
mappedValue = 50
default:
mappedValue = 1
let value = Int(sliderView.value)
if value >= 0 && value < item.values.count {
self.item?.updated(item.values[value])
}
self.item?.updated(Int32(mappedValue))
}
}

View File

@ -29,6 +29,8 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
"//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode",
"//submodules/TelegramUI/Components/ChatControllerInteraction",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/TextFormat",
],
visibility = [
"//visibility:public",

View File

@ -19,6 +19,8 @@ import ChatMessageBubbleContentNode
import ChatMessageItemCommon
import ChatMessageAttachedContentButtonNode
import ChatControllerInteraction
import TextNodeWithEntities
import TextFormat
private let titleFont = Font.medium(15.0)
private let textFont = Font.regular(13.0)
@ -48,7 +50,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
private let dateTextNode: TextNode
private let badgeBackgroundNode: ASImageNode
private let badgeTextNode: TextNode
private let badgeTextNode: TextNodeWithEntities
private var giveaway: TelegramMediaGiveaway?
@ -104,7 +106,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
self.badgeBackgroundNode = ASImageNode()
self.badgeBackgroundNode.displaysAsynchronously = false
self.badgeTextNode = TextNode()
self.badgeTextNode = TextNodeWithEntities()
self.buttonNode = ChatMessageAttachedContentButtonNode()
self.channelButtons = PeerButtonsStackNode()
@ -126,7 +128,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
self.addSubnode(self.channelButtons)
self.addSubnode(self.animationNode)
self.addSubnode(self.badgeBackgroundNode)
self.addSubnode(self.badgeTextNode)
self.addSubnode(self.badgeTextNode.textNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
@ -221,7 +223,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
let makeDateTitleLayout = TextNode.asyncLayout(self.dateTitleNode)
let makeDateTextLayout = TextNode.asyncLayout(self.dateTextNode)
let makeBadgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
let makeBadgeTextLayout = TextNodeWithEntities.asyncLayout(self.badgeTextNode)
let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode)
@ -267,7 +269,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
case .premium:
badgeText = "X\(giveaway.quantity)"
case let .stars(amount):
badgeText = "\(amount)"
badgeText = "⭐️\(presentationStringsFormattedNumber(Int32(amount), item.presentationData.dateTimeFormat.groupingSeparator)) "
isStars = true
}
} else if let giveawayResults {
@ -275,14 +277,18 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
case .premium:
badgeText = "X\(giveawayResults.winnersCount)"
case let .stars(amount):
badgeText = "\(amount)"
badgeText = "⭐️\(presentationStringsFormattedNumber(Int32(amount), item.presentationData.dateTimeFormat.groupingSeparator)) "
isStars = true
}
} else {
badgeText = ""
}
let badgeString = NSAttributedString(string: badgeText, font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: badgeTextColor)
let badgeString = NSMutableAttributedString(string: badgeText, font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: badgeTextColor)
if let range = badgeString.string.range(of: "⭐️") {
badgeString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: NSRange(range, in: badgeString.string))
badgeString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: badgeString.string))
}
var updatedBadgeImage: UIImage?
if themeUpdated {
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: isStars ? UIColor(rgb: 0xffaf0a) : accentColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
@ -690,7 +696,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
strongSelf.giveaway = giveaway
let displaysAsynchronously = !item.presentationData.isPreview
strongSelf.badgeTextNode.displaysAsynchronously = displaysAsynchronously
strongSelf.badgeTextNode.textNode.displaysAsynchronously = displaysAsynchronously
strongSelf.prizeTitleNode.displaysAsynchronously = displaysAsynchronously
strongSelf.prizeTextNode.displaysAsynchronously = displaysAsynchronously
strongSelf.additionalPrizeTextNode.displaysAsynchronously = displaysAsynchronously
@ -705,7 +711,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
strongSelf.updateVisibility()
let _ = badgeTextApply()
let _ = badgeTextApply(TextNodeWithEntities.Arguments(context: item.context, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: .clear, attemptSynchronous: true))
let _ = prizeTitleApply()
let _ = prizeTextApply()
let _ = additionalPrizeSeparatorApply()
@ -728,7 +734,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
strongSelf.animationNode.updateLayout(size: iconSize)
let badgeTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - badgeTextLayout.size.width) / 2.0) + 1.0, y: originY + 88.0), size: badgeTextLayout.size)
strongSelf.badgeTextNode.frame = badgeTextFrame
strongSelf.badgeTextNode.textNode.frame = badgeTextFrame
strongSelf.badgeBackgroundNode.frame = badgeTextFrame.insetBy(dx: -6.0, dy: -5.0).offsetBy(dx: -1.0, dy: 0.0)
if let updatedBadgeImage {
strongSelf.badgeBackgroundNode.image = updatedBadgeImage

View File

@ -1217,7 +1217,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
theme: forceDark ? .dark : .default
)
self.navigationPresentation = .standaloneFlatModal
self.navigationPresentation = .flatModal
self.automaticallyControlPresentationContextLayout = false
openPeerImpl = { [weak self] peer in