Giveaway improvements

This commit is contained in:
Ilya Laktyushin 2023-10-10 16:08:20 +04:00
parent d06e7af2ce
commit 91e7305050
6 changed files with 288 additions and 218 deletions

View File

@ -105,6 +105,7 @@ swift_library(
"//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem", "//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem",
"//submodules/TelegramUI/Components/ShareWithPeersScreen", "//submodules/TelegramUI/Components/ShareWithPeersScreen",
"//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -69,7 +69,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
case awardUsers(PresentationTheme, String, String, Bool) case awardUsers(PresentationTheme, String, String, Bool)
case prepaidHeader(PresentationTheme, String) case prepaidHeader(PresentationTheme, String)
case prepaid(PresentationTheme, String, String, Int32, Int32) case prepaid(PresentationTheme, String, String, PrepaidGiveaway)
case subscriptionsHeader(PresentationTheme, String, String) case subscriptionsHeader(PresentationTheme, String, String)
case subscriptions(PresentationTheme, Int32) case subscriptions(PresentationTheme, Int32)
@ -190,8 +190,8 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .prepaid(lhsTheme, lhsText, lhsSubtext, lhsBoosts, lhsMonths): case let .prepaid(lhsTheme, lhsText, lhsSubtext, lhsPrepaidGiveaway):
if case let .prepaid(rhsTheme, rhsText, rhsSubtext, rhsBoosts, rhsMonths) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsSubtext == rhsSubtext, lhsBoosts == rhsBoosts, lhsMonths == rhsMonths { if case let .prepaid(rhsTheme, rhsText, rhsSubtext, rhsPrepaidGiveaway) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsSubtext == rhsSubtext, lhsPrepaidGiveaway == rhsPrepaidGiveaway {
return true return true
} else { } else {
return false return false
@ -342,10 +342,9 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
}) })
case let .prepaidHeader(_, text): case let .prepaidHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .prepaid(_, title, subtitle, boosts, months): case let .prepaid(_, title, subtitle, prepaidGiveaway):
let _ = boosts
let color: GiftOptionItem.Icon.Color let color: GiftOptionItem.Icon.Color
switch months { switch prepaidGiveaway.months {
case 3: case 3:
color = .green color = .green
case 6: case 6:
@ -355,7 +354,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
default: default:
color = .blue color = .blue
} }
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(boosts), sectionId: self.section, action: nil) return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(prepaidGiveaway.quantity), sectionId: self.section, action: nil)
case let .subscriptionsHeader(_, text, additionalText): case let .subscriptionsHeader(_, text, additionalText):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
case let .subscriptions(_, value): case let .subscriptions(_, value):
@ -501,9 +500,9 @@ private func createGiveawayControllerEntries(peerId: EnginePeer.Id, subject: Cre
recipientsText = "select recipients" recipientsText = "select recipients"
} }
entries.append(.awardUsers(presentationData.theme, "Award Specific Users", recipientsText, state.mode == .gift)) entries.append(.awardUsers(presentationData.theme, "Award Specific Users", recipientsText, state.mode == .gift))
case let .prepaid(months, count): case let .prepaid(prepaidGiveaway):
entries.append(.prepaidHeader(presentationData.theme, "PREPAID GIVEAWAY")) entries.append(.prepaidHeader(presentationData.theme, "PREPAID GIVEAWAY"))
entries.append(.prepaid(presentationData.theme, "\(count) Telegram Premium", "\(months)-month subscriptions", count, months)) entries.append(.prepaid(presentationData.theme, "\(prepaidGiveaway.quantity) Telegram Premium", "\(prepaidGiveaway.months)-month subscriptions", prepaidGiveaway))
} }
if case .giveaway = state.mode { if case .giveaway = state.mode {
@ -607,15 +606,15 @@ private struct CreateGiveawayControllerState: Equatable {
public enum CreateGiveawaySubject { public enum CreateGiveawaySubject {
case generic case generic
case prepaid(months: Int32, count: Int32) case prepaid(PrepaidGiveaway)
} }
public func createGiveawayController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, subject: CreateGiveawaySubject, completion: (() -> Void)? = nil) -> ViewController { public func createGiveawayController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, subject: CreateGiveawaySubject, completion: (() -> Void)? = nil) -> ViewController {
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
let initialSubscriptions: Int32 let initialSubscriptions: Int32
if case let .prepaid(_, count) = subject { if case let .prepaid(prepaidGiveaway) = subject {
initialSubscriptions = count initialSubscriptions = prepaidGiveaway.quantity
} else { } else {
initialSubscriptions = 5 initialSubscriptions = 5
} }
@ -793,7 +792,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let purpose: AppStoreTransactionPurpose let purpose: AppStoreTransactionPurpose
switch state.mode { switch state.mode {
case .giveaway: case .giveaway:
purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId}, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time, currency: currency, amount: amount) purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId }, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time, currency: currency, amount: amount)
case .gift: case .gift:
purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount) purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount)
} }
@ -804,6 +803,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
return updatedState return updatedState
} }
switch subject {
case .generic:
let _ = (context.engine.payments.canPurchasePremium(purpose: purpose) let _ = (context.engine.payments.canPurchasePremium(purpose: purpose)
|> deliverOnMainQueue).startStandalone(next: { [weak controller] available in |> deliverOnMainQueue).startStandalone(next: { [weak controller] available in
if available, let inAppPurchaseManager = context.inAppPurchaseManager { if available, let inAppPurchaseManager = context.inAppPurchaseManager {
@ -888,6 +889,45 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
} }
} }
}) })
case let .prepaid(prepaidGiveaway):
let _ = (context.engine.payments.launchPrepaidGiveaway(peerId: peerId, id: prepaidGiveaway.id, additionalPeerIds: state.channels.filter { $0 != peerId }, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time)
|> deliverOnMainQueue).startStandalone(completed: {
if let controller, let navigationController = controller.navigationController as? NavigationController {
var controllers = navigationController.viewControllers
var count = 0
for c in controllers.reversed() {
if c is PeerInfoScreen {
if case .giveaway = state.mode {
count += 1
}
break
} else {
count += 1
}
}
controllers.removeLast(count)
navigationController.setViewControllers(controllers, animated: true)
let title = "Giveaway Created"
let text = "Check your channel's [Statistics]() to see how this giveaway boosted your channel."
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: title, text: text, customUndoText: nil, timeout: nil, linkAction: { [weak navigationController] _ in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: peerId))
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] statsDatacenterId in
guard let statsDatacenterId else {
return
}
let statsController = context.sharedContext.makeChannelStatsController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, boosts: true, boostStatus: nil, statsDatacenterId: statsDatacenterId)
navigationController?.pushViewController(statsController)
})
}), elevatedLayout: false, action: { _ in
return true
})
(controllers.last as? ViewController)?.present(tooltipController, in: .current)
}
})
break
}
} }
openPeersSelectionImpl = { openPeersSelectionImpl = {

View File

@ -18,6 +18,7 @@ import BalancedTextComponent
import ConfettiEffect import ConfettiEffect
import AvatarNode import AvatarNode
import TextFormat import TextFormat
import RoundedRectWithTailPath
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? { func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
@ -40,131 +41,6 @@ func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor
}) })
} }
private func generateBadgePath(rectSize: CGSize, tailPosition: CGFloat? = 0.5) -> UIBezierPath {
let cornerRadius: CGFloat = rectSize.height / 2.0
let tailWidth: CGFloat = 20.0
let tailHeight: CGFloat = 9.0
let tailRadius: CGFloat = 4.0
let rect = CGRect(origin: CGPoint(x: 0.0, y: tailHeight), size: rectSize)
guard let tailPosition else {
return UIBezierPath(cgPath: CGPath(roundedRect: rect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil))
}
let path = UIBezierPath()
path.move(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
var leftArcEndAngle: CGFloat = .pi / 2.0
var leftConnectionArcRadius = tailRadius
var tailLeftHalfWidth: CGFloat = tailWidth / 2.0
var tailLeftArcStartAngle: CGFloat = -.pi / 4.0
var tailLeftHalfRadius = tailRadius
var rightArcStartAngle: CGFloat = -.pi / 2.0
var rightConnectionArcRadius = tailRadius
var tailRightHalfWidth: CGFloat = tailWidth / 2.0
var tailRightArcStartAngle: CGFloat = .pi / 4.0
var tailRightHalfRadius = tailRadius
if tailPosition < 0.5 {
let fraction = max(0.0, tailPosition - 0.15) / 0.35
leftArcEndAngle *= fraction
let connectionFraction = max(0.0, tailPosition - 0.35) / 0.15
leftConnectionArcRadius *= connectionFraction
if tailPosition < 0.27 {
let fraction = tailPosition / 0.27
tailLeftHalfWidth *= fraction
tailLeftArcStartAngle *= fraction
tailLeftHalfRadius *= fraction
}
} else if tailPosition > 0.5 {
let tailPosition = 1.0 - tailPosition
let fraction = max(0.0, tailPosition - 0.15) / 0.35
rightArcStartAngle *= fraction
let connectionFraction = max(0.0, tailPosition - 0.35) / 0.15
rightConnectionArcRadius *= connectionFraction
if tailPosition < 0.27 {
let fraction = tailPosition / 0.27
tailRightHalfWidth *= fraction
tailRightArcStartAngle *= fraction
tailRightHalfRadius *= fraction
}
}
path.addArc(
withCenter: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius),
radius: cornerRadius,
startAngle: .pi,
endAngle: .pi + max(0.0001, leftArcEndAngle),
clockwise: true
)
let leftArrowStart = max(rect.minX, rect.minX + rectSize.width * tailPosition - tailLeftHalfWidth - leftConnectionArcRadius)
path.addArc(
withCenter: CGPoint(x: leftArrowStart, y: rect.minY - leftConnectionArcRadius),
radius: leftConnectionArcRadius,
startAngle: .pi / 2.0,
endAngle: .pi / 4.0,
clockwise: false
)
path.addLine(to: CGPoint(x: max(rect.minX, rect.minX + rectSize.width * tailPosition - tailLeftHalfRadius), y: rect.minY - tailHeight))
path.addArc(
withCenter: CGPoint(x: rect.minX + rectSize.width * tailPosition, y: rect.minY - tailHeight + tailRadius / 2.0),
radius: tailRadius,
startAngle: -.pi / 2.0 + tailLeftArcStartAngle,
endAngle: -.pi / 2.0 + tailRightArcStartAngle,
clockwise: true
)
path.addLine(to: CGPoint(x: min(rect.maxX, rect.minX + rectSize.width * tailPosition + tailRightHalfRadius), y: rect.minY - tailHeight))
let rightArrowStart = min(rect.maxX, rect.minX + rectSize.width * tailPosition + tailRightHalfWidth + rightConnectionArcRadius)
path.addArc(
withCenter: CGPoint(x: rightArrowStart, y: rect.minY - rightConnectionArcRadius),
radius: rightConnectionArcRadius,
startAngle: .pi - .pi / 4.0,
endAngle: .pi / 2.0,
clockwise: false
)
path.addArc(
withCenter: CGPoint(x: rect.minX + rectSize.width - cornerRadius, y: rect.minY + cornerRadius),
radius: cornerRadius,
startAngle: min(-0.0001, rightArcStartAngle),
endAngle: 0.0,
clockwise: true
)
path.addLine(to: CGPoint(x: rect.minX + rectSize.width, y: rect.minY + rectSize.height - cornerRadius))
path.addArc(
withCenter: CGPoint(x: rect.minX + rectSize.width - cornerRadius, y: rect.minY + rectSize.height - cornerRadius),
radius: cornerRadius,
startAngle: 0.0,
endAngle: .pi / 2.0,
clockwise: true
)
path.addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + rectSize.height))
path.addArc(
withCenter: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + rectSize.height - cornerRadius),
radius: cornerRadius,
startAngle: .pi / 2.0,
endAngle: .pi,
clockwise: true
)
return path
}
public class PremiumLimitDisplayComponent: Component { public class PremiumLimitDisplayComponent: Component {
private let inactiveColor: UIColor private let inactiveColor: UIColor
private let activeColors: [UIColor] private let activeColors: [UIColor]
@ -658,7 +534,7 @@ public class PremiumLimitDisplayComponent: Component {
if badgePosition > 1.0 - 0.15 { if badgePosition > 1.0 - 0.15 {
progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 1.0, y: 1.0)) progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 1.0, y: 1.0))
progressTransition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateBadgePath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 1.0).cgPath) progressTransition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 1.0).cgPath)
if let _ = self.badgeView.layer.animation(forKey: "appearance1") { if let _ = self.badgeView.layer.animation(forKey: "appearance1") {
@ -667,7 +543,7 @@ public class PremiumLimitDisplayComponent: Component {
} }
} else if badgePosition < 0.15 { } else if badgePosition < 0.15 {
progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.0, y: 1.0)) progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.0, y: 1.0))
progressTransition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateBadgePath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 0.0).cgPath) progressTransition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 0.0).cgPath)
if let _ = self.badgeView.layer.animation(forKey: "appearance1") { if let _ = self.badgeView.layer.animation(forKey: "appearance1") {
@ -676,7 +552,7 @@ public class PremiumLimitDisplayComponent: Component {
} }
} else { } else {
progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.5, y: 1.0)) progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.5, y: 1.0))
progressTransition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateBadgePath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 0.5).cgPath) progressTransition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 0.5).cgPath)
if let _ = self.badgeView.layer.animation(forKey: "appearance1") { if let _ = self.badgeView.layer.animation(forKey: "appearance1") {

View File

@ -34,9 +34,9 @@ private final class ChannelStatsControllerArguments {
let openPeer: (EnginePeer) -> Void let openPeer: (EnginePeer) -> Void
let expandBoosters: () -> Void let expandBoosters: () -> Void
let openGifts: () -> Void let openGifts: () -> Void
let createPrepaidGiveaway: (Int32, Int32) -> Void let createPrepaidGiveaway: (PrepaidGiveaway) -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (Int32, Int32) -> Void) { init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void) {
self.context = context self.context = context
self.loadDetailedGraph = loadDetailedGraph self.loadDetailedGraph = loadDetailedGraph
self.openMessageStats = openMessage self.openMessageStats = openMessage
@ -110,7 +110,7 @@ private enum StatsEntry: ItemListNodeEntry {
case boostOverview(PresentationTheme, ChannelBoostStatus) case boostOverview(PresentationTheme, ChannelBoostStatus)
case boostPrepaidTitle(PresentationTheme, String) case boostPrepaidTitle(PresentationTheme, String)
case boostPrepaid(Int32, PresentationTheme, String, String, Int32, Int32) case boostPrepaid(Int32, PresentationTheme, String, String, PrepaidGiveaway)
case boostPrepaidInfo(PresentationTheme, String) case boostPrepaidInfo(PresentationTheme, String)
case boostersTitle(PresentationTheme, String) case boostersTitle(PresentationTheme, String)
@ -219,7 +219,7 @@ private enum StatsEntry: ItemListNodeEntry {
return 2002 return 2002
case .boostPrepaidTitle: case .boostPrepaidTitle:
return 2003 return 2003
case let .boostPrepaid(index, _, _, _, _, _): case let .boostPrepaid(index, _, _, _, _):
return 2004 + index return 2004 + index
case .boostPrepaidInfo: case .boostPrepaidInfo:
return 2100 return 2100
@ -404,8 +404,8 @@ private enum StatsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .boostPrepaid(lhsIndex, lhsTheme, lhsTitle, lhsSubtitle, lhsMonths, lhsCount): case let .boostPrepaid(lhsIndex, lhsTheme, lhsTitle, lhsSubtitle, lhsPrepaidGiveaway):
if case let .boostPrepaid(rhsIndex, rhsTheme, rhsTitle, rhsSubtitle, rhsMonths, rhsCount) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsMonths == rhsMonths, lhsCount == rhsCount { if case let .boostPrepaid(rhsIndex, rhsTheme, rhsTitle, rhsSubtitle, rhsPrepaidGiveaway) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsPrepaidGiveaway == rhsPrepaidGiveaway {
return true return true
} else { } else {
return false return false
@ -561,9 +561,9 @@ private enum StatsEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addBoostsIcon(theme), title: title, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addBoostsIcon(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.openGifts() arguments.openGifts()
}) })
case let .boostPrepaid(_, _, title, subtitle, months, count): case let .boostPrepaid(_, _, title, subtitle, prepaidGiveaway):
let color: GiftOptionItem.Icon.Color let color: GiftOptionItem.Icon.Color
switch months { switch prepaidGiveaway.months {
case 3: case 3:
color = .green color = .green
case 6: case 6:
@ -573,8 +573,8 @@ private enum StatsEntry: ItemListNodeEntry {
default: default:
color = .blue color = .blue
} }
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(count), sectionId: self.section, action: { return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(prepaidGiveaway.quantity), sectionId: self.section, action: {
arguments.createPrepaidGiveaway(months, count) arguments.createPrepaidGiveaway(prepaidGiveaway)
}) })
} }
} }
@ -701,10 +701,15 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
entries.append(.boostOverview(presentationData.theme, boostData)) entries.append(.boostOverview(presentationData.theme, boostData))
//TODO:localize //TODO:localize
if !boostData.prepaidGiveaways.isEmpty {
entries.append(.boostPrepaidTitle(presentationData.theme, "PREPAID GIVEAWAYS")) entries.append(.boostPrepaidTitle(presentationData.theme, "PREPAID GIVEAWAYS"))
entries.append(.boostPrepaid(0, presentationData.theme, "70 Telegram Premium", "3-month subscriptions", 3, 70)) var i: Int32 = 0
entries.append(.boostPrepaid(1, presentationData.theme, "200 Telegram Premium", "6-month subscriptions", 6, 200)) for giveaway in boostData.prepaidGiveaways {
entries.append(.boostPrepaid(i, presentationData.theme, "\(giveaway.quantity) Telegram Premium", "\(giveaway.months)-month subscriptions", giveaway))
i += 1
}
entries.append(.boostPrepaidInfo(presentationData.theme, "Select a giveaway you already paid for to set it up.")) entries.append(.boostPrepaidInfo(presentationData.theme, "Select a giveaway you already paid for to set it up."))
}
let boostersTitle: String let boostersTitle: String
let boostersPlaceholder: String? let boostersPlaceholder: String?
@ -871,8 +876,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let controller = createGiveawayController(context: context, peerId: peerId, subject: .generic) let controller = createGiveawayController(context: context, peerId: peerId, subject: .generic)
pushImpl?(controller) pushImpl?(controller)
}, },
createPrepaidGiveaway: { months, count in createPrepaidGiveaway: { prepaidGiveaway in
let controller = createGiveawayController(context: context, peerId: peerId, subject: .prepaid(months: months, count: count)) let controller = createGiveawayController(context: context, peerId: peerId, subject: .prepaid(prepaidGiveaway))
pushImpl?(controller) pushImpl?(controller)
}) })

