diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index 009be583d4..2570ad141d 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -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", diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index 7fe6f62353..2e7f50438f 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -69,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) @@ -190,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 @@ -342,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: @@ -355,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): @@ -501,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 { @@ -607,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)? = 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 } @@ -793,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) } @@ -804,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: @@ -867,27 +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 { - 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 = { diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index b7290eca8e..1c1d38d57c 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -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") { diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 2f559296f3..95f6d9aa26 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -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, 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, 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) }) diff --git a/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/BUILD b/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/BUILD new file mode 100644 index 0000000000..ded71fb772 --- /dev/null +++ b/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/BUILD @@ -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", + ], +) diff --git a/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/Sources/RoundedRectWithTailPath.swift b/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/Sources/RoundedRectWithTailPath.swift new file mode 100644 index 0000000000..98d4bf895f --- /dev/null +++ b/submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath/Sources/RoundedRectWithTailPath.swift @@ -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 +}