mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Giveaway improvements
This commit is contained in:
parent
d06e7af2ce
commit
91e7305050
@ -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",
|
||||||
|
@ -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,57 +803,59 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
|||||||
return updatedState
|
return updatedState
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (context.engine.payments.canPurchasePremium(purpose: purpose)
|
switch subject {
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] available in
|
case .generic:
|
||||||
if available, let inAppPurchaseManager = context.inAppPurchaseManager {
|
let _ = (context.engine.payments.canPurchasePremium(purpose: purpose)
|
||||||
let _ = (inAppPurchaseManager.buyProduct(selectedProduct.storeProduct, quantity: selectedProduct.giftOption.storeQuantity, purpose: purpose)
|
|> deliverOnMainQueue).startStandalone(next: { [weak controller] available in
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] status in
|
if available, let inAppPurchaseManager = context.inAppPurchaseManager {
|
||||||
if case .purchased = status {
|
let _ = (inAppPurchaseManager.buyProduct(selectedProduct.storeProduct, quantity: selectedProduct.giftOption.storeQuantity, purpose: purpose)
|
||||||
if let controller, let navigationController = controller.navigationController as? NavigationController {
|
|> deliverOnMainQueue).startStandalone(next: { [weak controller] status in
|
||||||
var controllers = navigationController.viewControllers
|
if case .purchased = status {
|
||||||
var count = 0
|
if let controller, let navigationController = controller.navigationController as? NavigationController {
|
||||||
for c in controllers.reversed() {
|
var controllers = navigationController.viewControllers
|
||||||
if c is PeerInfoScreen {
|
var count = 0
|
||||||
if case .giveaway = state.mode {
|
for c in controllers.reversed() {
|
||||||
|
if c is PeerInfoScreen {
|
||||||
|
if case .giveaway = state.mode {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else {
|
||||||
count += 1
|
count += 1
|
||||||
}
|
}
|
||||||
break
|
|
||||||
} else {
|
|
||||||
count += 1
|
|
||||||
}
|
}
|
||||||
}
|
controllers.removeLast(count)
|
||||||
controllers.removeLast(count)
|
navigationController.setViewControllers(controllers, animated: true)
|
||||||
navigationController.setViewControllers(controllers, animated: true)
|
|
||||||
|
let title: String
|
||||||
let title: String
|
let text: String
|
||||||
let text: String
|
switch state.mode {
|
||||||
switch state.mode {
|
case .giveaway:
|
||||||
case .giveaway:
|
title = "Giveaway Created"
|
||||||
title = "Giveaway Created"
|
text = "Check your channel's [Statistics]() to see how this giveaway boosted your channel."
|
||||||
text = "Check your channel's [Statistics]() to see how this giveaway boosted your channel."
|
case .gift:
|
||||||
case .gift:
|
title = "Premium Subscriptions Gifted"
|
||||||
title = "Premium Subscriptions Gifted"
|
text = "Check your channel's [Statistics]() to see how gifts boosted your channel."
|
||||||
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 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))
|
||||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: peerId))
|
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] statsDatacenterId in
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] statsDatacenterId in
|
guard let statsDatacenterId else {
|
||||||
guard let statsDatacenterId else {
|
return
|
||||||
return
|
}
|
||||||
}
|
let statsController = context.sharedContext.makeChannelStatsController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, boosts: true, boostStatus: nil, statsDatacenterId: statsDatacenterId)
|
||||||
let statsController = context.sharedContext.makeChannelStatsController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, boosts: true, boostStatus: nil, statsDatacenterId: statsDatacenterId)
|
navigationController?.pushViewController(statsController)
|
||||||
navigationController?.pushViewController(statsController)
|
})
|
||||||
|
}), elevatedLayout: false, action: { _ in
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
}), elevatedLayout: false, action: { _ in
|
(controllers.last as? ViewController)?.present(tooltipController, in: .current)
|
||||||
return true
|
}
|
||||||
})
|
|
||||||
(controllers.last as? ViewController)?.present(tooltipController, in: .current)
|
|
||||||
}
|
}
|
||||||
}
|
}, error: { error in
|
||||||
}, error: { error in
|
var errorText: String?
|
||||||
var errorText: String?
|
switch error {
|
||||||
switch error {
|
|
||||||
case .generic:
|
case .generic:
|
||||||
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
|
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
|
||||||
case .network:
|
case .network:
|
||||||
@ -867,27 +868,66 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
|||||||
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
|
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if let errorText = errorText {
|
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: {})])
|
let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||||
presentControllerImpl?(alertController)
|
presentControllerImpl?(alertController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateState { state in
|
||||||
|
var updatedState = state
|
||||||
|
updatedState.updating = false
|
||||||
|
return updatedState
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var updatedState = state
|
var updatedState = state
|
||||||
updatedState.updating = false
|
updatedState.updating = false
|
||||||
return updatedState
|
return updatedState
|
||||||
}
|
}
|
||||||
})
|
|
||||||
} else {
|
|
||||||
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 = {
|
openPeersSelectionImpl = {
|
||||||
|
@ -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") {
|
||||||
|
|
||||||
|
@ -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
|
||||||
entries.append(.boostPrepaidTitle(presentationData.theme, "PREPAID GIVEAWAYS"))
|
if !boostData.prepaidGiveaways.isEmpty {
|
||||||
entries.append(.boostPrepaid(0, presentationData.theme, "70 Telegram Premium", "3-month subscriptions", 3, 70))
|
entries.append(.boostPrepaidTitle(presentationData.theme, "PREPAID GIVEAWAYS"))
|
||||||
entries.append(.boostPrepaid(1, presentationData.theme, "200 Telegram Premium", "6-month subscriptions", 6, 200))
|
var i: Int32 = 0
|
||||||
entries.append(.boostPrepaidInfo(presentationData.theme, "Select a giveaway you already paid for to set it up."))
|
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 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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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