mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge commit '91e73050508ba2f4b0b201cbe9ab6584bb2e62f3'
This commit is contained in:
commit
50881b558f
@ -580,8 +580,8 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
|
||||
}
|
||||
}
|
||||
|
||||
public func ensureItemNodeVisible(_ itemNode: ListViewItemNode, animated: Bool = true, curve: ListViewAnimationCurve = .Default(duration: 0.25)) {
|
||||
(self.displayNode as! ItemListControllerNode).listNode.ensureItemNodeVisible(itemNode, animated: animated, curve: curve)
|
||||
public func ensureItemNodeVisible(_ itemNode: ListViewItemNode, animated: Bool = true, overflow: CGFloat = 0.0, atTop: Bool = false, curve: ListViewAnimationCurve = .Default(duration: 0.25)) {
|
||||
(self.displayNode as! ItemListControllerNode).listNode.ensureItemNodeVisible(itemNode, animated: animated, overflow: overflow, atTop: atTop, curve: curve)
|
||||
}
|
||||
|
||||
public func afterLayout(_ f: @escaping () -> Void) {
|
||||
|
@ -389,7 +389,7 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
}
|
||||
case .unknown, .none:
|
||||
if let headerItemNode = strongSelf.headerItemNode {
|
||||
headerItemNode.updateContentOffset(0.0, transition: .immediate)
|
||||
headerItemNode.updateContentOffset(1000.0, transition: .immediate)
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
} else {
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||
|
@ -105,6 +105,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem",
|
||||
"//submodules/TelegramUI/Components/ShareWithPeersScreen",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -27,14 +27,16 @@ private final class CreateGiveawayControllerArguments {
|
||||
let openPeersSelection: () -> Void
|
||||
let openChannelsSelection: () -> Void
|
||||
let openPremiumIntro: () -> Void
|
||||
let scrollToDate: () -> Void
|
||||
|
||||
init(context: AccountContext, updateState: @escaping ((CreateGiveawayControllerState) -> CreateGiveawayControllerState) -> Void, dismissInput: @escaping () -> Void, openPeersSelection: @escaping () -> Void, openChannelsSelection: @escaping () -> Void, openPremiumIntro: @escaping () -> Void) {
|
||||
init(context: AccountContext, updateState: @escaping ((CreateGiveawayControllerState) -> CreateGiveawayControllerState) -> Void, dismissInput: @escaping () -> Void, openPeersSelection: @escaping () -> Void, openChannelsSelection: @escaping () -> Void, openPremiumIntro: @escaping () -> Void, scrollToDate: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
self.dismissInput = dismissInput
|
||||
self.openPeersSelection = openPeersSelection
|
||||
self.openChannelsSelection = openChannelsSelection
|
||||
self.openPremiumIntro = openPremiumIntro
|
||||
self.scrollToDate = scrollToDate
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +51,7 @@ private enum CreateGiveawaySection: Int32 {
|
||||
}
|
||||
|
||||
private enum CreateGiveawayEntryTag: ItemListItemTag {
|
||||
case usage
|
||||
case date
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? CreateGiveawayEntryTag, self == other {
|
||||
@ -67,7 +69,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
case awardUsers(PresentationTheme, String, String, Bool)
|
||||
|
||||
case prepaidHeader(PresentationTheme, String)
|
||||
case prepaid(PresentationTheme, String, String, Int32, Int32)
|
||||
case prepaid(PresentationTheme, String, String, PrepaidGiveaway)
|
||||
|
||||
case subscriptionsHeader(PresentationTheme, String, String)
|
||||
case subscriptions(PresentationTheme, Int32)
|
||||
@ -188,8 +190,8 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .prepaid(lhsTheme, lhsText, lhsSubtext, lhsBoosts, lhsMonths):
|
||||
if case let .prepaid(rhsTheme, rhsText, rhsSubtext, rhsBoosts, rhsMonths) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsSubtext == rhsSubtext, lhsBoosts == rhsBoosts, lhsMonths == rhsMonths {
|
||||
case let .prepaid(lhsTheme, lhsText, lhsSubtext, lhsPrepaidGiveaway):
|
||||
if case let .prepaid(rhsTheme, rhsText, rhsSubtext, rhsPrepaidGiveaway) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsSubtext == rhsSubtext, lhsPrepaidGiveaway == rhsPrepaidGiveaway {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -340,10 +342,9 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
})
|
||||
case let .prepaidHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .prepaid(_, title, subtitle, boosts, months):
|
||||
let _ = boosts
|
||||
case let .prepaid(_, title, subtitle, prepaidGiveaway):
|
||||
let color: GiftOptionItem.Icon.Color
|
||||
switch months {
|
||||
switch prepaidGiveaway.months {
|
||||
case 3:
|
||||
color = .green
|
||||
case 6:
|
||||
@ -353,7 +354,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
default:
|
||||
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):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
|
||||
case let .subscriptions(_, value):
|
||||
@ -409,11 +410,20 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
}
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Ends", label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
|
||||
arguments.dismissInput()
|
||||
var focus = false
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.pickingTimeLimit = !state.pickingTimeLimit
|
||||
if updatedState.pickingTimeLimit {
|
||||
focus = true
|
||||
}
|
||||
return updatedState
|
||||
}
|
||||
if focus {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
arguments.scrollToDate()
|
||||
}
|
||||
}
|
||||
})
|
||||
case let .timeCustomPicker(_, dateTimeFormat, date):
|
||||
return ItemListDatePickerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, date: date, sectionId: self.section, style: .blocks, updated: { date in
|
||||
@ -422,7 +432,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
updatedState.time = date
|
||||
return updatedState
|
||||
})
|
||||
})
|
||||
}, tag: CreateGiveawayEntryTag.date)
|
||||
case let .timeInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .durationHeader(_, text):
|
||||
@ -490,9 +500,9 @@ private func createGiveawayControllerEntries(peerId: EnginePeer.Id, subject: Cre
|
||||
recipientsText = "select recipients"
|
||||
}
|
||||
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(.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 {
|
||||
@ -596,15 +606,15 @@ private struct CreateGiveawayControllerState: Equatable {
|
||||
|
||||
public enum CreateGiveawaySubject {
|
||||
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 {
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let initialSubscriptions: Int32
|
||||
if case let .prepaid(_, count) = subject {
|
||||
initialSubscriptions = count
|
||||
if case let .prepaid(prepaidGiveaway) = subject {
|
||||
initialSubscriptions = prepaidGiveaway.quantity
|
||||
} else {
|
||||
initialSubscriptions = 5
|
||||
}
|
||||
@ -626,6 +636,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
var openPremiumIntroImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var scrollToDateImpl: (() -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var dismissInputImpl: (() -> Void)?
|
||||
|
||||
@ -639,6 +650,8 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
openChannelsSelectionImpl?()
|
||||
}, openPremiumIntro: {
|
||||
openPremiumIntroImpl?()
|
||||
}, scrollToDate: {
|
||||
scrollToDateImpl?()
|
||||
})
|
||||
|
||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
@ -779,7 +792,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
let purpose: AppStoreTransactionPurpose
|
||||
switch state.mode {
|
||||
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:
|
||||
purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount)
|
||||
}
|
||||
@ -790,57 +803,59 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
return updatedState
|
||||
}
|
||||
|
||||
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: selectedProduct.giftOption.storeQuantity, purpose: purpose)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] status in
|
||||
if case .purchased = status {
|
||||
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 {
|
||||
switch subject {
|
||||
case .generic:
|
||||
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: selectedProduct.giftOption.storeQuantity, purpose: purpose)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] status in
|
||||
if case .purchased = status {
|
||||
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
|
||||
}
|
||||
break
|
||||
} else {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
controllers.removeLast(count)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
|
||||
let title: String
|
||||
let text: String
|
||||
switch state.mode {
|
||||
case .giveaway:
|
||||
title = "Giveaway Created"
|
||||
text = "Check your channel's [Statistics]() to see how this giveaway boosted your channel."
|
||||
case .gift:
|
||||
title = "Premium Subscriptions Gifted"
|
||||
text = "Check your channel's [Statistics]() to see how gifts 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)
|
||||
controllers.removeLast(count)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
|
||||
let title: String
|
||||
let text: String
|
||||
switch state.mode {
|
||||
case .giveaway:
|
||||
title = "Giveaway Created"
|
||||
text = "Check your channel's [Statistics]() to see how this giveaway boosted your channel."
|
||||
case .gift:
|
||||
title = "Premium Subscriptions Gifted"
|
||||
text = "Check your channel's [Statistics]() to see how gifts 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
|
||||
})
|
||||
}), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
})
|
||||
(controllers.last as? ViewController)?.present(tooltipController, in: .current)
|
||||
(controllers.last as? ViewController)?.present(tooltipController, in: .current)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, error: { error in
|
||||
var errorText: String?
|
||||
switch error {
|
||||
}, error: { error in
|
||||
var errorText: String?
|
||||
switch error {
|
||||
case .generic:
|
||||
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
|
||||
case .network:
|
||||
@ -853,30 +868,66 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
|
||||
case .cancelled:
|
||||
break
|
||||
}
|
||||
|
||||
if let errorText = errorText {
|
||||
let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
presentControllerImpl?(alertController)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if let errorText = errorText {
|
||||
let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
presentControllerImpl?(alertController)
|
||||
}
|
||||
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.updating = false
|
||||
return updatedState
|
||||
}
|
||||
})
|
||||
} else {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.updating = false
|
||||
return updatedState
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Premium_Purchase_ErrorUnknown, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
presentControllerImpl?(alertController)
|
||||
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.updating = false
|
||||
return updatedState
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
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 = {
|
||||
@ -903,6 +954,15 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
}
|
||||
)
|
||||
controller.dismissed = {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
if updatedState.peers.isEmpty {
|
||||
updatedState.mode = .giveaway
|
||||
}
|
||||
return updatedState
|
||||
}
|
||||
}
|
||||
pushControllerImpl?(controller)
|
||||
})
|
||||
}
|
||||
@ -937,5 +997,27 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
|
||||
scrollToDateImpl = { [weak controller] in
|
||||
controller?.afterLayout({
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
|
||||
var resultItemNode: ListViewItemNode?
|
||||
let _ = controller.frameForItemNode({ listItemNode in
|
||||
if let itemNode = listItemNode as? ItemListItemNode {
|
||||
if let tag = itemNode.tag as? CreateGiveawayEntryTag, tag == .date {
|
||||
resultItemNode = listItemNode
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if let resultItemNode = resultItemNode {
|
||||
controller.ensureItemNodeVisible(resultItemNode, overflow: 120.0, atTop: true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let statusAttributedString = NSAttributedString(string: item.subtitle ?? "", font: statusFont, textColor: item.subtitleActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
let badgeAttributedString = NSAttributedString(string: item.badge ?? "", font: Font.with(size: 14.0, design: .round, weight: .semibold), textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
let badgeAttributedString = NSAttributedString(string: item.badge ?? "", font: Font.with(size: 13.0, design: .round, weight: .semibold), textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
|
||||
let labelColor: UIColor
|
||||
let labelFont: UIFont
|
||||
@ -450,7 +450,7 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
||||
var badgeOffset: CGFloat = 0.0
|
||||
if badgeLayout.size.width > 0.0 {
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset + 2.0, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: badgeLayout.size)
|
||||
let badgeBackgroundFrame = badgeFrame.insetBy(dx: -2.0, dy: -2.0)
|
||||
let badgeBackgroundFrame = badgeFrame.insetBy(dx: -3.0, dy: -2.0)
|
||||
|
||||
let badgeBackgroundNode: ASImageNode
|
||||
if let current = strongSelf.badgeBackgroundNode {
|
||||
@ -491,7 +491,7 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.containerNode.addSubnode(iconNode)
|
||||
|
||||
strongSelf.labelBackgroundNode = backgroundNode
|
||||
strongSelf.labelIconNode = backgroundNode
|
||||
strongSelf.labelIconNode = iconNode
|
||||
}
|
||||
|
||||
if let icon = iconNode.image {
|
||||
|
@ -18,6 +18,7 @@ import BalancedTextComponent
|
||||
import ConfettiEffect
|
||||
import AvatarNode
|
||||
import TextFormat
|
||||
import RoundedRectWithTailPath
|
||||
|
||||
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
||||
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 {
|
||||
private let inactiveColor: UIColor
|
||||
private let activeColors: [UIColor]
|
||||
@ -658,7 +534,7 @@ public class PremiumLimitDisplayComponent: Component {
|
||||
|
||||
if badgePosition > 1.0 - 0.15 {
|
||||
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") {
|
||||
|
||||
@ -667,7 +543,7 @@ public class PremiumLimitDisplayComponent: Component {
|
||||
}
|
||||
} else if badgePosition < 0.15 {
|
||||
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") {
|
||||
|
||||
@ -676,7 +552,7 @@ public class PremiumLimitDisplayComponent: Component {
|
||||
}
|
||||
} else {
|
||||
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") {
|
||||
|
||||
|
@ -34,9 +34,9 @@ private final class ChannelStatsControllerArguments {
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let expandBoosters: () -> 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.loadDetailedGraph = loadDetailedGraph
|
||||
self.openMessageStats = openMessage
|
||||
@ -110,7 +110,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
case boostOverview(PresentationTheme, ChannelBoostStatus)
|
||||
|
||||
case boostPrepaidTitle(PresentationTheme, String)
|
||||
case boostPrepaid(Int32, PresentationTheme, String, String, Int32, Int32)
|
||||
case boostPrepaid(Int32, PresentationTheme, String, String, PrepaidGiveaway)
|
||||
case boostPrepaidInfo(PresentationTheme, String)
|
||||
|
||||
case boostersTitle(PresentationTheme, String)
|
||||
@ -219,7 +219,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return 2002
|
||||
case .boostPrepaidTitle:
|
||||
return 2003
|
||||
case let .boostPrepaid(index, _, _, _, _, _):
|
||||
case let .boostPrepaid(index, _, _, _, _):
|
||||
return 2004 + index
|
||||
case .boostPrepaidInfo:
|
||||
return 2100
|
||||
@ -404,8 +404,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .boostPrepaid(lhsIndex, lhsTheme, lhsTitle, lhsSubtitle, lhsMonths, lhsCount):
|
||||
if case let .boostPrepaid(rhsIndex, rhsTheme, rhsTitle, rhsSubtitle, rhsMonths, rhsCount) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsMonths == rhsMonths, lhsCount == rhsCount {
|
||||
case let .boostPrepaid(lhsIndex, lhsTheme, lhsTitle, lhsSubtitle, lhsPrepaidGiveaway):
|
||||
if case let .boostPrepaid(rhsIndex, rhsTheme, rhsTitle, rhsSubtitle, rhsPrepaidGiveaway) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsPrepaidGiveaway == rhsPrepaidGiveaway {
|
||||
return true
|
||||
} else {
|
||||
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: {
|
||||
arguments.openGifts()
|
||||
})
|
||||
case let .boostPrepaid(_, _, title, subtitle, months, count):
|
||||
case let .boostPrepaid(_, _, title, subtitle, prepaidGiveaway):
|
||||
let color: GiftOptionItem.Icon.Color
|
||||
switch months {
|
||||
switch prepaidGiveaway.months {
|
||||
case 3:
|
||||
color = .green
|
||||
case 6:
|
||||
@ -573,8 +573,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
default:
|
||||
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: {
|
||||
arguments.createPrepaidGiveaway(months, count)
|
||||
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(prepaidGiveaway)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -701,10 +701,15 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
entries.append(.boostOverview(presentationData.theme, boostData))
|
||||
|
||||
//TODO:localize
|
||||
entries.append(.boostPrepaidTitle(presentationData.theme, "PREPAID GIVEAWAYS"))
|
||||
entries.append(.boostPrepaid(0, presentationData.theme, "70 Telegram Premium", "3-month subscriptions", 3, 70))
|
||||
entries.append(.boostPrepaid(1, presentationData.theme, "200 Telegram Premium", "6-month subscriptions", 6, 200))
|
||||
entries.append(.boostPrepaidInfo(presentationData.theme, "Select a giveaway you already paid for to set it up."))
|
||||
if !boostData.prepaidGiveaways.isEmpty {
|
||||
entries.append(.boostPrepaidTitle(presentationData.theme, "PREPAID GIVEAWAYS"))
|
||||
var i: Int32 = 0
|
||||
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."))
|
||||
}
|
||||
|
||||
let boostersTitle: String
|
||||
let boostersPlaceholder: String?
|
||||
@ -871,8 +876,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
let controller = createGiveawayController(context: context, peerId: peerId, subject: .generic)
|
||||
pushImpl?(controller)
|
||||
},
|
||||
createPrepaidGiveaway: { months, count in
|
||||
let controller = createGiveawayController(context: context, peerId: peerId, subject: .prepaid(months: months, count: count))
|
||||
createPrepaidGiveaway: { prepaidGiveaway in
|
||||
let controller = createGiveawayController(context: context, peerId: peerId, subject: .prepaid(prepaidGiveaway))
|
||||
pushImpl?(controller)
|
||||
})
|
||||
|
||||
|
@ -663,6 +663,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[629052971] = { return Api.PremiumGiftCodeOption.parse_premiumGiftCodeOption($0) }
|
||||
dict[1958953753] = { return Api.PremiumGiftOption.parse_premiumGiftOption($0) }
|
||||
dict[1596792306] = { return Api.PremiumSubscriptionOption.parse_premiumSubscriptionOption($0) }
|
||||
dict[-1303143084] = { return Api.PrepaidGiveaway.parse_prepaidGiveaway($0) }
|
||||
dict[-1534675103] = { return Api.PrivacyKey.parse_privacyKeyAbout($0) }
|
||||
dict[1124062251] = { return Api.PrivacyKey.parse_privacyKeyAddedByPhone($0) }
|
||||
dict[1343122938] = { return Api.PrivacyKey.parse_privacyKeyChatInvite($0) }
|
||||
@ -1187,7 +1188,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1862033025] = { return Api.stories.AllStories.parse_allStories($0) }
|
||||
dict[291044926] = { return Api.stories.AllStories.parse_allStoriesNotModified($0) }
|
||||
dict[-203604707] = { return Api.stories.BoostersList.parse_boostersList($0) }
|
||||
dict[-440292772] = { return Api.stories.BoostsStatus.parse_boostsStatus($0) }
|
||||
dict[1911715597] = { return Api.stories.BoostsStatus.parse_boostsStatus($0) }
|
||||
dict[-1021889145] = { return Api.stories.CanApplyBoostResult.parse_canApplyBoostOk($0) }
|
||||
dict[1898726997] = { return Api.stories.CanApplyBoostResult.parse_canApplyBoostReplace($0) }
|
||||
dict[-890861720] = { return Api.stories.PeerStories.parse_peerStories($0) }
|
||||
@ -1673,6 +1674,8 @@ public extension Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PremiumSubscriptionOption:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PrepaidGiveaway:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PrivacyKey:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PrivacyRule:
|
||||
|
@ -926,6 +926,54 @@ public extension Api {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum PrepaidGiveaway: TypeConstructorDescription {
|
||||
case prepaidGiveaway(id: Int64, months: Int32, quantity: Int32, date: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .prepaidGiveaway(let id, let months, let quantity, let date):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1303143084)
|
||||
}
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
serializeInt32(months, buffer: buffer, boxed: false)
|
||||
serializeInt32(quantity, buffer: buffer, boxed: false)
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .prepaidGiveaway(let id, let months, let quantity, let date):
|
||||
return ("prepaidGiveaway", [("id", id as Any), ("months", months as Any), ("quantity", quantity as Any), ("date", date as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_prepaidGiveaway(_ reader: BufferReader) -> PrepaidGiveaway? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.PrepaidGiveaway.prepaidGiveaway(id: _1!, months: _2!, quantity: _3!, date: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum PrivacyKey: TypeConstructorDescription {
|
||||
case privacyKeyAbout
|
||||
|
@ -660,13 +660,13 @@ public extension Api.stories {
|
||||
}
|
||||
public extension Api.stories {
|
||||
enum BoostsStatus: TypeConstructorDescription {
|
||||
case boostsStatus(flags: Int32, level: Int32, currentLevelBoosts: Int32, boosts: Int32, nextLevelBoosts: Int32?, premiumAudience: Api.StatsPercentValue?, boostUrl: String)
|
||||
case boostsStatus(flags: Int32, level: Int32, currentLevelBoosts: Int32, boosts: Int32, nextLevelBoosts: Int32?, premiumAudience: Api.StatsPercentValue?, boostUrl: String, prepaidGiveaways: [Api.PrepaidGiveaway]?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let nextLevelBoosts, let premiumAudience, let boostUrl):
|
||||
case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways):
|
||||
if boxed {
|
||||
buffer.appendInt32(-440292772)
|
||||
buffer.appendInt32(1911715597)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(level, buffer: buffer, boxed: false)
|
||||
@ -675,14 +675,19 @@ public extension Api.stories {
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextLevelBoosts!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {premiumAudience!.serialize(buffer, true)}
|
||||
serializeString(boostUrl, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(prepaidGiveaways!.count))
|
||||
for item in prepaidGiveaways! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let nextLevelBoosts, let premiumAudience, let boostUrl):
|
||||
return ("boostsStatus", [("flags", flags as Any), ("level", level as Any), ("currentLevelBoosts", currentLevelBoosts as Any), ("boosts", boosts as Any), ("nextLevelBoosts", nextLevelBoosts as Any), ("premiumAudience", premiumAudience as Any), ("boostUrl", boostUrl as Any)])
|
||||
case .boostsStatus(let flags, let level, let currentLevelBoosts, let boosts, let nextLevelBoosts, let premiumAudience, let boostUrl, let prepaidGiveaways):
|
||||
return ("boostsStatus", [("flags", flags as Any), ("level", level as Any), ("currentLevelBoosts", currentLevelBoosts as Any), ("boosts", boosts as Any), ("nextLevelBoosts", nextLevelBoosts as Any), ("premiumAudience", premiumAudience as Any), ("boostUrl", boostUrl as Any), ("prepaidGiveaways", prepaidGiveaways as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -703,6 +708,10 @@ public extension Api.stories {
|
||||
} }
|
||||
var _7: String?
|
||||
_7 = parseString(reader)
|
||||
var _8: [Api.PrepaidGiveaway]?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrepaidGiveaway.self)
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -710,8 +719,9 @@ public extension Api.stories {
|
||||
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.stories.BoostsStatus.boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, nextLevelBoosts: _5, premiumAudience: _6, boostUrl: _7!)
|
||||
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.stories.BoostsStatus.boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, nextLevelBoosts: _5, premiumAudience: _6, boostUrl: _7!, prepaidGiveaways: _8)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -7641,6 +7641,23 @@ public extension Api.functions.payments {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func launchPrepaidGiveaway(peer: Api.InputPeer, giveawayId: Int64, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1609928480)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt64(giveawayId, buffer: buffer, boxed: false)
|
||||
purpose.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "payments.launchPrepaidGiveaway", parameters: [("peer", String(describing: peer)), ("giveawayId", String(describing: giveawayId)), ("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentResult>) {
|
||||
let buffer = Buffer()
|
||||
|
@ -10,14 +10,16 @@ public final class ChannelBoostStatus: Equatable {
|
||||
public let nextLevelBoosts: Int?
|
||||
public let premiumAudience: StatsPercentValue?
|
||||
public let url: String
|
||||
public let prepaidGiveaways: [PrepaidGiveaway]
|
||||
|
||||
public init(level: Int, boosts: Int, currentLevelBoosts: Int, nextLevelBoosts: Int?, premiumAudience: StatsPercentValue?, url: String) {
|
||||
public init(level: Int, boosts: Int, currentLevelBoosts: Int, nextLevelBoosts: Int?, premiumAudience: StatsPercentValue?, url: String, prepaidGiveaways: [PrepaidGiveaway]) {
|
||||
self.level = level
|
||||
self.boosts = boosts
|
||||
self.currentLevelBoosts = currentLevelBoosts
|
||||
self.nextLevelBoosts = nextLevelBoosts
|
||||
self.premiumAudience = premiumAudience
|
||||
self.url = url
|
||||
self.prepaidGiveaways = prepaidGiveaways
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChannelBoostStatus, rhs: ChannelBoostStatus) -> Bool {
|
||||
@ -39,6 +41,9 @@ public final class ChannelBoostStatus: Equatable {
|
||||
if lhs.url != rhs.url {
|
||||
return false
|
||||
}
|
||||
if lhs.prepaidGiveaways != rhs.prepaidGiveaways {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -62,8 +67,8 @@ func _internal_getChannelBoostStatus(account: Account, peerId: PeerId) -> Signal
|
||||
}
|
||||
|
||||
switch result {
|
||||
case let .boostsStatus(_, level, currentLevelBoosts, boosts, nextLevelBoosts, premiumAudience, url):
|
||||
return ChannelBoostStatus(level: Int(level), boosts: Int(boosts), currentLevelBoosts: Int(currentLevelBoosts), nextLevelBoosts: nextLevelBoosts.flatMap(Int.init), premiumAudience: premiumAudience.flatMap({ StatsPercentValue(apiPercentValue: $0) }), url: url)
|
||||
case let .boostsStatus(_, level, currentLevelBoosts, boosts, nextLevelBoosts, premiumAudience, url, prepaidGiveaways):
|
||||
return ChannelBoostStatus(level: Int(level), boosts: Int(boosts), currentLevelBoosts: Int(currentLevelBoosts), nextLevelBoosts: nextLevelBoosts.flatMap(Int.init), premiumAudience: premiumAudience.flatMap({ StatsPercentValue(apiPercentValue: $0) }), url: url, prepaidGiveaways: prepaidGiveaways?.map({ PrepaidGiveaway(apiPrepaidGiveaway: $0) }) ?? [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
public let maxStoriesWeeklyCount: Int32
|
||||
public let maxStoriesMonthlyCount: Int32
|
||||
public let maxStoriesSuggestedReactions: Int32
|
||||
public let maxGiveawayChannelsCount: Int32
|
||||
|
||||
public static var defaultValue: UserLimitsConfiguration {
|
||||
return UserLimitsConfiguration(
|
||||
@ -44,7 +45,8 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
maxExpiringStoriesCount: 3,
|
||||
maxStoriesWeeklyCount: 7,
|
||||
maxStoriesMonthlyCount: 30,
|
||||
maxStoriesSuggestedReactions: 1
|
||||
maxStoriesSuggestedReactions: 1,
|
||||
maxGiveawayChannelsCount: 10
|
||||
)
|
||||
}
|
||||
|
||||
@ -68,7 +70,8 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
maxExpiringStoriesCount: Int32,
|
||||
maxStoriesWeeklyCount: Int32,
|
||||
maxStoriesMonthlyCount: Int32,
|
||||
maxStoriesSuggestedReactions: Int32
|
||||
maxStoriesSuggestedReactions: Int32,
|
||||
maxGiveawayChannelsCount: Int32
|
||||
) {
|
||||
self.maxPinnedChatCount = maxPinnedChatCount
|
||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||
@ -90,6 +93,7 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
self.maxStoriesWeeklyCount = maxStoriesWeeklyCount
|
||||
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
|
||||
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
|
||||
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,5 +138,6 @@ extension UserLimitsConfiguration {
|
||||
self.maxStoriesWeeklyCount = getValue("stories_sent_weekly_limit", orElse: defaultValue.maxStoriesWeeklyCount)
|
||||
self.maxStoriesMonthlyCount = getValue("stories_sent_monthly_limit", orElse: defaultValue.maxStoriesMonthlyCount)
|
||||
self.maxStoriesSuggestedReactions = getValue("stories_suggested_reactions_limit", orElse: defaultValue.maxStoriesMonthlyCount)
|
||||
self.maxGiveawayChannelsCount = getGeneralValue("giveaway_add_peers_max", orElse: defaultValue.maxGiveawayChannelsCount)
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ public enum EngineConfiguration {
|
||||
public let maxStoriesWeeklyCount: Int32
|
||||
public let maxStoriesMonthlyCount: Int32
|
||||
public let maxStoriesSuggestedReactions: Int32
|
||||
public let maxGiveawayChannelsCount: Int32
|
||||
|
||||
public static var defaultValue: UserLimits {
|
||||
return UserLimits(UserLimitsConfiguration.defaultValue)
|
||||
@ -81,7 +82,8 @@ public enum EngineConfiguration {
|
||||
maxExpiringStoriesCount: Int32,
|
||||
maxStoriesWeeklyCount: Int32,
|
||||
maxStoriesMonthlyCount: Int32,
|
||||
maxStoriesSuggestedReactions: Int32
|
||||
maxStoriesSuggestedReactions: Int32,
|
||||
maxGiveawayChannelsCount: Int32
|
||||
) {
|
||||
self.maxPinnedChatCount = maxPinnedChatCount
|
||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||
@ -103,6 +105,7 @@ public enum EngineConfiguration {
|
||||
self.maxStoriesWeeklyCount = maxStoriesWeeklyCount
|
||||
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
|
||||
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
|
||||
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,7 +162,8 @@ public extension EngineConfiguration.UserLimits {
|
||||
maxExpiringStoriesCount: userLimitsConfiguration.maxExpiringStoriesCount,
|
||||
maxStoriesWeeklyCount: userLimitsConfiguration.maxStoriesWeeklyCount,
|
||||
maxStoriesMonthlyCount: userLimitsConfiguration.maxStoriesMonthlyCount,
|
||||
maxStoriesSuggestedReactions: userLimitsConfiguration.maxStoriesSuggestedReactions
|
||||
maxStoriesSuggestedReactions: userLimitsConfiguration.maxStoriesSuggestedReactions,
|
||||
maxGiveawayChannelsCount: userLimitsConfiguration.maxGiveawayChannelsCount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,13 @@ public enum PremiumGiveawayInfo: Equatable {
|
||||
case finished(status: ResultStatus, finishDate: Int32, winnersCount: Int32, activatedCount: Int32)
|
||||
}
|
||||
|
||||
public struct PrepaidGiveaway: Equatable {
|
||||
public let id: Int64
|
||||
public let months: Int32
|
||||
public let quantity: Int32
|
||||
public let date: Int32
|
||||
}
|
||||
|
||||
func _internal_getPremiumGiveawayInfo(account: Account, peerId: EnginePeer.Id, messageId: EngineMessage.Id) -> Signal<PremiumGiveawayInfo?, NoError> {
|
||||
return account.postbox.loadedPeerWithId(peerId)
|
||||
|> mapToSignal { peer in
|
||||
@ -177,6 +184,46 @@ func _internal_applyPremiumGiftCode(account: Account, slug: String) -> Signal<Ne
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_launchPrepaidGiveaway(account: Account, peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||
var flags: Int32 = 0
|
||||
if onlyNewSubscribers {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
|
||||
var inputPeer: Api.InputPeer?
|
||||
if let peer = transaction.getPeer(peerId), let apiPeer = apiInputPeer(peer) {
|
||||
inputPeer = apiPeer
|
||||
}
|
||||
|
||||
var additionalPeers: [Api.InputPeer] = []
|
||||
if !additionalPeerIds.isEmpty {
|
||||
flags |= (1 << 1)
|
||||
for peerId in additionalPeerIds {
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
additionalPeers.append(inputPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .complete()
|
||||
}
|
||||
return account.network.request(Api.functions.payments.launchPrepaidGiveaway(peer: inputPeer, giveawayId: id, purpose: .inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: inputPeer, additionalPeers: additionalPeers, randomId: randomId, untilDate: untilDate, currency: "", amount: 0)))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Never, NoError> in
|
||||
if let updates = updates {
|
||||
account.stateManager.addUpdates(updates)
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
extension PremiumGiftCodeOption {
|
||||
init(apiGiftCodeOption: Api.PremiumGiftCodeOption) {
|
||||
switch apiGiftCodeOption {
|
||||
@ -207,3 +254,15 @@ public extension PremiumGiftCodeInfo {
|
||||
return self.usedDate != nil
|
||||
}
|
||||
}
|
||||
|
||||
extension PrepaidGiveaway {
|
||||
init(apiPrepaidGiveaway: Api.PrepaidGiveaway) {
|
||||
switch apiPrepaidGiveaway {
|
||||
case let .prepaidGiveaway(id, months, quantity, date):
|
||||
self.id = id
|
||||
self.months = months
|
||||
self.quantity = quantity
|
||||
self.date = date
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,5 +61,9 @@ public extension TelegramEngine {
|
||||
public func premiumGiveawayInfo(peerId: EnginePeer.Id, messageId: EngineMessage.Id) -> Signal<PremiumGiveawayInfo?, NoError> {
|
||||
return _internal_getPremiumGiveawayInfo(account: self.account, peerId: peerId, messageId: messageId)
|
||||
}
|
||||
|
||||
public func launchPrepaidGiveaway(peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32) -> Signal<Never, NoError> {
|
||||
return _internal_launchPrepaidGiveaway(account: self.account, peerId: peerId, id: id, additionalPeerIds: additionalPeerIds, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1452,8 +1452,9 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
let index = self.selectedPeers.firstIndex(of: peer.id)
|
||||
let togglePeer = {
|
||||
if let index = self.selectedPeers.firstIndex(of: peer.id) {
|
||||
if let index {
|
||||
self.selectedPeers.remove(at: index)
|
||||
self.updateSelectedGroupPeers()
|
||||
} else {
|
||||
@ -1462,11 +1463,12 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
update()
|
||||
}
|
||||
if peer.id.isGroupOrChannel {
|
||||
if case .channels = component.stateContext.subject, self.selectedPeers.count >= 10 {
|
||||
if case .channels = component.stateContext.subject, self.selectedPeers.count >= component.context.userLimits.maxGiveawayChannelsCount, index == nil {
|
||||
self.hapticFeedback.error()
|
||||
return
|
||||
}
|
||||
if case .channels = component.stateContext.subject {
|
||||
if case let .channel(channel) = peer, channel.addressName == nil, !self.selectedPeers.contains(peer.id) {
|
||||
if case let .channel(channel) = peer, channel.addressName == nil, index == nil {
|
||||
let alertController = textAlertController(
|
||||
context: component.context,
|
||||
forceTheme: environment.theme,
|
||||
@ -1488,7 +1490,8 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
update()
|
||||
}
|
||||
} else {
|
||||
if case .members = component.stateContext.subject, self.selectedPeers.count >= 10 {
|
||||
if case .members = component.stateContext.subject, self.selectedPeers.count >= 10, index == nil {
|
||||
self.hapticFeedback.error()
|
||||
return
|
||||
}
|
||||
togglePeer()
|
||||
@ -2359,7 +2362,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
case .channels:
|
||||
title = "Add Channels"
|
||||
actionButtonTitle = "Save Channels"
|
||||
subtitle = "select up to 10 channels"
|
||||
subtitle = "select up to \(component.context.userLimits.maxGiveawayChannelsCount) channels"
|
||||
}
|
||||
|
||||
let titleComponent: AnyComponent<Empty>
|
||||
@ -2982,6 +2985,10 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
deinit {
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
self.dismissed()
|
||||
}
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
@ -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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user