View File

@ -0,0 +1,18 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "RoundedRectWithTailPath",
module_name = "RoundedRectWithTailPath",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,130 @@
import Foundation
import UIKit
public func generateRoundedRectWithTailPath(rectSize: CGSize, cornerRadius: CGFloat? = nil, tailSize: CGSize = CGSize(width: 20.0, height: 9.0), tailRadius: CGFloat = 4.0, tailPosition: CGFloat? = 0.5, transformTail: Bool = true) -> UIBezierPath {
let cornerRadius: CGFloat = cornerRadius ?? rectSize.height / 2.0
let tailWidth: CGFloat = tailSize.width
let tailHeight: CGFloat = tailSize.height
let rect = CGRect(origin: CGPoint(x: 0.0, y: tailHeight), size: rectSize)
guard let tailPosition else {
return UIBezierPath(cgPath: CGPath(roundedRect: rect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil))
}
let cutoff: CGFloat = 0.27
let path = UIBezierPath()
path.move(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
var leftArcEndAngle: CGFloat = .pi / 2.0
var leftConnectionArcRadius = tailRadius
var tailLeftHalfWidth: CGFloat = tailWidth / 2.0
var tailLeftArcStartAngle: CGFloat = -.pi / 4.0
var tailLeftHalfRadius = tailRadius
var rightArcStartAngle: CGFloat = -.pi / 2.0
var rightConnectionArcRadius = tailRadius
var tailRightHalfWidth: CGFloat = tailWidth / 2.0
var tailRightArcStartAngle: CGFloat = .pi / 4.0
var tailRightHalfRadius = tailRadius
if transformTail {
if tailPosition < 0.5 {
let fraction = max(0.0, tailPosition - 0.15) / 0.35
leftArcEndAngle *= fraction
let connectionFraction = max(0.0, tailPosition - 0.35) / 0.15
leftConnectionArcRadius *= connectionFraction
if tailPosition < cutoff {
let fraction = tailPosition / cutoff
tailLeftHalfWidth *= fraction
tailLeftArcStartAngle *= fraction
tailLeftHalfRadius *= fraction
}
} else if tailPosition > 0.5 {
let tailPosition = 1.0 - tailPosition
let fraction = max(0.0, tailPosition - 0.15) / 0.35
rightArcStartAngle *= fraction
let connectionFraction = max(0.0, tailPosition - 0.35) / 0.15
rightConnectionArcRadius *= connectionFraction
if tailPosition < cutoff {
let fraction = tailPosition / cutoff
tailRightHalfWidth *= fraction
tailRightArcStartAngle *= fraction
tailRightHalfRadius *= fraction
}
}
}
path.addArc(
withCenter: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius),
radius: cornerRadius,
startAngle: .pi,
endAngle: .pi + max(0.0001, leftArcEndAngle),
clockwise: true
)
let leftArrowStart = max(rect.minX, rect.minX + rectSize.width * tailPosition - tailLeftHalfWidth - leftConnectionArcRadius)
path.addArc(
withCenter: CGPoint(x: leftArrowStart, y: rect.minY - leftConnectionArcRadius),
radius: leftConnectionArcRadius,
startAngle: .pi / 2.0,
endAngle: .pi / 4.0,
clockwise: false
)
path.addLine(to: CGPoint(x: max(rect.minX, rect.minX + rectSize.width * tailPosition - tailLeftHalfRadius), y: rect.minY - tailHeight))
path.addArc(
withCenter: CGPoint(x: rect.minX + rectSize.width * tailPosition, y: rect.minY - tailHeight + tailRadius / 2.0),
radius: tailRadius,
startAngle: -.pi / 2.0 + tailLeftArcStartAngle,
endAngle: -.pi / 2.0 + tailRightArcStartAngle,
clockwise: true
)
path.addLine(to: CGPoint(x: min(rect.maxX, rect.minX + rectSize.width * tailPosition + tailRightHalfRadius), y: rect.minY - tailHeight))
let rightArrowStart = min(rect.maxX, rect.minX + rectSize.width * tailPosition + tailRightHalfWidth + rightConnectionArcRadius)
path.addArc(
withCenter: CGPoint(x: rightArrowStart, y: rect.minY - rightConnectionArcRadius),
radius: rightConnectionArcRadius,
startAngle: .pi - .pi / 4.0,
endAngle: .pi / 2.0,
clockwise: false
)
path.addArc(
withCenter: CGPoint(x: rect.minX + rectSize.width - cornerRadius, y: rect.minY + cornerRadius),
radius: cornerRadius,
startAngle: min(-0.0001, rightArcStartAngle),
endAngle: 0.0,
clockwise: true
)
path.addLine(to: CGPoint(x: rect.minX + rectSize.width, y: rect.minY + rectSize.height - cornerRadius))
path.addArc(
withCenter: CGPoint(x: rect.minX + rectSize.width - cornerRadius, y: rect.minY + rectSize.height - cornerRadius),
radius: cornerRadius,
startAngle: 0.0,
endAngle: .pi / 2.0,
clockwise: true
)
path.addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + rectSize.height))
path.addArc(
withCenter: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + rectSize.height - cornerRadius),
radius: cornerRadius,
startAngle: .pi / 2.0,
endAngle: .pi,
clockwise: true
)
return path
}