Stars giveaways [skip ci]

This commit is contained in:
Ilya Laktyushin
2024-08-23 18:44:16 +04:00
parent bc33cc7e9b
commit 5941becc33
41 changed files with 1623 additions and 476 deletions

View File

@@ -33,8 +33,9 @@ private final class CreateGiveawayControllerArguments {
let scrollToDescription: () -> Void
let setItemIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void
let removeChannel: (EnginePeer.Id) -> Void
let expandStars: () -> Void
init(context: AccountContext, updateState: @escaping ((CreateGiveawayControllerState) -> CreateGiveawayControllerState) -> Void, dismissInput: @escaping () -> Void, openPeersSelection: @escaping () -> Void, openChannelsSelection: @escaping () -> Void, openCountriesSelection: @escaping () -> Void, openPremiumIntro: @escaping () -> Void, scrollToDate: @escaping () -> Void, scrollToDescription: @escaping () -> Void, setItemIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removeChannel: @escaping (EnginePeer.Id) -> Void) {
init(context: AccountContext, updateState: @escaping ((CreateGiveawayControllerState) -> CreateGiveawayControllerState) -> Void, dismissInput: @escaping () -> Void, openPeersSelection: @escaping () -> Void, openChannelsSelection: @escaping () -> Void, openCountriesSelection: @escaping () -> Void, openPremiumIntro: @escaping () -> Void, scrollToDate: @escaping () -> Void, scrollToDescription: @escaping () -> Void, setItemIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removeChannel: @escaping (EnginePeer.Id) -> Void, expandStars: @escaping () -> Void) {
self.context = context
self.updateState = updateState
self.dismissInput = dismissInput
@@ -46,12 +47,14 @@ private final class CreateGiveawayControllerArguments {
self.scrollToDescription = scrollToDescription
self.setItemIdWithRevealedOptions = setItemIdWithRevealedOptions
self.removeChannel = removeChannel
self.expandStars = expandStars
}
}
private enum CreateGiveawaySection: Int32 {
case header
case mode
case stars
case subscriptions
case channels
case users
@@ -77,14 +80,20 @@ private enum CreateGiveawayEntryTag: ItemListItemTag {
private enum CreateGiveawayEntry: ItemListNodeEntry {
case header(PresentationTheme, String, String)
case createGiveaway(PresentationTheme, String, String, Bool)
case awardUsers(PresentationTheme, String, String, Bool)
case modeHeader(PresentationTheme, String)
case giftPremium(PresentationTheme, String, String, Bool)
case giftStars(PresentationTheme, String, String, Bool)
case prepaidHeader(PresentationTheme, String)
case prepaid(PresentationTheme, String, String, PrepaidGiveaway)
case starsHeader(PresentationTheme, String, String)
case stars(Int32, PresentationTheme, Int32, String, String, String, Bool)
case starsMore(PresentationTheme, String)
case starsInfo(PresentationTheme, String)
case subscriptionsHeader(PresentationTheme, String, String)
case subscriptions(PresentationTheme, Int32)
case subscriptions(PresentationTheme, Int32, [Int32])
case subscriptionsInfo(PresentationTheme, String)
case channelsHeader(PresentationTheme, String)
@@ -117,8 +126,10 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
switch self {
case .header:
return CreateGiveawaySection.header.rawValue
case .createGiveaway, .awardUsers, .prepaidHeader, .prepaid:
case .modeHeader, .giftPremium, .giftStars, .prepaidHeader, .prepaid:
return CreateGiveawaySection.mode.rawValue
case .starsHeader, .stars, .starsMore, .starsInfo:
return CreateGiveawaySection.stars.rawValue
case .subscriptionsHeader, .subscriptions, .subscriptionsInfo:
return CreateGiveawaySection.subscriptions.rawValue
case .channelsHeader, .channel, .channelAdd, .channelsInfo:
@@ -139,61 +150,71 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
var stableId: Int32 {
switch self {
case .header:
return -2
case .modeHeader:
return -1
case .createGiveaway:
case .giftPremium:
return 0
case .awardUsers:
case .giftStars:
return 1
case .prepaidHeader:
return 2
case .prepaid:
return 3
case .subscriptionsHeader:
case .starsHeader:
return 4
case let .stars(_, _, stars, _, _, _, _):
return 5 + stars
case .starsMore:
return 100000
case .starsInfo:
return 100001
case .subscriptionsHeader:
return 100002
case .subscriptions:
return 5
return 100003
case .subscriptionsInfo:
return 6
return 100004
case .channelsHeader:
return 7
return 100005
case let .channel(index, _, _, _, _):
return 8 + index
return 100006 + index
case .channelAdd:
return 100
return 100200
case .channelsInfo:
return 101
return 100201
case .usersHeader:
return 102
return 100202
case .usersAll:
return 103
return 100203
case .usersNew:
return 104
return 100204
case .usersInfo:
return 105
return 100205
case .durationHeader:
return 106
return 100206
case let .duration(index, _, _, _, _, _, _, _):
return 107 + index
return 100207 + index
case .durationInfo:
return 200
return 100300
case .prizeDescription:
return 201
return 100301
case .prizeDescriptionText:
return 202
return 100302
case .prizeDescriptionInfo:
return 203
case .winners:
return 204
case .winnersInfo:
return 205
return 100303
case .timeHeader:
return 206
return 100304
case .timeExpiryDate:
return 207
return 100305
case .timeCustomPicker:
return 208
return 100306
case .timeInfo:
return 209
return 100307
case .winners:
return 100308
case .winnersInfo:
return 100309
}
}
@@ -205,14 +226,20 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
} else {
return false
}
case let .createGiveaway(lhsTheme, lhsText, lhsSubtext, lhsSelected):
if case let .createGiveaway(rhsTheme, rhsText, rhsSubtext, rhsSelected) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsSubtext == rhsSubtext, lhsSelected == rhsSelected {
case let .modeHeader(lhsTheme, lhsText):
if case let .modeHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .awardUsers(lhsTheme, lhsText, lhsSubtext, lhsSelected):
if case let .awardUsers(rhsTheme, rhsText, rhsSubtext, rhsSelected) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsSubtext == rhsSubtext, lhsSelected == rhsSelected {
case let .giftPremium(lhsTheme, lhsText, lhsSubtext, lhsSelected):
if case let .giftPremium(rhsTheme, rhsText, rhsSubtext, rhsSelected) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsSubtext == rhsSubtext, lhsSelected == rhsSelected {
return true
} else {
return false
}
case let .giftStars(lhsTheme, lhsText, lhsSubtext, lhsSelected):
if case let .giftStars(rhsTheme, rhsText, rhsSubtext, rhsSelected) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsSubtext == rhsSubtext, lhsSelected == rhsSelected {
return true
} else {
return false
@@ -229,14 +256,38 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
} else {
return false
}
case let .starsHeader(lhsTheme, lhsText, lhsAdditionalText):
if case let .starsHeader(rhsTheme, rhsText, rhsAdditionalText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsAdditionalText == rhsAdditionalText {
return true
} 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 {
return true
} else {
return false
}
case let .starsMore(lhsTheme, lhsText):
if case let .starsMore(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .starsInfo(lhsTheme, lhsText):
if case let .starsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .subscriptionsHeader(lhsTheme, lhsText, lhsAdditionalText):
if case let .subscriptionsHeader(rhsTheme, rhsText, rhsAdditionalText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsAdditionalText == rhsAdditionalText {
return true
} else {
return false
}
case let .subscriptions(lhsTheme, lhsValue):
if case let .subscriptions(rhsTheme, rhsValue) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue {
case let .subscriptions(lhsTheme, lhsValue, lhsValues):
if case let .subscriptions(rhsTheme, rhsValue, rhsValues) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsValues == rhsValues {
return true
} else {
return false
@@ -379,29 +430,31 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
switch self {
case let .header(_, title, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(title + text), sectionId: self.section)
case let .createGiveaway(_, title, subtitle, isSelected):
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: .blue, name: "Premium/Giveaway"), title: title, subtitle: subtitle, isSelected: isSelected, sectionId: self.section, action: {
arguments.updateState { state in
var updatedState = state
updatedState.mode = .giveaway
return updatedState
}
})
case let .awardUsers(_, title, subtitle, isSelected):
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: .violet, name: "Media Editor/Privacy/SelectedUsers"), title: title, subtitle: subtitle, subtitleActive: true, isSelected: isSelected, sectionId: self.section, action: {
case let .modeHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .giftPremium(_, title, subtitle, isSelected):
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: .premium, name: "Peer Info/PremiumIcon"), title: title, subtitle: subtitle, subtitleActive: true, isSelected: isSelected, sectionId: self.section, action: {
var openSelection = false
arguments.updateState { state in
var updatedState = state
if state.mode == .gift || state.peers.isEmpty {
if (state.mode == .giveaway && state.peers.isEmpty) {
openSelection = true
}
updatedState.mode = .gift
updatedState.mode = .giveaway
return updatedState
}
if openSelection {
arguments.openPeersSelection()
}
})
case let .giftStars(_, title, subtitle, isSelected):
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: .stars, name: "Peer Info/PremiumIcon"), title: title, subtitle: subtitle, subtitleActive: false, isSelected: isSelected, sectionId: self.section, action: {
arguments.updateState { state in
var updatedState = state
updatedState.mode = .starsGiveaway
return updatedState
}
})
case let .prepaidHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .prepaid(_, title, subtitle, prepaidGiveaway):
@@ -417,13 +470,33 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
color = .blue
}
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 .subscriptionsHeader(_, text, additionalText):
case let .starsHeader(_, text, additionalText):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
case let .subscriptions(_, value):
return SubscriptionsCountItem(theme: presentationData.theme, strings: presentationData.strings, value: value, sectionId: self.section, updated: { value in
case let .stars(_, _, stars, title, subtitle, label, isSelected):
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.subscriptions = value
updatedState.stars = Int64(stars)
return updatedState
}
})
case let .starsMore(theme, title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandStars()
})
case let .starsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .subscriptionsHeader(_, text, additionalText):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
case let .subscriptions(_, value, values):
return SubscriptionsCountItem(theme: presentationData.theme, strings: presentationData.strings, value: value, values: values, sectionId: self.section, updated: { value in
arguments.updateState { state in
var updatedState = state
if state.mode == .giveaway {
updatedState.subscriptions = value
} else if state.mode == .starsGiveaway {
updatedState.winners = value
}
return updatedState
}
})
@@ -626,6 +699,19 @@ private struct PremiumGiftProduct: Equatable {
}
}
private struct StarsGiveawayProduct: Equatable {
let giveawayOption: StarsGiveawayOption
let storeProduct: InAppPurchaseManager.Product
var id: String {
return self.storeProduct.id
}
var price: String {
return self.storeProduct.price
}
}
private func createGiveawayControllerEntries(
peerId: EnginePeer.Id,
subject: CreateGiveawaySubject,
@@ -635,6 +721,7 @@ private func createGiveawayControllerEntries(
peers: [EnginePeer.Id: EnginePeer],
products: [PremiumGiftProduct],
defaultPrice: (Int64, NSDecimalNumber),
starsGiveawayOptions: [StarsGiveawayProduct],
minDate: Int32,
maxDate: Int32
) -> [CreateGiveawayEntry] {
@@ -647,10 +734,8 @@ private func createGiveawayControllerEntries(
switch subject {
case .generic:
entries.append(.createGiveaway(presentationData.theme, presentationData.strings.BoostGift_CreateGiveaway, presentationData.strings.BoostGift_CreateGiveawayInfo, state.mode == .giveaway))
let recipientsText: String
if !state.peers.isEmpty {
if !state.peers.isEmpty && state.mode == .gift {
var peerNamesArray: [String] = []
let peersCount = state.peers.count
for peerId in state.peers.prefix(2) {
@@ -665,14 +750,47 @@ private func createGiveawayControllerEntries(
recipientsText = presentationData.strings.PremiumGift_LabelRecipients(Int32(peersCount))
}
} else {
recipientsText = presentationData.strings.BoostGift_SelectRecipients
recipientsText = presentationData.strings.BoostGift_CreateGiveawayInfo //presentationData.strings.BoostGift_SelectRecipients
}
entries.append(.awardUsers(presentationData.theme, presentationData.strings.BoostGift_AwardSpecificUsers, recipientsText, state.mode == .gift))
//TODO:localize
entries.append(.modeHeader(presentationData.theme, "PRIZE"))
entries.append(.giftPremium(presentationData.theme, "Telegram Premium", recipientsText, state.mode == .giveaway || state.mode == .gift))
entries.append(.giftStars(presentationData.theme, "Telegram 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))
}
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"))
var i: Int32 = 0
for product in starsGiveawayOptions {
if !state.starsExpanded && product.giveawayOption.isExtended {
continue
}
let giftTitle: String = "\(product.giveawayOption.count) Stars"
let winners = product.giveawayOption.winners.first(where: { $0.users == state.winners }) ?? product.giveawayOption.winners.first!
let subtitle = "\(winners.starsPerUser) per user"
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))
i += 1
}
if !state.starsExpanded {
entries.append(.starsMore(presentationData.theme, "Show More Options"))
}
entries.append(.starsInfo(presentationData.theme, "Choose how many stars to give away and how many boosts to receive for 1 year."))
}
let appendDurationEntries = {
entries.append(.durationHeader(presentationData.theme, presentationData.strings.BoostGift_DurationTitle.uppercased()))
@@ -682,6 +800,8 @@ private func createGiveawayControllerEntries(
recipientCount = Int(state.subscriptions)
case .gift:
recipientCount = state.peers.count
case .starsGiveaway:
recipientCount = Int(state.subscriptions)
}
var i: Int32 = 0
@@ -721,11 +841,21 @@ private func createGiveawayControllerEntries(
}
switch state.mode {
case .giveaway:
if case .generic = subject {
entries.append(.subscriptionsHeader(presentationData.theme, presentationData.strings.BoostGift_QuantityTitle.uppercased(), presentationData.strings.BoostGift_QuantityBoosts(state.subscriptions * 4)))
entries.append(.subscriptions(presentationData.theme, state.subscriptions))
entries.append(.subscriptionsInfo(presentationData.theme, presentationData.strings.BoostGift_QuantityInfo))
case .giveaway, .starsGiveaway:
if case .starsGiveaway = state.mode {
var values: [Int32] = [1]
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."))
} else {
if case .generic = subject {
entries.append(.subscriptionsHeader(presentationData.theme, presentationData.strings.BoostGift_QuantityTitle.uppercased(), presentationData.strings.BoostGift_QuantityBoosts(state.subscriptions * 4)))
entries.append(.subscriptions(presentationData.theme, state.subscriptions, [1, 3, 5, 7, 10, 25, 50]))
entries.append(.subscriptionsInfo(presentationData.theme, presentationData.strings.BoostGift_QuantityInfo))
}
}
entries.append(.channelsHeader(presentationData.theme, isGroup ? presentationData.strings.BoostGift_GroupsAndChannelsTitle.uppercased() : presentationData.strings.BoostGift_ChannelsAndGroupsTitle.uppercased()))
@@ -765,12 +895,16 @@ private func createGiveawayControllerEntries(
entries.append(.usersNew(presentationData.theme, isGroup ? presentationData.strings.BoostGift_Group_OnlyNewMembers : presentationData.strings.BoostGift_OnlyNewSubscribers, countriesText, state.onlyNewEligible))
entries.append(.usersInfo(presentationData.theme, isGroup ? presentationData.strings.BoostGift_Group_LimitMembersInfo : presentationData.strings.BoostGift_LimitSubscribersInfo))
if case .generic = subject {
appendDurationEntries()
if case .starsGiveaway = state.mode {
} else {
if case .generic = subject {
appendDurationEntries()
}
}
entries.append(.prizeDescription(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizes, state.showPrizeDescription))
var prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOff
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
if state.showPrizeDescription {
entries.append(.prizeDescriptionText(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizesPlaceholder, state.prizeDescription, state.subscriptions))
@@ -785,18 +919,28 @@ private func createGiveawayControllerEntries(
}
}
entries.append(.prizeDescriptionInfo(presentationData.theme, prizeDescriptionInfoText))
entries.append(.winners(presentationData.theme, presentationData.strings.BoostGift_Winners, state.showWinners))
entries.append(.winnersInfo(presentationData.theme, presentationData.strings.BoostGift_WinnersInfo))
entries.append(.timeHeader(presentationData.theme, presentationData.strings.BoostGift_DateTitle.uppercased()))
entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time, minDate, maxDate, state.pickingExpiryDate, state.pickingExpiryTime))
let timeInfoText: String
if isGroup {
entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_Group_DateInfo(presentationData.strings.BoostGift_Group_DateInfoMembers(Int32(state.subscriptions))).string))
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."
} else {
timeInfoText = presentationData.strings.BoostGift_Group_DateInfo(presentationData.strings.BoostGift_Group_DateInfoMembers(Int32(state.subscriptions))).string
}
} else {
entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string))
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."
} else {
timeInfoText = presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string
}
}
entries.append(.timeInfo(presentationData.theme, timeInfoText))
entries.append(.winners(presentationData.theme, presentationData.strings.BoostGift_Winners, state.showWinners))
entries.append(.winnersInfo(presentationData.theme, presentationData.strings.BoostGift_WinnersInfo))
case .gift:
appendDurationEntries()
}
@@ -808,10 +952,13 @@ private struct CreateGiveawayControllerState: Equatable {
enum Mode {
case giveaway
case gift
case starsGiveaway
}
var mode: Mode
var subscriptions: Int32
var stars: Int64
var winners: Int32
var channels: [EnginePeer.Id] = []
var peers: [EnginePeer.Id] = []
var selectedMonths: Int32?
@@ -825,6 +972,7 @@ private struct CreateGiveawayControllerState: Equatable {
var pickingExpiryDate = false
var revealedItemId: EnginePeer.Id? = nil
var updating = false
var starsExpanded = false
}
public enum CreateGiveawaySubject {
@@ -857,7 +1005,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let minDate = currentTime + 60 * 1
let maxDate = currentTime + context.userLimits.maxGiveawayPeriodSeconds
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, time: expiryTime)
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, stars: 500, winners: 5, time: expiryTime)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
@@ -868,6 +1016,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let isGroupValue = Atomic<Bool>(value: false)
let productsValue = Atomic<[PremiumGiftProduct]?>(value: nil)
let starsValue = Atomic<[StarsGiveawayProduct]?>(value: nil)
var buyActionImpl: (() -> Void)?
var openPeersSelectionImpl: (() -> Void)?
@@ -912,6 +1061,13 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
updatedState.channels = updatedState.channels.filter { $0 != id }
return updatedState
}
},
expandStars: {
updateState { state in
var updatedState = state
updatedState.starsExpanded = true
return updatedState
}
})
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
@@ -938,6 +1094,20 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
return (gifts, defaultPrice)
}
let starsGiveawayOptions: Signal<[StarsGiveawayProduct], NoError> = combineLatest(
.single([]) |> then(context.engine.payments.starsGiveawayOptions()),
context.inAppPurchaseManager?.availableProducts ?? .single([])
)
|> map { options, products in
var result: [StarsGiveawayProduct] = []
for option in options {
if let product = products.first(where: { $0.id == option.storeProductId }), !product.isSubscription {
result.append(StarsGiveawayProduct(giveawayOption: option, storeProduct: product))
}
}
return result
}
let previousState = Atomic<CreateGiveawayControllerState?>(value: nil)
let signal = combineLatest(
presentationData,
@@ -952,10 +1122,11 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
return (state, peers)
}
},
productsAndDefaultPrice
productsAndDefaultPrice,
starsGiveawayOptions
)
|> deliverOnMainQueue
|> map { presentationData, stateAndPeersMap, productsAndDefaultPrice -> (ItemListControllerState, (ItemListNodeState, Any)) in
|> map { presentationData, stateAndPeersMap, productsAndDefaultPrice, starsGiveawayOptions -> (ItemListControllerState, (ItemListNodeState, Any)) in
var presentationData = presentationData
let (products, defaultPrice) = productsAndDefaultPrice
@@ -971,7 +1142,9 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
}
let _ = isGroupValue.swap(isGroup)
let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, strings: presentationData.strings, title: presentationData.strings.BoostGift_Title, text: isGroup ? presentationData.strings.BoostGift_Group_Description : presentationData.strings.BoostGift_Description, cancel: {
//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: {
dismissImpl?()
})
@@ -981,6 +1154,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
badgeCount = state.subscriptions * 4
case .gift:
badgeCount = Int32(state.peers.count) * 4
case .starsGiveaway:
badgeCount = Int32(state.stars) / 500
}
let footerItem = CreateGiveawayFooterItem(theme: presentationData.theme, title: state.mode == .gift ? presentationData.strings.BoostGift_GiftPremium : presentationData.strings.BoostGift_StartGiveaway, badgeCount: badgeCount, isLoading: state.updating, action: {
if case .prepaid = subject {
@@ -995,6 +1170,10 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let leftNavigationButton = ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {})
let _ = productsValue.swap(products)
let previousStars = starsValue.swap(starsGiveawayOptions)
if (previousStars ?? []).isEmpty && !starsGiveawayOptions.isEmpty {
}
let previousState = previousState.swap(state)
var animateChanges = false
@@ -1014,6 +1193,9 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
if previousState.showPrizeDescription != state.showPrizeDescription {
animateChanges = true
}
if previousState.starsExpanded != state.starsExpanded {
animateChanges = true
}
}
var peers: [EnginePeer.Id: EnginePeer] = [:]
@@ -1024,7 +1206,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(""), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createGiveawayControllerEntries(peerId: peerId, subject: subject, state: state, presentationData: presentationData, locale: locale, peers: peers, products: products, defaultPrice: defaultPrice, minDate: minDate, maxDate: maxDate), style: .blocks, emptyStateItem: nil, headerItem: headerItem, footerItem: footerItem, crossfadeState: false, animateChanges: animateChanges)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createGiveawayControllerEntries(peerId: peerId, subject: subject, state: state, presentationData: presentationData, locale: locale, peers: peers, products: products, defaultPrice: defaultPrice, starsGiveawayOptions: starsGiveawayOptions, minDate: minDate, maxDate: maxDate), style: .blocks, emptyStateItem: nil, headerItem: headerItem, footerItem: footerItem, crossfadeState: false, animateChanges: animateChanges)
return (controllerState, (listState, arguments))
}
@@ -1063,7 +1245,9 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
return
}
var selectedProduct: PremiumGiftProduct?
var selectedStarsProduct: StarsGiveawayProduct?
let selectedMonths = state.selectedMonths ?? 12
let selectedStars = state.stars
switch state.mode {
case .giveaway:
if let product = products.first(where: { $0.months == selectedMonths && $0.giftOption.users == state.subscriptions }) {
@@ -1073,18 +1257,27 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
if let product = products.first(where: { $0.months == selectedMonths && $0.giftOption.users == 1 }) {
selectedProduct = product
}
case .starsGiveaway:
guard let starsOptions = starsValue.with({ $0 }), !starsOptions.isEmpty else {
return
}
if let product = starsOptions.first(where: { $0.giveawayOption.count == selectedStars }) {
selectedStarsProduct = product
}
}
guard let selectedProduct else {
let alertController = textAlertController(context: context, title: presentationData.strings.BoostGift_ReduceQuantity_Title, text: presentationData.strings.BoostGift_ReduceQuantity_Text("\(state.subscriptions)", "\(selectedMonths)", "\(25)").string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.BoostGift_ReduceQuantity_Reduce, action: {
updateState { state in
var updatedState = state
updatedState.subscriptions = 25
return updatedState
}
})], parseMarkdown: true)
presentControllerImpl?(alertController)
return
if [.gift, .giveaway].contains(state.mode) {
guard let _ = selectedProduct else {
let alertController = textAlertController(context: context, title: presentationData.strings.BoostGift_ReduceQuantity_Title, text: presentationData.strings.BoostGift_ReduceQuantity_Text("\(state.subscriptions)", "\(selectedMonths)", "\(25)").string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.BoostGift_ReduceQuantity_Reduce, action: {
updateState { state in
var updatedState = state
updatedState.subscriptions = 25
return updatedState
}
})], parseMarkdown: true)
presentControllerImpl?(alertController)
return
}
}
updateState { state in
@@ -1093,25 +1286,48 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
return updatedState
}
let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount
let purpose: AppStoreTransactionPurpose
let quantity: Int32
var storeProduct: InAppPurchaseManager.Product?
switch state.mode {
case .giveaway:
guard let selectedProduct else {
return
}
let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let untilDate = max(state.time, currentTime + 60)
purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, showWinners: state.showWinners, prizeDescription: state.prizeDescription.isEmpty ? nil : state.prizeDescription, randomId: Int64.random(in: .min ..< .max), untilDate: untilDate, currency: currency, amount: amount)
quantity = selectedProduct.giftOption.storeQuantity
storeProduct = selectedProduct.storeProduct
case .gift:
guard let selectedProduct else {
return
}
let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount
purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount)
quantity = Int32(state.peers.count)
storeProduct = selectedProduct.storeProduct
case .starsGiveaway:
guard let selectedStarsProduct else {
return
}
let (currency, amount) = selectedStarsProduct.storeProduct.priceCurrencyAndAmount
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let untilDate = max(state.time, currentTime + 60)
purpose = .starsGiveaway(stars: selectedStarsProduct.giveawayOption.count, boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, showWinners: state.showWinners, prizeDescription: state.prizeDescription.isEmpty ? nil : state.prizeDescription, randomId: Int64.random(in: .min ..< .max), untilDate: untilDate, currency: currency, amount: amount, users: state.winners)
quantity = 1
storeProduct = selectedStarsProduct.storeProduct
}
guard let storeProduct else {
return
}
let _ = (context.engine.payments.canPurchasePremium(purpose: purpose)
|> deliverOnMainQueue).startStandalone(next: { [weak controller] available in
if available, let inAppPurchaseManager = context.inAppPurchaseManager {
let _ = (inAppPurchaseManager.buyProduct(selectedProduct.storeProduct, quantity: quantity, purpose: purpose)
let _ = (inAppPurchaseManager.buyProduct(storeProduct, quantity: quantity, purpose: purpose)
|> deliverOnMainQueue).startStandalone(next: { [weak controller] status in
if case .purchased = status {
if let controller, let navigationController = controller.navigationController as? NavigationController {
@@ -1133,7 +1349,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let title: String
let text: String
switch state.mode {
case .giveaway:
case .giveaway, .starsGiveaway:
title = presentationData.strings.BoostGift_GiveawayCreated_Title
text = isGroup ? presentationData.strings.BoostGift_Group_GiveawayCreated_Text : presentationData.strings.BoostGift_GiveawayCreated_Text
case .gift:
@@ -1248,6 +1464,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
updatedState.peers = privacy.additionallyIncludePeers
if updatedState.peers.isEmpty {
updatedState.mode = .giveaway
} else {
updatedState.mode = .gift
}
return updatedState
}