diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 22487de47f..57328e7102 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7550,10 +7550,8 @@ Sorry for the inconvenience."; "DialogList.ExtendedPinLimitError" = "Sorry, you can't pin more than **%1$@** chats to the top. Unpin some of the currently pinned ones or subscribe to **Telegram Premium** to double the limit to **%2$@** chats."; -"Group.Username.RemoveExistingUsernamesTitle" = "Too Many Public Links"; "Group.Username.RemoveExistingUsernamesOrExtendInfo" = "You have reserved too many public links. Try revoking a link from an older group or channel, or upgrade to **Telegram Premium** to double the limit to **%@** public links."; -"OldChannels.TooManyCommunitiesTitle" = "Too Many Communities"; "OldChannels.TooManyCommunitiesText" = "You are a member of **%@** groups and channels. Please leave some before joining a new one or upgrade to **Telegram Premium** to double the limit to **%@** groups and channels."; "OldChannels.LeaveCommunities_1" = "Leave %@ Community"; "OldChannels.LeaveCommunities_any" = "Leave %@ Communities"; @@ -7565,6 +7563,11 @@ Sorry for the inconvenience."; "Premium.MaxChatsInFolderCountText" = "Sorry, you can't add more than **%@** chats to a folder. You can increase this limit to **%@** by upgrading to **Telegram Premium**."; "Premium.MaxFileSizeText" = "Double this limit to %@ per file by subscribing to **Telegram Premium**."; "Premium.MaxPinsText" = "Sorry, you can't pin more than **%1$@** chats to the top. Unpin some of the currently pinned ones or subscribe to **Telegram Premium** to double the limit to **%2$@** chats."; +"Premium.MaxFavedStickersTitle" = "The Limit of %@ Stickers Reached"; +"Premium.MaxFavedStickersText" = "An older sticker was replaced with this one. You can [increase the limit]() to %@ stickers."; + +"Premium.Free" = "Free"; +"Premium.Premium" = "Premium"; "Premium.Title" = "Telegram Premium"; "Premium.Description" = "Go **beyond the limits**, get **exclusive features** and support us by subscribing to **Telegram Premium**."; @@ -7596,3 +7599,5 @@ Sorry for the inconvenience."; "Premium.HelpUs" = "Help us maintain Premium Features while keeping Telegram free for everyone."; "Premium.SubscribeFor" = "Subscribe for %@ per month"; + + diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 4ffce82f61..672ec628ea 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -321,17 +321,16 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch case .limitExceeded: f(.default) - var dismissImpl: (() -> Void)? + var replaceImpl: ((ViewController) -> Void)? let controller = PremiumLimitScreen(context: context, subject: .pins, action: { - dismissImpl?() let premiumScreen = PremiumIntroScreen(context: context, action: { }) - chatListController?.push(premiumScreen) + replaceImpl?(premiumScreen) }) chatListController?.push(controller) - dismissImpl = { [weak controller] in - controller?.dismiss() + replaceImpl = { [weak controller] c in + controller?.replace(with: c) } } }) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 91833d3d13..ad293b8e21 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -29,6 +29,7 @@ import FetchManagerImpl import ComponentFlow import LottieAnimationComponent import ProgressIndicatorComponent +import PremiumUI private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool { if listNode.scroller.isDragging { @@ -1312,14 +1313,49 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let strongSelf = self else { return } - let _ = (strongSelf.context.engine.peers.currentChatListFilters() - |> deliverOnMainQueue).start(next: { presetList in + + let _ = combineLatest( + queue: Queue.mainQueue(), + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ), + strongSelf.context.engine.peers.currentChatListFilters() + ).start(next: { result, presetList in guard let strongSelf = self else { return } var found = false for filter in presetList { if filter.id == id { + let (accountPeer, limits, premiumLimits) = result + let limit = limits.maxFolderChatsCount + let premiumLimit = premiumLimits.maxFolderChatsCount + + if let accountPeer = accountPeer, accountPeer.isPremium { + if filter.data.includePeers.peers.count >= premiumLimit { + //printPremiumError + return + } + } else { + if filter.data.includePeers.peers.count >= limit { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .chatsInFolder, action: { + let controller = PremiumIntroScreen(context: context, action: { + + }) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + strongSelf.push(controller) + f(.dismissWithoutContent) + return + } + } + let _ = (strongSelf.context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { filters in guard let strongSelf = self else { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 909e7ae853..0abd8a75e6 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -290,18 +290,17 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch } } else { if filters.count >= limit { - var dismissImpl: (() -> Void)? + var replaceImpl: ((ViewController) -> Void)? let controller = PremiumLimitScreen(context: context, subject: .folders, action: { - dismissImpl?() let controller = PremiumIntroScreen(context: context, action: { }) - pushControllerImpl?(controller) + replaceImpl?(controller) }) - pushControllerImpl?(controller) - dismissImpl = { [weak controller] in - controller?.dismiss() + replaceImpl = { [weak controller] c in + controller?.replace(with: c) } + pushControllerImpl?(controller) return } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index fc3f2ccc15..a89b0b66c4 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -843,15 +843,15 @@ public final class ChatListNode: ListView { case .done: break case .limitExceeded: - var dismissImpl: (() -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .pins, action: { [weak self] in + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .pins, action: { let premiumScreen = PremiumIntroScreen(context: context, action: { - dismissImpl?() + }) - self?.push?(premiumScreen) + replaceImpl?(premiumScreen) }) - dismissImpl = { [weak controller] in - controller?.dismiss() + replaceImpl = { [weak controller] c in + controller?.replace(with: c) } strongSelf.push?(controller) } diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift index cffc02b23d..50c79ffad5 100644 --- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -155,6 +155,8 @@ public final class SheetComponent: Component { if environment[SheetComponentEnvironment.self].value.isDisplaying, !self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateInTransition.self) { self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.scrollView.layer.animatePosition(from: CGPoint(x: 0.0, y: availableSize.height - self.scrollView.contentInset.top), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true, completion: nil) + } else if !environment[SheetComponentEnvironment.self].value.isDisplaying, self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateOutTransition.self) { + self.animateOut(completion: {}) } self.previousIsDisplaying = environment[SheetComponentEnvironment.self].value.isDisplaying diff --git a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift index 347583660d..9d322809da 100644 --- a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift +++ b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift @@ -123,6 +123,9 @@ open class ViewControllerComponentContainer: ViewController { public final class AnimateInTransition { } + public final class AnimateOutTransition { + } + public final class Node: ViewControllerTracingNode { private var presentationData: PresentationData private weak var controller: ViewControllerComponentContainer? @@ -183,7 +186,7 @@ open class ViewControllerComponentContainer: ViewController { guard let currentLayout = self.currentLayout else { return } - self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: animated ? Transition(animation: .none).withUserData(AnimateInTransition()) : .immediate) + self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: animated ? Transition(animation: .none).withUserData(isVisible ? AnimateInTransition() : AnimateOutTransition()) : .immediate) } func updateComponent(component: AnyComponent, transition: Transition) { @@ -240,7 +243,11 @@ open class ViewControllerComponentContainer: ViewController { override open func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - self.node.updateIsVisible(isVisible: false, animated: false) + self.node.updateIsVisible(isVisible: false, animated: animated) + } + + open override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + super.dismiss(animated: flag, completion: completion) } override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index f7780a6e2e..8689ccc8e2 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -554,6 +554,15 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { (self.navigationController as? NavigationController)?.pushViewController(controller) } + public func replace(with controller: ViewController) { + if let navigationController = self.navigationController as? NavigationController { + var controllers = navigationController.viewControllers + controllers.removeAll(where: { $0 === self }) + controllers.append(controller) + navigationController.setViewControllers(controllers, animated: true) + } + } + open func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) { if !(controller is StandalonePresentableController), case .window = context, let arguments = arguments as? ViewControllerPresentationArguments, case .modalSheet = arguments.presentationAnimation, self.navigationController != nil { controller.navigationPresentation = .modal diff --git a/submodules/PeerInfoUI/BUILD b/submodules/PeerInfoUI/BUILD index c6359760fa..52215e6327 100644 --- a/submodules/PeerInfoUI/BUILD +++ b/submodules/PeerInfoUI/BUILD @@ -73,6 +73,7 @@ swift_library( "//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/PremiumUI:PremiumUI", "//submodules/Components/ReactionImageComponent:ReactionImageComponent", "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", ], diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index 85c3938b32..eba1682f2d 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -88,7 +88,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case publicLinkHeader(PresentationTheme, String) case publicLinkAvailability(PresentationTheme, String, Bool) - case linksLimitInfo(PresentationTheme, String, String, Int) + case linksLimitInfo(PresentationTheme, String, Int32, Int32) case editablePublicLink(PresentationTheme, PresentationStrings, String, String) case privateLinkHeader(PresentationTheme, String) case privateLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool) @@ -227,8 +227,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { } else { return false } - case let .linksLimitInfo(lhsTheme, lhsTitle, lhsText, lhsLimit): - if case let .linksLimitInfo(rhsTheme, rhsTitle, rhsText, rhsLimit) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText, lhsLimit == rhsLimit { + case let .linksLimitInfo(lhsTheme, lhsText, lhsLimit, lhsPremiumLimit): + if case let .linksLimitInfo(rhsTheme, rhsText, rhsLimit, rhsPremiumLimit) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLimit == rhsLimit, lhsPremiumLimit == rhsPremiumLimit { return true } else { return false @@ -393,8 +393,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { let attr = NSMutableAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor) attr.addAttribute(.font, value: Font.regular(13), range: NSMakeRange(0, attr.length)) return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: attr, sectionId: self.section) - case let .linksLimitInfo(theme, title, text, limit): - return IncreaseLimitHeaderItem(theme: theme, icon: .link, count: limit, title: title, text: text, sectionId: self.section) + case let .linksLimitInfo(theme, text, limit, premiumLimit): + return IncreaseLimitHeaderItem(theme: theme, strings: presentationData.strings, icon: .link, count: limit, premiumCount: premiumLimit, text: text, sectionId: self.section) case let .privateLinkHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .privateLink(_, invite, peers, importersCount, displayImporters): @@ -729,7 +729,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa if displayAvailability { if let publicChannelsToRevoke = publicChannelsToRevoke { - entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesTitle, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10)) + entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10, 20)) var index: Int32 = 0 for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in @@ -858,7 +858,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa switch mode { case .revokeNames: if let publicChannelsToRevoke = publicChannelsToRevoke { - entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesTitle, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(1000)").string, 500)) + entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10, 20)) entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false)) var index: Int32 = 0 @@ -921,7 +921,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa if displayAvailability { if let publicChannelsToRevoke = publicChannelsToRevoke { - entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesTitle, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(1000)").string, 500)) + entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10, 20)) entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false)) var index: Int32 = 0 diff --git a/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift index ad8c3b62d1..dc25f7f8b6 100644 --- a/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift +++ b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift @@ -7,6 +7,8 @@ import TelegramPresentationData import ItemListUI import PresentationDataUtils import Markdown +import PremiumUI +import ComponentFlow class IncreaseLimitHeaderItem: ListViewItem, ItemListItem { enum Icon { @@ -15,17 +17,19 @@ class IncreaseLimitHeaderItem: ListViewItem, ItemListItem { } let theme: PresentationTheme + let strings: PresentationStrings let icon: Icon - let count: Int - let title: String + let count: Int32 + let premiumCount: Int32 let text: String let sectionId: ItemListSectionId - init(theme: PresentationTheme, icon: Icon, count: Int, title: String, text: String, sectionId: ItemListSectionId) { + init(theme: PresentationTheme, strings: PresentationStrings, icon: Icon, count: Int32, premiumCount: Int32, text: String, sectionId: ItemListSectionId) { self.theme = theme + self.strings = strings self.icon = icon self.count = count - self.title = title + self.premiumCount = premiumCount self.text = text self.sectionId = sectionId } @@ -68,13 +72,11 @@ class IncreaseLimitHeaderItem: ListViewItem, ItemListItem { } private let titleFont = Font.semibold(17.0) -private let textFont = Font.regular(14.0) -private let boldTextFont = Font.semibold(13.0) +private let textFont = Font.regular(15.0) +private let boldTextFont = Font.semibold(15.0) class IncreaseLimitHeaderItemNode: ListViewItemNode { - private var backgroundNode: ASImageNode - private var iconNode: ASImageNode - private var countNode: TextNode + private var hostView: ComponentHostView private let titleNode: TextNode private let textNode: TextNode @@ -91,25 +93,10 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { self.textNode.contentMode = .left self.textNode.contentsScale = UIScreen.main.scale - self.backgroundNode = ASImageNode() - self.backgroundNode.clipsToBounds = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.image = generateGradientImage(size: CGSize(width: 100.0, height: 47.0), colors: [UIColor(rgb: 0xa44ece), UIColor(rgb: 0xff7924)], locations: [0.0, 1.0], direction: .horizontal) - self.backgroundNode.cornerRadius = 23.5 - - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - - self.countNode = TextNode() - self.countNode.isUserInteractionEnabled = false - self.countNode.contentMode = .left - self.countNode.contentsScale = UIScreen.main.scale + self.hostView = ComponentHostView() super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.backgroundNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.countNode) self.addSubnode(self.titleNode) self.addSubnode(self.textNode) } @@ -117,39 +104,27 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { override func didLoad() { super.didLoad() - if #available(iOS 13.0, *) { - self.backgroundNode.layer.cornerCurve = .continuous - } + self.view.addSubview(self.hostView) } func asyncLayout() -> (_ item: IncreaseLimitHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { - let makeCountLayout = TextNode.asyncLayout(self.countNode) - let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTextLayout = TextNode.asyncLayout(self.textNode) return { item, params, neighbors in - let leftInset: CGFloat = 32.0 + params.leftInset let topInset: CGFloat = 2.0 - let badgeHeight: CGFloat = 47.0 - let titleSpacing: CGFloat = 19.0 - let textSpacing: CGFloat = 15.0 - let bottomInset: CGFloat = 2.0 - - let countAttributedText = NSAttributedString(string: "\(item.count)", font: Font.with(size: 24.0, design: .round, weight: .semibold, traits: []), textColor: .white) - let (countLayout, countApply) = makeCountLayout(TextNodeLayoutArguments(attributedString: countAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - - let titleAttributedText = NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor) - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let badgeHeight: CGFloat = 200.0 + let textSpacing: CGFloat = -6.0 + let bottomInset: CGFloat = -86.0 let textColor = item.theme.list.freeTextColor let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: titleFont, textColor: textColor), linkAttribute: { _ in return nil })) - let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.1, cutout: nil, insets: UIEdgeInsets())) - let contentSize = CGSize(width: params.width, height: topInset + badgeHeight + titleSpacing + titleLayout.size.height + textSpacing + textLayout.size.height + bottomInset) + let contentSize = CGSize(width: params.width, height: topInset + badgeHeight + textSpacing + textLayout.size.height + bottomInset) let insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -159,33 +134,39 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { strongSelf.item = item strongSelf.accessibilityLabel = attributedText.string - if strongSelf.iconNode.image == nil { - let image: UIImage? - switch item.icon { - case .group: - image = UIImage(bundleImageName: "Premium/Group") - case .link: - image = UIImage(bundleImageName: "Premium/Link") - } - strongSelf.iconNode.image = generateTintedImage(image: image, color: .white) - } - - let countBackgroundWidth: CGFloat = countLayout.size.width + 67.0 - let countBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - countBackgroundWidth) / 2.0), y: topInset), size: CGSize(width: countBackgroundWidth, height: badgeHeight)) - strongSelf.backgroundNode.frame = countBackgroundFrame - - let _ = countApply() - strongSelf.countNode.frame = CGRect(origin: CGPoint(x: countBackgroundFrame.maxX - countLayout.size.width - 15.0, y: countBackgroundFrame.minY + floorToScreenPixels((countBackgroundFrame.height - countLayout.size.height) / 2.0)), size: countLayout.size) - - if let image = strongSelf.iconNode.image { - strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: countBackgroundFrame.minX + 18.0, y: countBackgroundFrame.minY + floorToScreenPixels((countBackgroundFrame.height - image.size.height) / 2.0)), size: image.size) + let badgeIconName: String + switch item.icon { + case .group: + badgeIconName = "Premium/Group" + case .link: + badgeIconName = "Premium/Link" } - let _ = titleApply() - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleLayout.size.width) / 2.0), y: countBackgroundFrame.maxY + titleSpacing), size: titleLayout.size) + let size = strongSelf.hostView.update( + transition: .immediate, + component: AnyComponent(PremiumLimitDisplayComponent( + inactiveColor: UIColor(rgb: 0xE9E9EA), + activeColors: [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ], + inactiveTitle: item.strings.Premium_Free, + inactiveTitleColor: .black, + activeTitle: item.strings.Premium_Premium, + activeValue: "\(item.premiumCount)", + activeTitleColor: .white, + badgeIconName: badgeIconName, + badgeText: "\(item.count)" + )), + environment: {}, + containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0) + ) + strongSelf.hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -30.0), size: size) let _ = textApply() - strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: countBackgroundFrame.maxY + titleSpacing + titleLayout.size.height + textSpacing), size: textLayout.size) + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: size.height + textSpacing), size: textLayout.size) } }) } diff --git a/submodules/PeerInfoUI/Sources/OldChannelsController.swift b/submodules/PeerInfoUI/Sources/OldChannelsController.swift index 53fcc66dc8..e09d89e53b 100644 --- a/submodules/PeerInfoUI/Sources/OldChannelsController.swift +++ b/submodules/PeerInfoUI/Sources/OldChannelsController.swift @@ -83,7 +83,7 @@ private enum OldChannelsEntryId: Hashable { } private enum OldChannelsEntry: ItemListNodeEntry { - case info(Int, String, String) + case info(Int32, Int32, String) case peersHeader(String) case peer(Int, InactiveChannel, Bool) @@ -109,8 +109,8 @@ private enum OldChannelsEntry: ItemListNodeEntry { static func ==(lhs: OldChannelsEntry, rhs: OldChannelsEntry) -> Bool { switch lhs { - case let .info(count, title, text): - if case .info(count, title, text) = rhs { + case let .info(count, premiumCount, text): + if case .info(count, premiumCount, text) = rhs { return true } else { return false @@ -167,8 +167,8 @@ private enum OldChannelsEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! OldChannelsItemArguments switch self { - case let .info(count, title, text): - return IncreaseLimitHeaderItem(theme: presentationData.theme, icon: .group, count: count, title: title, text: text, sectionId: self.section) + case let .info(count, premiumCount, text): + return IncreaseLimitHeaderItem(theme: presentationData.theme, strings: presentationData.strings, icon: .group, count: count, premiumCount: premiumCount, text: text, sectionId: self.section) case let .peersHeader(title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .peer(_, peer, selected): @@ -184,10 +184,10 @@ private struct OldChannelsState: Equatable { var isSearching: Bool = false } -private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, limit: Int, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] { +private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, limit: Int32, premiumLimit: Int32, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] { var entries: [OldChannelsEntry] = [] - entries.append(.info(limit, presentationData.strings.OldChannels_TooManyCommunitiesTitle, presentationData.strings.OldChannels_TooManyCommunitiesText("\(limit)", "\(limit * 2)").string)) + entries.append(.info(limit, premiumLimit, presentationData.strings.OldChannels_TooManyCommunitiesText("\(limit)", "\(premiumLimit)").string)) if let peers = peers, !peers.isEmpty { entries.append(.peersHeader(presentationData.strings.OldChannels_ChannelsHeader)) @@ -263,17 +263,25 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa var previousPeersWereEmpty = true + + let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let signal = combineLatest( queue: Queue.mainQueue(), presentationData, statePromise.get(), - peersPromise.get() + peersPromise.get(), + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ) ) - |> map { presentationData, state, peers -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, peers, limits -> (ItemListControllerState, (ItemListNodeState, Any)) in let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { dismissImpl?() }) + let (_, limits, premiumLimits) = limits let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.OldChannels_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) @@ -316,7 +324,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa leaveActionImpl?() }) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, limit: 500, peers: peers, intent: intent), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, footerItem: footerItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, limit: limits.maxChannelsCount, premiumLimit: premiumLimits.maxChannelsCount, peers: peers, intent: intent), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, footerItem: footerItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false) return (controllerState, (listState, arguments)) } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index bf60593631..0643aabcc9 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -84,6 +84,11 @@ private class StarComponent: Component { } } + if node.animationKeys.contains("tapRotate") { + self.playAppearanceAnimation(velocity: nil, mirror: left, boom: true) + return + } + let initial = node.rotation let target = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: left ? -0.6 : 0.6) @@ -93,7 +98,7 @@ private class StarComponent: Component { animation.duration = 0.25 animation.timingFunction = CAMediaTimingFunction(name: .easeOut) animation.fillMode = .forwards - node.addAnimation(animation, forKey: "rotate") + node.addAnimation(animation, forKey: "tapRotate") node.rotation = target @@ -106,7 +111,7 @@ private class StarComponent: Component { springAnimation.stiffness = 21.0 springAnimation.damping = 5.8 springAnimation.duration = springAnimation.settlingDuration * 0.8 - node.addAnimation(springAnimation, forKey: "rotate") + node.addAnimation(springAnimation, forKey: "tapRotate") } } @@ -116,6 +121,13 @@ private class StarComponent: Component { return } + if #available(iOS 11.0, *) { + node.removeAnimation(forKey: "rotate", blendOutDuration: 0.1) + node.removeAnimation(forKey: "tapRotate", blendOutDuration: 0.1) + } else { + node.removeAllAnimations() + } + switch gesture.state { case .began: self.previousAngle = 0.0 @@ -128,12 +140,12 @@ private class StarComponent: Component { case .ended: let velocity = gesture.velocity(in: gesture.view) - var small = false + var smallAngle = false if (self.previousAngle < .pi / 2 && self.previousAngle > -.pi / 2) && abs(velocity.x) < 200 { - small = true + smallAngle = true } - self.playAppearanceAnimation(velocity: velocity.x, small: small) + self.playAppearanceAnimation(velocity: velocity.x, smallAngle: smallAngle) node.rotation = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0) default: break @@ -211,12 +223,12 @@ private class StarComponent: Component { node.geometry?.materials.first?.emission.addAnimation(group, forKey: "shimmer") } - private func playAppearanceAnimation(velocity: CGFloat? = nil, small: Bool = false, boom: Bool = false) { + private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) { guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else { return } - if boom, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) { + if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) { node.physicsField?.isActive = true Queue.mainQueue().after(1.0) { node.physicsField?.isActive = false @@ -224,9 +236,14 @@ private class StarComponent: Component { } } - let from = node.rotation - var toValue: Float = small ? 0.0 : .pi * 2.0 - if let velocity = velocity, !small && abs(velocity) > 200 && velocity < 0.0 { + let from = node.presentation.rotation + node.removeAnimation(forKey: "tapRotate") + + var toValue: Float = smallAngle ? 0.0 : .pi * 2.0 + if let velocity = velocity, !smallAngle && abs(velocity) > 200 && velocity < 0.0 { + toValue *= -1 + } + if mirror { toValue *= -1 } let to = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: toValue) diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index 1a8083233d..a50009a06d 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -21,17 +21,20 @@ private class PremiumLimitAnimationComponent: Component { private let inactiveColor: UIColor private let activeColors: [UIColor] private let textColor: UIColor + private let badgeText: String? init( iconName: String, inactiveColor: UIColor, activeColors: [UIColor], - textColor: UIColor + textColor: UIColor, + badgeText: String? ) { self.iconName = iconName self.inactiveColor = inactiveColor self.activeColors = activeColors self.textColor = textColor + self.badgeText = badgeText } static func ==(lhs: PremiumLimitAnimationComponent, rhs: PremiumLimitAnimationComponent) -> Bool { @@ -47,6 +50,9 @@ private class PremiumLimitAnimationComponent: Component { if lhs.textColor != rhs.textColor { return false } + if lhs.badgeText != rhs.badgeText { + return false + } return true } @@ -128,7 +134,7 @@ private class PremiumLimitAnimationComponent: Component { } private var didPlayAppearanceAnimation = false - func playAppearanceAnimation(availableSize: CGSize) { + func playAppearanceAnimation(component: PremiumLimitAnimationComponent, availableSize: CGSize) { self.badgeView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) let now = self.badgeView.layer.convertTime(CACurrentMediaTime(), from: nil) @@ -163,7 +169,9 @@ private class PremiumLimitAnimationComponent: Component { self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") self.badgeView.layer.add(returnAnimation, forKey: "appearance3") - self.badgeCountLabel.text(num: 4) + if let badgeText = component.badgeText, let num = Int(badgeText) { + self.badgeCountLabel.text(num: num) + } } var previousAvailableSize: CGSize? @@ -186,7 +194,25 @@ private class PremiumLimitAnimationComponent: Component { self.activeBackground.position = CGPoint(x: containerFrame.width * 3.0 / 4.0, y: lineHeight / 2.0) } - let badgeSize = CGSize(width: 82.0, height: 48.0 + 12.0) + + let countWidth: CGFloat + if let badgeText = component.badgeText { + switch badgeText.count { + case 1: + countWidth = 20.0 + case 2: + countWidth = 35.0 + case 3: + countWidth = 51.0 + default: + countWidth = 51.0 + } + } else { + countWidth = 51.0 + } + let badgeWidth: CGFloat = countWidth + 62.0 + + let badgeSize = CGSize(width: badgeWidth, height: 48.0 + 12.0) self.badgeMaskView.frame = CGRect(origin: .zero, size: badgeSize) self.badgeMaskBackgroundView.frame = CGRect(origin: .zero, size: CGSize(width: badgeSize.width, height: 48.0)) self.badgeMaskArrowView.frame = CGRect(origin: CGPoint(x: (badgeSize.width - 44.0) / 2.0, y: badgeSize.height - 12.0), size: CGSize(width: 44.0, height: 12.0)) @@ -197,14 +223,13 @@ private class PremiumLimitAnimationComponent: Component { self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0, y: badgeSize.height / 2.0) } self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeSize.width * 3.0, height: badgeSize.height)) - + self.badgeIcon.frame = CGRect(x: 15.0, y: 9.0, width: 30.0, height: 30.0) - - self.badgeCountLabel.frame = CGRect(x: badgeSize.width - 36.0, y: 10.0, width: 30.0, height: 48.0) + self.badgeCountLabel.frame = CGRect(x: badgeSize.width - countWidth - 11.0, y: 10.0, width: countWidth, height: 48.0) if !self.didPlayAppearanceAnimation { self.didPlayAppearanceAnimation = true - self.playAppearanceAnimation(availableSize: availableSize) + self.playAppearanceAnimation(component: component, availableSize: availableSize) } if self.previousAvailableSize != availableSize { @@ -291,7 +316,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { public let activeValue: String public let activeTitleColor: UIColor public let badgeIconName: String - public let badgeValue: String + public let badgeText: String? public init( inactiveColor: UIColor, @@ -302,7 +327,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { activeValue: String, activeTitleColor: UIColor, badgeIconName: String, - badgeValue: String + badgeText: String? ) { self.inactiveColor = inactiveColor self.activeColors = activeColors @@ -312,7 +337,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { self.activeValue = activeValue self.activeTitleColor = activeTitleColor self.badgeIconName = badgeIconName - self.badgeValue = badgeValue + self.badgeText = badgeText } public static func ==(lhs: PremiumLimitDisplayComponent, rhs: PremiumLimitDisplayComponent) -> Bool { @@ -340,16 +365,16 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { if lhs.badgeIconName != rhs.badgeIconName { return false } - if lhs.badgeValue != rhs.badgeValue { + if lhs.badgeText != rhs.badgeText { return false } return true } public static var body: Body { - let inactiveTitle = Child(Text.self) - let activeTitle = Child(Text.self) - let activeValue = Child(Text.self) + let inactiveTitle = Child(MultilineTextComponent.self) + let activeTitle = Child(MultilineTextComponent.self) + let activeValue = Child(MultilineTextComponent.self) let animation = Child(PremiumLimitAnimationComponent.self) return { context in @@ -359,30 +384,42 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { let lineHeight: CGFloat = 30.0 let inactiveTitle = inactiveTitle.update( - component: Text( - text: component.inactiveTitle, - font: Font.semibold(15.0), - color: component.inactiveTitleColor + component: MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.inactiveTitle, + font: Font.semibold(15.0), + textColor: component.inactiveTitleColor + ) + ) ), availableSize: context.availableSize, transition: context.transition ) let activeTitle = activeTitle.update( - component: Text( - text: component.activeTitle, - font: Font.semibold(15.0), - color: component.activeTitleColor + component: MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.activeTitle, + font: Font.semibold(15.0), + textColor: component.activeTitleColor + ) + ) ), availableSize: context.availableSize, transition: context.transition ) let activeValue = activeValue.update( - component: Text( - text: component.activeValue, - font: Font.semibold(15.0), - color: component.activeTitleColor + component: MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.activeValue, + font: Font.semibold(15.0), + textColor: component.activeTitleColor + ) + ) ), availableSize: context.availableSize, transition: context.transition @@ -393,7 +430,8 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { iconName: component.badgeIconName, inactiveColor: component.inactiveColor, activeColors: component.activeColors, - textColor: component.activeTitleColor + textColor: component.activeTitleColor, + badgeText: component.badgeText ), availableSize: CGSize(width: context.availableSize.width, height: height), transition: context.transition @@ -449,6 +487,7 @@ private final class LimitSheetContent: CombinedComponent { private let context: AccountContext private var disposable: Disposable? + var initialized = false var limits: EngineConfiguration.UserLimits var premiumLimits: EngineConfiguration.UserLimits @@ -465,6 +504,7 @@ private final class LimitSheetContent: CombinedComponent { ) |> deliverOnMainQueue).start(next: { [weak self] result in if let strongSelf = self { let (limits, premiumLimits) = result + strongSelf.initialized = true strongSelf.limits = limits strongSelf.premiumLimits = premiumLimits strongSelf.updated(transition: .immediate) @@ -500,7 +540,7 @@ private final class LimitSheetContent: CombinedComponent { let textSideInset: CGFloat = 24.0 + environment.safeInsets.left let iconName: String - let badgeValue: String + let badgeText: String let string: String let premiumValue: String switch subject { @@ -508,28 +548,28 @@ private final class LimitSheetContent: CombinedComponent { let limit = state.limits.maxFoldersCount let premiumLimit = state.premiumLimits.maxFoldersCount iconName = "Premium/Folder" - badgeValue = "\(limit)" + badgeText = "\(limit)" string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string premiumValue = "\(premiumLimit)" case .chatsInFolder: let limit = state.limits.maxFolderChatsCount let premiumLimit = state.premiumLimits.maxFolderChatsCount iconName = "Premium/Chat" - badgeValue = "\(limit)" + badgeText = "\(limit)" string = strings.Premium_MaxChatsInFolderCountText("\(limit)", "\(premiumLimit)").string premiumValue = "\(premiumLimit)" case .pins: - let limit = 4//state.limits.maxPinnedChatCount - let premiumLimit = 6//state.premiumLimits.maxPinnedChatCount + let limit = state.limits.maxPinnedChatCount + let premiumLimit = state.premiumLimits.maxPinnedChatCount iconName = "Premium/Pin" - badgeValue = "\(limit)" + badgeText = "\(limit)" string = strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string premiumValue = "\(premiumLimit)" case .files: - let limit = 2048 * 1024 * 1024 //state.limits.maxPinnedChatCount - let premiumLimit = 4096 * 1024 * 1024 //state.premiumLimits.maxPinnedChatCount + let limit: Int64 = 2048 * 1024 * 1024 * Int64(state.limits.maxUploadFileParts) + let premiumLimit: Int64 = 4096 * 1024 * 1024 * Int64(state.limits.maxUploadFileParts) iconName = "Premium/File" - badgeValue = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) + badgeText = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) string = strings.Premium_MaxFileSizeText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string premiumValue = dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) } @@ -567,26 +607,31 @@ private final class LimitSheetContent: CombinedComponent { transition: .immediate ) - let limit = limit.update( - component: PremiumLimitDisplayComponent( - inactiveColor: UIColor(rgb: 0xE9E9EA), - activeColors: [ - UIColor(rgb: 0x0077ff), - UIColor(rgb: 0x6b93ff), - UIColor(rgb: 0x8878ff), - UIColor(rgb: 0xe46ace) - ], - inactiveTitle: "Free", - inactiveTitleColor: .black, - activeTitle: "Premium", - activeValue: premiumValue, - activeTitleColor: .white, - badgeIconName: iconName, - badgeValue: badgeValue - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), - transition: .immediate - ) + if state.initialized { + let limit = limit.update( + component: PremiumLimitDisplayComponent( + inactiveColor: UIColor(rgb: 0xE9E9EA), + activeColors: [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ], + inactiveTitle: strings.Premium_Free, + inactiveTitleColor: .black, + activeTitle: strings.Premium_Premium, + activeValue: premiumValue, + activeTitleColor: .white, + badgeIconName: iconName, + badgeText: badgeText + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + context.add(limit + .position(CGPoint(x: context.availableSize.width / 2.0, y: limit.size.height / 2.0 + 44.0)) + ) + } let button = button.update( component: SolidRoundedButtonComponent( @@ -620,17 +665,11 @@ private final class LimitSheetContent: CombinedComponent { transition: context.transition ) - let width = context.availableSize.width - - context.add(limit - .position(CGPoint(x: width / 2.0, y: limit.size.height / 2.0 + 44.0)) - ) - context.add(title - .position(CGPoint(x: width / 2.0, y: 28.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: 28.0)) ) context.add(text - .position(CGPoint(x: width / 2.0, y: 228.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: 228.0)) ) let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: 228.0 + ceil(text.size.height / 2.0) + 38.0), size: button.size) diff --git a/submodules/PremiumUI/Sources/RollingCountLabel.swift b/submodules/PremiumUI/Sources/RollingCountLabel.swift index 521e3ca275..694ab8030a 100644 --- a/submodules/PremiumUI/Sources/RollingCountLabel.swift +++ b/submodules/PremiumUI/Sources/RollingCountLabel.swift @@ -73,7 +73,7 @@ open class RollingLabel: UILabel { self.text = fullText } let w = UILabel.textWidth(font: self.font, text: self.text ?? "") - self.text = "" // 초기화 + self.text = "" x = -(w / 2) } else if self.textAlignment == .right { if showSymbol { @@ -82,7 +82,7 @@ open class RollingLabel: UILabel { self.text = fullText } let w = UILabel.textWidth(font: self.font, text: self.text ?? "") - self.text = "" // 초기화 + self.text = "" x = -w } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift index f3b65ebb4d..6d91abfc47 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift @@ -230,11 +230,9 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol f(.default) if let strongSelf = self { - if isStarred { - let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start() - } else { - let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() - } + let _ = strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file, saved: !isStarred).start(next: { result in + + }) } }))) } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index cb2ed8527a..fa46e2a395 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -363,11 +363,9 @@ private final class StickerPackContainer: ASDisplayNode { f(.default) if let strongSelf = self { - if isStarred { - let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start() - } else { - let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() - } + let _ = strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file, saved: !isStarred).start(next: { _ in + + }) } }))) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SavedStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SavedStickers.swift new file mode 100644 index 0000000000..276451d371 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SavedStickers.swift @@ -0,0 +1,37 @@ +import Foundation +import Postbox +import TelegramApi +import SwiftSignalKit + +public enum SavedStickerResult { + case generic + case limitExceeded +} + +func _internal_toggleStickerSaved(postbox: Postbox, network: Network, accountPeerId: PeerId, file: TelegramMediaFile, saved: Bool) -> Signal { + if saved { + return postbox.transaction { transaction -> Signal in + let isPremium = transaction.getPeer(accountPeerId)?.isPremium ?? false + let items = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) + + let appConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue + let userLimitsConfiguration = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: isPremium) + + return addSavedSticker(postbox: postbox, network: network, file: file) + |> map { _ -> SavedStickerResult in + return .generic + } + |> then( + .single(items.count == userLimitsConfiguration.maxFavedStickerCount ? .limitExceeded : .generic) + ) + } + |> castError(AddSavedStickerError.self) + |> switchToLatest + } else { + return removeSavedSticker(postbox: postbox, mediaId: file.fileId) + |> map { _ -> SavedStickerResult in + return .generic + } + |> castError(AddSavedStickerError.self) + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index da417b8d0e..97fdb38728 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -81,6 +81,10 @@ public extension TelegramEngine { return _internal_getStickerSetShortNameSuggestion(account: self.account, title: title) } + public func toggleStickerSaved(file: TelegramMediaFile, saved: Bool) -> Signal { + return _internal_toggleStickerSaved(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, file: file, saved: saved) + } + public func validateStickerSetShortNameInteractive(shortName: String) -> Signal { if let error = _internal_checkAddressNameFormat(shortName) { return .single(.invalidFormat(error)) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f67158122f..53de68ca11 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -7925,28 +7925,40 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } if let stickerFile = stickerFile { - let postbox = strongSelf.context.account.postbox - let network = strongSelf.context.account.network - let _ = (strongSelf.context.account.postbox.transaction { transaction -> Signal in + let context = strongSelf.context + let _ = (context.account.postbox.transaction { transaction -> Signal<(SavedStickerResult, Bool), AddSavedStickerError> in + let isSaved: Bool if getIsStickerSaved(transaction: transaction, fileId: stickerFile.fileId) { - removeSavedSticker(transaction: transaction, mediaId: stickerFile.fileId) - return .single(false) + isSaved = true } else { - return addSavedSticker(postbox: postbox, network: network, file: stickerFile) - |> `catch` { _ -> Signal in - return .complete() - } - |> map { _ -> Bool in - return true - } - |> then(.single(true)) + isSaved = false + } + return context.engine.stickers.toggleStickerSaved(file: stickerFile, saved: !isSaved) + |> map { result -> (SavedStickerResult, Bool) in + return (result, !isSaved) } } + |> castError(AddSavedStickerError.self) |> switchToLatest - |> deliverOnMainQueue).start(next: { [weak self] added in + |> deliverOnMainQueue).start(next: { [weak self] result, added in if let strongSelf = self { -// strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: added ? "The Limit of 5 Stickers Reached" : nil, text: added ? "An older sticker was replaced with this one. You can [increase the limit]() to 10 stickers." : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil) - strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: nil, text: added ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil) + switch result { + case .generic: + strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: nil, text: added ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil) + case .limitExceeded: + strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: strongSelf.presentationData.strings.Premium_MaxFavedStickersTitle("5").string, text: strongSelf.presentationData.strings.Premium_MaxFavedStickersText("10").string), elevatedLayout: true, action: { [weak self] action in + if let strongSelf = self { + if case .info = action { + let controller = PremiumIntroScreen(context: strongSelf.context, action: { + + }) + strongSelf.push(controller) + return true + } + } + return false + }), with: nil) + } } }) } diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 87281dba91..17219ed8d9 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -87,6 +87,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.animationBackgroundColor = UIColor(rgb: 0x474747) } + var isUserInteractionEnabled = false switch content { case let .removedChat(text): self.avatarNode = nil @@ -696,7 +697,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { }), textAlignment: .natural) self.textNode.attributedText = attributedText self.textNode.maximumNumberOfLines = 2 - + + if text.contains("](") { + isUserInteractionEnabled = true + } + displayUndo = false self.originalRemainingSeconds = 3 @@ -879,6 +884,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.isUserInteractionEnabled = false } } + if isUserInteractionEnabled { + self.isUserInteractionEnabled = true + } self.titleNode.isUserInteractionEnabled = false self.textNode.isUserInteractionEnabled = self.textNode.tapAttributeAction != nil