diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 548bb6526a..2877357edf 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7705,3 +7705,5 @@ Sorry for the inconvenience."; "Premium.Purchase.ErrorUnknown" = "An error occurred. Please try again."; "Premium.Purchase.ErrorNetwork" = "Please check your internet connection and try again."; "Premium.Purchase.ErrorNotAllowed" = "The device is not not allowed to make the payment."; + +"Settings.Premium" = "Telegram Premium"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 8e1cd29158..3a7df00c3d 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -51,6 +51,7 @@ public final class TelegramApplicationBindings { public let pushIdleTimerExtension: () -> Disposable public let openSettings: () -> Void public let openAppStorePage: () -> Void + public let openSubscriptions: () -> Void public let registerForNotifications: (@escaping (Bool) -> Void) -> Void public let requestSiriAuthorization: (@escaping (Bool) -> Void) -> Void public let siriAuthorization: () -> AccessType @@ -62,7 +63,7 @@ public final class TelegramApplicationBindings { public let requestSetAlternateIconName: (String?, @escaping (Bool) -> Void) -> Void public let forceOrientation: (UIInterfaceOrientation) -> Void - public init(isMainApp: Bool, appBundleId: String, appBuildType: TelegramAppBuildType, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal, applicationIsActive: Signal, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) { + public init(isMainApp: Bool, appBundleId: String, appBuildType: TelegramAppBuildType, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal, applicationIsActive: Signal, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, openSubscriptions: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) { self.isMainApp = isMainApp self.appBundleId = appBundleId self.appBuildType = appBuildType @@ -79,6 +80,7 @@ public final class TelegramApplicationBindings { self.pushIdleTimerExtension = pushIdleTimerExtension self.openSettings = openSettings self.openAppStorePage = openAppStorePage + self.openSubscriptions = openSubscriptions self.registerForNotifications = registerForNotifications self.requestSiriAuthorization = requestSiriAuthorization self.siriAuthorization = siriAuthorization @@ -885,3 +887,23 @@ public protocol AccountContext: AnyObject { func joinGroupCall(peerId: PeerId, invite: String?, requestJoinAsPeerId: ((@escaping (PeerId?) -> Void) -> Void)?, activeCall: EngineGroupCallDescription) func requestCall(peerId: PeerId, isVideo: Bool, completion: @escaping () -> Void) } + +public struct PremiumConfiguration { + public static var defaultValue: PremiumConfiguration { + return PremiumConfiguration(isPremiumDisabled: false) + } + + public let isPremiumDisabled: Bool + + fileprivate init(isPremiumDisabled: Bool) { + self.isPremiumDisabled = isPremiumDisabled + } + + public static func with(appConfiguration: AppConfiguration) -> PremiumConfiguration { + if let data = appConfiguration.data, let value = data["premium_purchase_blocked"] as? Bool { + return PremiumConfiguration(isPremiumDisabled: value) + } else { + return .defaultValue + } + } +} diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 79d844ba08..2a85754ef1 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -1259,8 +1259,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll case .generic: controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = presentationData.strings.Premium_MaxSavedGifsFinalText } else { text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string @@ -1453,8 +1454,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll case .generic: controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = presentationData.strings.Premium_MaxSavedGifsFinalText } else { text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string diff --git a/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift b/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift index d1cea4832c..138f9d20fd 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift @@ -127,6 +127,8 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod return self.item?.tag } + private var exceededLimit = false + public init() { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true @@ -197,6 +199,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod var limitTextString: NSAttributedString? var rightInset: CGFloat = params.rightInset + var exceededLimit = false if let maxLength = item.maxLength, maxLength.display { let textLength: Int switch maxLength.mode { @@ -210,6 +213,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod if displayTextLimit { limitTextString = NSAttributedString(string: "\(remainingCount)", font: Font.regular(13.0), textColor: remainingCount < 0 ? item.presentationData.theme.list.itemDestructiveColor : item.presentationData.theme.list.itemSecondaryTextColor) } + exceededLimit = remainingCount < 0 rightInset += 30.0 + 4.0 } @@ -254,6 +258,7 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod if let strongSelf = self { strongSelf.item = item strongSelf.layoutParams = params + strongSelf.exceededLimit = exceededLimit if let _ = updatedTheme { strongSelf.topStripeNode.backgroundColor = itemSeparatorColor @@ -470,6 +475,12 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod self.textNode.layer.addShakeAnimation() } + public func animateErrorIfNeeded() { + if self.exceededLimit { + self.animateError() + } + } + @objc private func inlineActionPressed() { if let action = self.item?.inlineAction?.action { action() diff --git a/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift index e0bb7bfddc..5eb7619454 100644 --- a/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift +++ b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift @@ -160,7 +160,8 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode { activeTitleColor: .white, badgeIconName: badgeIconName, badgeText: "\(item.count)", - badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount) + badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount), + isPremiumDisabled: false )), environment: {}, containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0) diff --git a/submodules/PremiumUI/Sources/AppIconsDemoComponent.swift b/submodules/PremiumUI/Sources/AppIconsDemoComponent.swift index 75eb32f78f..1892884991 100644 --- a/submodules/PremiumUI/Sources/AppIconsDemoComponent.swift +++ b/submodules/PremiumUI/Sources/AppIconsDemoComponent.swift @@ -64,7 +64,18 @@ final class AppIconsDemoComponent: Component { if self.imageViews.isEmpty { for icon in component.appIcons { - if let image = UIImage(named: icon.imageName, in: getAppBundle(), compatibleWith: nil) { + let image: UIImage? + switch icon.imageName { + case "Premium": + image = UIImage(bundleImageName: "Premium/Icons/Premium") + case "PremiumBlack": + image = UIImage(bundleImageName: "Premium/Icons/Black") + case "PremiumTurbo": + image = UIImage(bundleImageName: "Premium/Icons/Turbo") + default: + image = nil + } + if let image = image { let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: 90.0, height: 90.0))) imageView.clipsToBounds = true imageView.layer.cornerRadius = 24.0 diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index b443345f23..49f1158882 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1320,35 +1320,40 @@ private final class PremiumIntroScreenComponent: CombinedComponent { super.init() + let availableProducts: Signal<[InAppPurchaseManager.Product], NoError> if let inAppPurchaseManager = context.inAppPurchaseManager { - let otherPeerName: Signal - if case let .profile(peerId) = source { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> map { peer -> String? in - return peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - } - } else { - otherPeerName = .single(nil) - } - - self.disposable = combineLatest( - queue: Queue.mainQueue(), - inAppPurchaseManager.availableProducts, - context.account.postbox.peerView(id: context.account.peerId) - |> map { view -> Bool in - return view.peers[view.peerId]?.isPremium ?? false - }, - otherPeerName - ).start(next: { [weak self] products, isPremium, otherPeerName in - if let strongSelf = self { - strongSelf.premiumProduct = products.first - strongSelf.isPremium = isPremium - strongSelf.otherPeerName = otherPeerName - strongSelf.updated(transition: .immediate) - } - }) + availableProducts = inAppPurchaseManager.availableProducts + } else { + availableProducts = .single([]) } + + let otherPeerName: Signal + if case let .profile(peerId) = source { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> map { peer -> String? in + return peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + } + } else { + otherPeerName = .single(nil) + } + + self.disposable = combineLatest( + queue: Queue.mainQueue(), + availableProducts, + context.account.postbox.peerView(id: context.account.peerId) + |> map { view -> Bool in + return view.peers[view.peerId]?.isPremium ?? false + }, + otherPeerName + ).start(next: { [weak self] products, isPremium, otherPeerName in + if let strongSelf = self { + strongSelf.premiumProduct = products.first + strongSelf.isPremium = isPremium + strongSelf.otherPeerName = otherPeerName + strongSelf.updated(transition: .immediate) + } + }) } deinit { diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index 985f3fe5d5..a1f2ba2808 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -44,6 +44,7 @@ private class PremiumLimitAnimationComponent: Component { private let textColor: UIColor private let badgeText: String? private let badgePosition: CGFloat + private let isPremiumDisabled: Bool init( iconName: String?, @@ -51,7 +52,8 @@ private class PremiumLimitAnimationComponent: Component { activeColors: [UIColor], textColor: UIColor, badgeText: String?, - badgePosition: CGFloat + badgePosition: CGFloat, + isPremiumDisabled: Bool ) { self.iconName = iconName self.inactiveColor = inactiveColor @@ -59,6 +61,7 @@ private class PremiumLimitAnimationComponent: Component { self.textColor = textColor self.badgeText = badgeText self.badgePosition = badgePosition + self.isPremiumDisabled = isPremiumDisabled } static func ==(lhs: PremiumLimitAnimationComponent, rhs: PremiumLimitAnimationComponent) -> Bool { @@ -80,6 +83,9 @@ private class PremiumLimitAnimationComponent: Component { if lhs.badgePosition != rhs.badgePosition { return false } + if lhs.isPremiumDisabled != rhs.isPremiumDisabled { + return false + } return true } @@ -182,44 +188,41 @@ private class PremiumLimitAnimationComponent: Component { private var didPlayAppearanceAnimation = false 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) - + let positionAnimation = CABasicAnimation(keyPath: "position.x") positionAnimation.fromValue = NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)) positionAnimation.toValue = NSValue(cgPoint: self.badgeView.center) positionAnimation.duration = 0.5 positionAnimation.fillMode = .forwards - positionAnimation.beginTime = now - - let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - rotateAnimation.fromValue = 0.0 as NSNumber - rotateAnimation.toValue = 0.2 as NSNumber - rotateAnimation.isAdditive = true - rotateAnimation.duration = 0.2 - rotateAnimation.beginTime = now + 0.5 - rotateAnimation.fillMode = .forwards - rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) + self.badgeView.layer.add(positionAnimation, forKey: "appearance1") + Queue.mainQueue().after(0.5, { + let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + rotateAnimation.fromValue = 0.0 as NSNumber + rotateAnimation.toValue = 0.2 as NSNumber + rotateAnimation.duration = 0.2 + rotateAnimation.fillMode = .forwards + rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) + rotateAnimation.isRemovedOnCompletion = false + self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") + if !self.badgeView.isHidden { self.hapticFeedback.impact(.light) } + + Queue.mainQueue().after(0.2) { + let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + returnAnimation.fromValue = 0.2 as NSNumber + returnAnimation.toValue = 0.0 as NSNumber + returnAnimation.duration = 0.18 + returnAnimation.fillMode = .forwards + returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) + self.badgeView.layer.add(returnAnimation, forKey: "appearance3") + self.badgeView.layer.removeAnimation(forKey: "appearance2") + } }) - let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - returnAnimation.fromValue = 0.2 as NSNumber - returnAnimation.toValue = 0.0 as NSNumber - returnAnimation.isAdditive = true - returnAnimation.duration = 0.18 - returnAnimation.beginTime = now + 0.5 + 0.2 - returnAnimation.fillMode = .forwards - returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) - - self.badgeView.layer.add(positionAnimation, forKey: "appearance1") - self.badgeView.layer.add(rotateAnimation, forKey: "appearance2") - self.badgeView.layer.add(returnAnimation, forKey: "appearance3") - self.badgeView.alpha = 1.0 self.badgeView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) @@ -241,12 +244,14 @@ private class PremiumLimitAnimationComponent: Component { let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - lineHeight), size: CGSize(width: availableSize.width, height: lineHeight)) self.container.frame = containerFrame - self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: containerFrame.width / 2.0, height: lineHeight)) - self.activeContainer.frame = CGRect(origin: CGPoint(x: containerFrame.width / 2.0, y: 0.0), size: CGSize(width: containerFrame.width / 2.0, height: lineHeight)) - - self.activeBackground.bounds = CGRect(origin: .zero, size: CGSize(width: containerFrame.width * 3.0 / 2.0, height: lineHeight)) - if self.activeBackground.animation(forKey: "movement") == nil { - self.activeBackground.position = CGPoint(x: containerFrame.width * 3.0 / 4.0 - self.activeBackground.frame.width * 0.35, y: lineHeight / 2.0) + if !component.isPremiumDisabled { + self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: containerFrame.width / 2.0, height: lineHeight)) + self.activeContainer.frame = CGRect(origin: CGPoint(x: containerFrame.width / 2.0, y: 0.0), size: CGSize(width: containerFrame.width / 2.0, height: lineHeight)) + + self.activeBackground.bounds = CGRect(origin: .zero, size: CGSize(width: containerFrame.width * 3.0 / 2.0, height: lineHeight)) + if self.activeBackground.animation(forKey: "movement") == nil { + self.activeBackground.position = CGPoint(x: containerFrame.width * 3.0 / 4.0 - self.activeBackground.frame.width * 0.35, y: lineHeight / 2.0) + } } let countWidth: CGFloat @@ -276,24 +281,41 @@ private class PremiumLimitAnimationComponent: Component { self.badgeMaskTailView.frame = CGRect(origin: CGPoint(x: badgeSize.width - 44.0, y: badgeSize.height - 36.0), size: CGSize(width: 44.0, height: 36.0)) self.badgeView.bounds = CGRect(origin: .zero, size: badgeSize) - if component.badgePosition > 1.0 - .ulpOfOne { + + var badgePosition = component.badgePosition + if component.isPremiumDisabled { + badgePosition = 0.5 + } + if badgePosition > 1.0 - .ulpOfOne { self.badgeView.layer.anchorPoint = CGPoint(x: 1.0, y: 1.0) self.badgeMaskTailView.isHidden = false self.badgeMaskArrowView.isHidden = true - self.badgeView.center = CGPoint(x: 3.0 + (availableSize.width - 6.0) * component.badgePosition + 3.0, y: 82.0) + if let _ = self.badgeView.layer.animation(forKey: "appearance1") { + + } else { + self.badgeView.center = CGPoint(x: 3.0 + (availableSize.width - 6.0) * badgePosition + 3.0, y: 82.0) + } } else { self.badgeView.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0) self.badgeMaskTailView.isHidden = true - self.badgeMaskArrowView.isHidden = false + self.badgeMaskArrowView.isHidden = component.isPremiumDisabled - self.badgeView.center = CGPoint(x: 3.0 + (availableSize.width - 6.0) * component.badgePosition, y: 82.0) + if let _ = self.badgeView.layer.animation(forKey: "appearance1") { + + } else { + self.badgeView.center = CGPoint(x: 3.0 + (availableSize.width - 6.0) * badgePosition, y: 82.0) + } if self.badgeView.frame.maxX > availableSize.width { let delta = self.badgeView.frame.maxX - availableSize.width - 6.0 - self.badgeView.center = self.badgeView.center.offsetBy(dx: -delta, dy: 0.0) + if let _ = self.badgeView.layer.animation(forKey: "appearance1") { + + } else { + self.badgeView.center = self.badgeView.center.offsetBy(dx: -delta, dy: 0.0) + } } } self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeSize.width * 3.0, height: badgeSize.height)) @@ -304,7 +326,16 @@ private class PremiumLimitAnimationComponent: Component { self.badgeIcon.frame = CGRect(x: 15.0, y: 9.0, width: 30.0, height: 30.0) self.badgeCountLabel.frame = CGRect(x: badgeSize.width - countWidth - 11.0, y: 10.0, width: countWidth, height: 48.0) - if !self.didPlayAppearanceAnimation { + if component.isPremiumDisabled { + if !self.didPlayAppearanceAnimation { + self.didPlayAppearanceAnimation = true + + self.badgeView.alpha = 1.0 + if let badgeText = component.badgeText { + self.badgeCountLabel.configure(with: badgeText, duration: 0.3) + } + } + } else if !self.didPlayAppearanceAnimation { self.didPlayAppearanceAnimation = true self.playAppearanceAnimation(component: component, availableSize: availableSize) } @@ -396,6 +427,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { let badgeIconName: String? let badgeText: String? let badgePosition: CGFloat + let isPremiumDisabled: Bool public init( inactiveColor: UIColor, @@ -408,7 +440,8 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { activeTitleColor: UIColor, badgeIconName: String?, badgeText: String?, - badgePosition: CGFloat + badgePosition: CGFloat, + isPremiumDisabled: Bool ) { self.inactiveColor = inactiveColor self.activeColors = activeColors @@ -421,6 +454,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { self.badgeIconName = badgeIconName self.badgeText = badgeText self.badgePosition = badgePosition + self.isPremiumDisabled = isPremiumDisabled } public static func ==(lhs: PremiumLimitDisplayComponent, rhs: PremiumLimitDisplayComponent) -> Bool { @@ -457,6 +491,9 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { if lhs.badgePosition != rhs.badgePosition { return false } + if lhs.isPremiumDisabled != rhs.isPremiumDisabled { + return false + } return true } @@ -473,62 +510,6 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { let height: CGFloat = 120.0 let lineHeight: CGFloat = 30.0 - let inactiveTitle = inactiveTitle.update( - component: MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.inactiveTitle, - font: Font.semibold(15.0), - textColor: component.inactiveTitleColor - ) - ) - ), - availableSize: context.availableSize, - transition: context.transition - ) - - let inactiveValue = inactiveValue.update( - component: MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.inactiveValue, - font: Font.semibold(15.0), - textColor: component.inactiveTitleColor - ) - ) - ), - availableSize: context.availableSize, - transition: context.transition - ) - - let activeTitle = activeTitle.update( - 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: MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.activeValue, - font: Font.semibold(15.0), - textColor: component.activeTitleColor - ) - ) - ), - availableSize: context.availableSize, - transition: context.transition - ) - let animation = animation.update( component: PremiumLimitAnimationComponent( iconName: component.badgeIconName, @@ -536,7 +517,8 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { activeColors: component.activeColors, textColor: component.activeTitleColor, badgeText: component.badgeText, - badgePosition: component.badgePosition + badgePosition: component.badgePosition, + isPremiumDisabled: component.isPremiumDisabled ), availableSize: CGSize(width: context.availableSize.width, height: height), transition: context.transition @@ -546,22 +528,80 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: height / 2.0)) ) - context.add(inactiveTitle - .position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0)) - ) - - context.add(inactiveValue - .position(CGPoint(x: context.availableSize.width / 2.0 - inactiveValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0)) - ) - - context.add(activeTitle - .position(CGPoint(x: context.availableSize.width / 2.0 + activeTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0)) - ) - - context.add(activeValue - .position(CGPoint(x: context.availableSize.width - activeValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0)) - ) - + if !component.isPremiumDisabled { + let inactiveTitle = inactiveTitle.update( + component: MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.inactiveTitle, + font: Font.semibold(15.0), + textColor: component.inactiveTitleColor + ) + ) + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let inactiveValue = inactiveValue.update( + component: MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.inactiveValue, + font: Font.semibold(15.0), + textColor: component.inactiveTitleColor + ) + ) + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let activeTitle = activeTitle.update( + 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: MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.activeValue, + font: Font.semibold(15.0), + textColor: component.activeTitleColor + ) + ) + ), + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(inactiveTitle + .position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0)) + ) + + context.add(inactiveValue + .position(CGPoint(x: context.availableSize.width / 2.0 - inactiveValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0)) + ) + + context.add(activeTitle + .position(CGPoint(x: context.availableSize.width / 2.0 + activeTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0)) + ) + + context.add(activeValue + .position(CGPoint(x: context.availableSize.width - activeValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0)) + ) + } + return CGSize(width: context.availableSize.width, height: height) } } @@ -656,6 +696,9 @@ private final class LimitSheetContent: CombinedComponent { let state = context.state let subject = component.subject + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) + let isPremiumDisabled = premiumConfiguration.isPremiumDisabled + let sideInset: CGFloat = 16.0 + environment.safeInsets.left let textSideInset: CGFloat = 24.0 + environment.safeInsets.left @@ -684,7 +727,7 @@ private final class LimitSheetContent: CombinedComponent { var titleText = strings.Premium_LimitReached var buttonAnimationName = "premium_x2" let iconName: String - let badgeText: String + var badgeText: String var string: String let defaultValue: String let premiumValue: String @@ -703,6 +746,11 @@ private final class LimitSheetContent: CombinedComponent { if !state.isPremium && badgePosition > 0.5 { string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string } + + if isPremiumDisabled { + badgeText = "\(limit)" + string = strings.Premium_MaxFoldersCountNoPremiumText("\(limit)").string + } case .chatsPerFolder: let limit = state.limits.maxFolderChatsCount let premiumLimit = state.premiumLimits.maxFolderChatsCount @@ -712,6 +760,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" badgePosition = CGFloat(component.count) / CGFloat(premiumLimit) + + if isPremiumDisabled { + badgeText = "\(limit)" + string = strings.Premium_MaxChatsInFolderNoPremiumText("\(limit)").string + } case .pins: let limit = state.limits.maxPinnedChatCount let premiumLimit = state.premiumLimits.maxPinnedChatCount @@ -721,6 +774,11 @@ private final class LimitSheetContent: CombinedComponent { defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" badgePosition = CGFloat(component.count) / CGFloat(premiumLimit) + + if isPremiumDisabled { + badgeText = "\(limit)" + string = strings.Premium_MaxPinsNoPremiumText("\(limit)").string + } case .files: let limit = Int64(state.limits.maxUploadFileParts) * 512 * 1024 + 1024 * 1024 * 100 let premiumLimit = Int64(state.premiumLimits.maxUploadFileParts) * 512 * 1024 + 1024 * 1024 * 100 @@ -731,6 +789,11 @@ private final class LimitSheetContent: CombinedComponent { premiumValue = component.count != 4 ? dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) : "" badgePosition = component.count == 4 ? 1.0 : 0.5 titleText = strings.Premium_FileTooLarge + + if isPremiumDisabled { + badgeText = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) + string = strings.Premium_MaxFileSizeNoPremiumText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string + } case .accounts: let limit = 3 let premiumLimit = component.count + 1 @@ -745,6 +808,11 @@ private final class LimitSheetContent: CombinedComponent { badgePosition = CGFloat(component.count) / CGFloat(premiumLimit) } buttonAnimationName = "premium_addone" + + if isPremiumDisabled { + badgeText = "\(limit)" + string = strings.Premium_MaxAccountsNoPremiumText("\(limit)").string + } } var reachedMaximumLimit = badgePosition >= 1.0 if case .folders = subject, !state.isPremium { @@ -784,16 +852,26 @@ private final class LimitSheetContent: CombinedComponent { transition: .immediate ) + let gradientColors: [UIColor] + if isPremiumDisabled { + gradientColors = [ + UIColor(rgb: 0x007afe), + UIColor(rgb: 0x5494ff) + ] + } else { + gradientColors = [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] + } + if state.initialized { let limit = limit.update( component: PremiumLimitDisplayComponent( inactiveColor: theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5), - activeColors: [ - UIColor(rgb: 0x0077ff), - UIColor(rgb: 0x6b93ff), - UIColor(rgb: 0x8878ff), - UIColor(rgb: 0xe46ace) - ], + activeColors: gradientColors, inactiveTitle: strings.Premium_Free, inactiveValue: defaultValue, inactiveTitleColor: theme.list.itemPrimaryTextColor, @@ -802,7 +880,8 @@ private final class LimitSheetContent: CombinedComponent { activeTitleColor: .white, badgeIconName: iconName, badgeText: badgeText, - badgePosition: badgePosition + badgePosition: badgePosition, + isPremiumDisabled: isPremiumDisabled ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), transition: .immediate @@ -812,47 +891,50 @@ private final class LimitSheetContent: CombinedComponent { ) } + let isIncreaseButton = !reachedMaximumLimit && !isPremiumDisabled let button = button.update( component: SolidRoundedButtonComponent( - title: !reachedMaximumLimit ? strings.Premium_IncreaseLimit : strings.Common_OK, + title: isIncreaseButton ? strings.Premium_IncreaseLimit : strings.Common_OK, theme: SolidRoundedButtonComponent.Theme( backgroundColor: .black, - backgroundColors: [ - UIColor(rgb: 0x0077ff), - UIColor(rgb: 0x6b93ff), - UIColor(rgb: 0x8878ff), - UIColor(rgb: 0xe46ace) - ], + backgroundColors: gradientColors, foregroundColor: .white ), font: .bold, fontSize: 17.0, height: 50.0, cornerRadius: 10.0, - gloss: !reachedMaximumLimit, - animationName: !reachedMaximumLimit ? buttonAnimationName : nil, + gloss: isIncreaseButton, + animationName: isIncreaseButton ? buttonAnimationName : nil, iconPosition: .right, action: { [weak component] in guard let component = component else { return } component.dismiss() - component.action() + if isIncreaseButton { + component.action() + } } ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), transition: context.transition ) + var textOffset: CGFloat = 228.0 + if isPremiumDisabled { + textOffset -= 68.0 + } + context.add(title .position(CGPoint(x: context.availableSize.width / 2.0, y: 28.0)) ) context.add(text - .position(CGPoint(x: context.availableSize.width / 2.0, y: 228.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: textOffset)) ) - let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: 228.0 + ceil(text.size.height / 2.0) + 38.0), size: button.size) + let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: textOffset + ceil(text.size.height / 2.0) + 38.0), size: button.size) context.add(button .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) ) diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 0d35423853..e4f47cf261 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -167,7 +167,8 @@ private final class LimitComponent: CombinedComponent { activeTitleColor: component.activeTextColor, badgeIconName: "", badgeText: nil, - badgePosition: 0.0 + badgePosition: 0.0, + isPremiumDisabled: false ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), transition: .immediate diff --git a/submodules/PremiumUI/Sources/RollingCountLabel.swift b/submodules/PremiumUI/Sources/RollingCountLabel.swift index 48195fe479..5191a5e775 100644 --- a/submodules/PremiumUI/Sources/RollingCountLabel.swift +++ b/submodules/PremiumUI/Sources/RollingCountLabel.swift @@ -30,7 +30,6 @@ open class RollingLabel: UILabel { open var showSymbol = false private var scrollLayers: [CAScrollLayer] = [] private var scrollLabels: [UILabel] = [] - private let duration = 1.12 private let durationOffset = 0.2 private let textsNotAnimated = [","] @@ -38,26 +37,26 @@ open class RollingLabel: UILabel { self.suffix = suffix } - func configure(with string: String) { - fullText = string + func configure(with string: String, duration: Double = 0.9) { + self.fullText = string - clean() - setupSubviews() + self.clean() + self.setupSubviews() self.text = " " - self.animate() + self.animate(duration: duration) } - private func animate(ascending: Bool = true) { - createAnimations(ascending: ascending) + private func animate(ascending: Bool = true, duration: Double) { + self.createAnimations(ascending: ascending, duration: duration) } private func clean() { self.text = nil self.subviews.forEach { $0.removeFromSuperview() } self.layer.sublayers?.forEach { $0.removeFromSuperlayer() } - scrollLayers.removeAll() - scrollLabels.removeAll() + self.scrollLayers.removeAll() + self.scrollLabels.removeAll() } private func setupSubviews() { @@ -168,7 +167,7 @@ open class RollingLabel: UILabel { } } - private func createAnimations(ascending: Bool) { + private func createAnimations(ascending: Bool, duration: Double) { var offset: CFTimeInterval = 0.0 for scrollLayer in scrollLayers { diff --git a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift index b4535bdb2d..33ff458566 100644 --- a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift +++ b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift @@ -257,7 +257,7 @@ private class StickerNode: ASDisplayNode { } } - let placeholderFrame = CGRect(origin: .zero, size: imageSize) + let placeholderFrame = CGRect(origin: CGPoint(x: -10.0, y: 0.0), size: imageSize) let thumbnailDimensions = PixelDimensions(width: 512, height: 512) self.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: self.file.immediateThumbnailData, size: placeholderFrame.size, imageSize: thumbnailDimensions.cgSize) self.placeholderNode.frame = placeholderFrame @@ -297,7 +297,7 @@ private class StickersCarouselNode: ASDisplayNode, UIScrollViewDelegate { super.init() - self.clipsToBounds = true +// self.clipsToBounds = true self.addSubnode(self.scrollNode) self.scrollNode.addSubnode(self.tapNode) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index ee06a4a8e9..38403b25a6 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -416,13 +416,18 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The let _ = telegramWallpapers(postbox: context.account.postbox, network: context.account.network).start() let currentAppIcon: PresentationAppIcon? - let appIcons = context.sharedContext.applicationBindings.getAvailableAlternateIcons() + var appIcons = context.sharedContext.applicationBindings.getAvailableAlternateIcons() if let alternateIconName = context.sharedContext.applicationBindings.getAlternateIconName() { currentAppIcon = appIcons.filter { $0.name == alternateIconName }.first } else { currentAppIcon = appIcons.filter { $0.isDefault }.first } + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + if premiumConfiguration.isPremiumDisabled { + appIcons = appIcons.filter { !$0.isPremium } + } + let availableAppIcons: Signal<[PresentationAppIcon], NoError> = .single(appIcons) let currentAppIconName = ValuePromise() currentAppIconName.set(currentAppIcon?.name ?? "Blue") diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 443f256dda..04b7b114c5 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -640,31 +640,6 @@ private final class StickerPackContainer: ASDisplayNode { self.currentStickerPack = (info, items, installed) - if installed { - let text: String - if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { - text = self.presentationData.strings.StickerPack_RemoveStickerCount(info.count) - } else { - text = self.presentationData.strings.StickerPack_RemoveMaskCount(info.count) - } - self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal) - self.buttonNode.setBackgroundImage(nil, for: []) - } else { - let text: String - if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { - text = self.presentationData.strings.StickerPack_AddStickerCount(info.count) - } else { - text = self.presentationData.strings.StickerPack_AddMaskCount(info.count) - } - self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) - let roundedAccentBackground = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - })?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11) - self.buttonNode.setBackgroundImage(roundedAccentBackground, for: []) - } - if self.titleNode.attributedText == nil { if let titlePlaceholderNode = self.titlePlaceholderNode { self.titlePlaceholderNode = nil @@ -678,6 +653,9 @@ private final class StickerPackContainer: ASDisplayNode { updateLayout = true + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) + var generalItems: [StickerPackItem] = [] var premiumItems: [StickerPackItem] = [] @@ -711,11 +689,39 @@ private final class StickerPackContainer: ASDisplayNode { addItem(item, false, false) } - if !premiumItems.isEmpty { - for item in premiumItems { - addItem(item, true, !hasPremium) + if !premiumConfiguration.isPremiumDisabled { + if !premiumItems.isEmpty { + for item in premiumItems { + addItem(item, true, !hasPremium) + } } } + + if installed { + let text: String + if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + text = self.presentationData.strings.StickerPack_RemoveStickerCount(Int32(entries.count)) + } else { + text = self.presentationData.strings.StickerPack_RemoveMaskCount(Int32(entries.count)) + } + self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal) + self.buttonNode.setBackgroundImage(nil, for: []) + } else { + let text: String + if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + text = self.presentationData.strings.StickerPack_AddStickerCount(Int32(entries.count)) + } else { + text = self.presentationData.strings.StickerPack_AddMaskCount(Int32(entries.count)) + } + self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) + let roundedAccentBackground = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) + })?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11) + self.buttonNode.setBackgroundImage(roundedAccentBackground, for: []) + } + } let previousEntries = self.currentEntries self.currentEntries = entries diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift index 855859bbe1..e81333433e 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift @@ -209,7 +209,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC animationNode.updateLayout(size: imageSize) if let additionalAnimationNode = self.additionalAnimationNode { - additionalAnimationNode.frame = imageFrame.offsetBy(dx: -imageFrame.width * 0.245 + 21, dy: -1.0).insetBy(dx: -imageFrame.width * 0.245, dy: -imageFrame.height * 0.245) + additionalAnimationNode.frame = imageFrame.offsetBy(dx: -imageFrame.width * 0.245 + 21.0, dy: -1.0).insetBy(dx: -imageFrame.width * 0.245, dy: -imageFrame.height * 0.245) additionalAnimationNode.updateLayout(size: additionalAnimationNode.frame.size) } } diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Icons/Black.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Black.imageset/Contents.json new file mode 100644 index 0000000000..0e16260166 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Black.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "black@270x270.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Icons/Black.imageset/black@270x270.png b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Black.imageset/black@270x270.png new file mode 100644 index 0000000000..93c4d590ce Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Black.imageset/black@270x270.png differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Icons/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Icons/Premium.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Premium.imageset/Contents.json new file mode 100644 index 0000000000..687a0cb1b4 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Premium.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "premium@270x270.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Icons/Premium.imageset/premium@270x270.png b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Premium.imageset/premium@270x270.png new file mode 100644 index 0000000000..e7b6b683ed Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Premium.imageset/premium@270x270.png differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Icons/Turbo.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Turbo.imageset/Contents.json new file mode 100644 index 0000000000..5265842f30 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Turbo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "turbo@270x270.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Icons/Turbo.imageset/turbo@270x270.png b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Turbo.imageset/turbo@270x270.png new file mode 100644 index 0000000000..2f7da271e8 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Icons/Turbo.imageset/turbo@270x270.png differ diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index c9dc7f0b23..944d5bcb33 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -286,7 +286,14 @@ public final class AccountContextImpl: AccountContext { strongSelf.animatedEmojiStickers = stickers }) - self.userLimitsConfigurationDisposable = (self.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false)) + self.userLimitsConfigurationDisposable = (self.account.postbox.peerView(id: self.account.peerId) + |> mapToSignal { peerView -> Signal in + if let peer = peerView.peers[peerView.peerId] { + return self.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: peer.isPremium)) + } else { + return .complete() + } + } |> deliverOnMainQueue).start(next: { [weak self] value in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 87c29fc9c5..4d6e67446b 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -33,6 +33,7 @@ import TelegramAudio import DebugSettingsUI import BackgroundTasks import UIKitRuntimeUtils +import StoreKit #if canImport(AppCenter) import AppCenter @@ -622,6 +623,14 @@ private func extractAccountManagerState(records: AccountRecordsView take(1) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 06871648d3..d6290331ff 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1029,7 +1029,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } actions.context = strongSelf.context - + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) + if canAddMessageReactions(message: topMessage), let availableReactions = availableReactions, let allowedReactions = allowedReactions { var hasPremiumPlaceholder = false filterReactions: for reaction in availableReactions.reactions { @@ -1067,7 +1069,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G largeApplicationAnimation: reaction.effectAnimation ))) } - if hasPremiumPlaceholder { + + if hasPremiumPlaceholder && !premiumConfiguration.isPremiumDisabled { actions.reactionItems.append(.premium) } } @@ -7973,8 +7976,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G 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, undoText: nil), elevatedLayout: true, action: { _ in return false }), with: nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = strongSelf.presentationData.strings.Premium_MaxFavedStickersFinalText } else { text = strongSelf.presentationData.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string @@ -12391,6 +12395,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func displayPremiumStickerTooltip(file: TelegramMediaFile, message: Message) { + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) + guard !premiumConfiguration.isPremiumDisabled else { + return + } + var currentOverlayController: UndoOverlayController? self.window?.forEachController({ controller in diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 471987ee10..179dbe35d0 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -394,12 +394,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }) }))) - if !chatPresentationInterfaceState.isPremium { + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + if !chatPresentationInterfaceState.isPremium && !premiumConfiguration.isPremiumDisabled { actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0)), badge: nil, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor) }, iconSource: nil, action: { c, _ in c.dismiss(completion: { - controllerInteraction.navigationController()?.pushViewController(PremiumIntroScreen(context: context, source: .ads)) + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: context, subject: .noAds, action: { + let controller = PremiumIntroScreen(context: context, source: .ads) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + controllerInteraction.navigationController()?.pushViewController(controller) }) }))) } @@ -1225,8 +1234,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState case .generic: controllerInteraction.presentControllerInCurrent(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = presentationData.strings.Premium_MaxSavedGifsFinalText } else { text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index a600739cf9..147a4da640 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -261,7 +261,7 @@ func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, strings: Presen return entries } -func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, trendingPacks: [FeaturedStickerPackItem], installedPacks: Set, premiumStickers: OrderedItemListView? = nil, trendingIsDismissed: Bool = false, hasSearch: Bool = true, hasAccessories: Bool = true, strings: PresentationStrings, theme: PresentationTheme, hasPremium: Bool) -> [ChatMediaInputGridEntry] { +func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, trendingPacks: [FeaturedStickerPackItem], installedPacks: Set, premiumStickers: OrderedItemListView? = nil, trendingIsDismissed: Bool = false, hasSearch: Bool = true, hasAccessories: Bool = true, strings: PresentationStrings, theme: PresentationTheme, hasPremium: Bool, isPremiumDisabled: Bool) -> [ChatMediaInputGridEntry] { var entries: [ChatMediaInputGridEntry] = [] if hasSearch && view.lower == nil { @@ -284,7 +284,11 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered savedStickerIds.insert(item.file.fileId.id) let index = ItemCollectionItemIndex(index: Int32(i), id: item.file.fileId.id) let stickerItem = StickerPackItem(index: index, file: item.file, indexKeys: []) - entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -3, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: nil, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) + if isPremiumDisabled && item.file.isPremiumSticker { + + } else { + entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -3, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: nil, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) + } } } } @@ -307,8 +311,13 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered if !savedStickerIds.contains(mediaId.id) { let index = ItemCollectionItemIndex(index: Int32(i), id: mediaId.id) let stickerItem = StickerPackItem(index: index, file: file, indexKeys: []) - entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -2, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: nil, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) - addedCount += 1 + + if isPremiumDisabled && file.isPremiumSticker { + + } else { + entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -2, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: nil, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) + addedCount += 1 + } } } } @@ -330,12 +339,16 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered if let item = peerSpecificPack.items[i] as? StickerPackItem { let index = ItemCollectionItemIndex(index: Int32(i), id: item.file.fileId.id) let stickerItem = StickerPackItem(index: index, file: item.file, indexKeys: []) - entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -1, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: canManagePeerSpecificPack, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) + if isPremiumDisabled && item.file.isPremiumSticker { + + } else { + entries.append(.sticker(index: ItemCollectionViewEntryIndex(collectionIndex: -1, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo, canManagePeerSpecificPack: canManagePeerSpecificPack, maybeManageable: hasAccessories, theme: theme, isLocked: stickerItem.file.isPremiumSticker && !hasPremium)) + } } } } - if let premiumStickers = premiumStickers, !premiumStickers.items.isEmpty && hasPremium { + if let premiumStickers = premiumStickers, !premiumStickers.items.isEmpty && hasPremium && !isPremiumDisabled { let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.premium.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_PremiumStickers.uppercased(), shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0) for i in 0 ..< premiumStickers.items.count { if let item = premiumStickers.items[i].contents.get(RecentMediaItem.self) { @@ -351,7 +364,11 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered for entry in view.entries { if let item = entry.item as? StickerPackItem { - entries.append(.sticker(index: entry.index, stickerItem: item, stickerPackInfo: stickerPackInfos[entry.index.collectionId], canManagePeerSpecificPack: false, maybeManageable: hasAccessories, theme: theme, isLocked: item.file.isPremiumSticker && !hasPremium)) + if isPremiumDisabled && item.file.isPremiumSticker { + + } else { + entries.append(.sticker(index: entry.index, stickerItem: item, stickerPackInfo: stickerPackInfos[entry.index.collectionId], canManagePeerSpecificPack: false, maybeManageable: hasAccessories, theme: theme, isLocked: item.file.isPremiumSticker && !hasPremium)) + } } } @@ -1102,6 +1119,8 @@ final class ChatMediaInputNode: ChatInputNode { return animatedEmojiStickers } + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + let previousView = Atomic(value: nil) let transitionQueue = Queue() let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions, self.panelIsFocusedPromise.get(), ApplicationSpecificNotice.dismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager), temporaryPackOrder.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId)) @@ -1142,7 +1161,7 @@ final class ChatMediaInputNode: ChatInputNode { let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, temporaryPackOrder: temporaryPackOrder, trendingIsDismissed: trendingIsDismissed, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, theme: theme, strings: strings, premiumStickers: hasPremium ? premiumStickers : nil, expanded: panelExpanded, reorderable: true) let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, strings: strings, reactions: reactions, animatedEmojiStickers: animatedEmojiStickers, expanded: panelExpanded) - var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, trendingPacks: trendingPacks, installedPacks: installedPacks, premiumStickers: premiumStickers, trendingIsDismissed: trendingIsDismissed, strings: strings, theme: theme, hasPremium: hasPremium) + var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, trendingPacks: trendingPacks, installedPacks: installedPacks, premiumStickers: premiumStickers, trendingIsDismissed: trendingIsDismissed, strings: strings, theme: theme, hasPremium: hasPremium, isPremiumDisabled: premiumConfiguration.isPremiumDisabled) if view.higher == nil { var hasTopSeparator = true @@ -1471,8 +1490,9 @@ final class ChatMediaInputNode: ChatInputNode { case .generic: controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = presentationData.strings.Premium_MaxSavedGifsFinalText } else { text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string @@ -1578,8 +1598,9 @@ final class ChatMediaInputNode: ChatInputNode { case .generic: strongSelf.controllerInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.strings.Conversation_StickerAddedToFavorites : strongSelf.strings.Conversation_StickerRemovedFromFavorites, undoText: nil), elevatedLayout: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = strongSelf.strings.Premium_MaxFavedStickersFinalText } else { text = strongSelf.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string @@ -1730,8 +1751,9 @@ final class ChatMediaInputNode: ChatInputNode { case .generic: strongSelf.controllerInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.strings.Conversation_StickerAddedToFavorites : strongSelf.strings.Conversation_StickerRemovedFromFavorites, undoText: nil), elevatedLayout: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = strongSelf.strings.Premium_MaxFavedStickersFinalText } else { text = strongSelf.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index cc0a0dbc80..be902da0ea 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -594,7 +594,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { } let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, theme: theme, strings: strings, hasGifs: false, hasSettings: false) - let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: [], installedPacks: installedPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme, hasPremium: false) + let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: [], installedPacks: installedPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme, hasPremium: false, isPremiumDisabled: true) let (previousPanelEntries, previousGridEntries) = previousStickerEntries.swap((panelEntries, gridEntries)) return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: stickersInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: stickersInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty) @@ -629,7 +629,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { } let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, theme: theme, strings: strings, hasGifs: false, hasSettings: false) - let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: [], installedPacks: installedPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme, hasPremium: false) + let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: [], installedPacks: installedPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme, hasPremium: false, isPremiumDisabled: true) let (previousPanelEntries, previousGridEntries) = previousMaskEntries.swap((panelEntries, gridEntries)) return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: masksInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: masksInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty) diff --git a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift index acd504c0df..39986aa3ae 100644 --- a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift +++ b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift @@ -502,8 +502,9 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { case .generic: strongSelf.controller?.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil), elevatedLayout: false, action: { _ in return false }), with: nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = strongSelf.presentationData.strings.Premium_MaxFavedStickersFinalText } else { text = strongSelf.presentationData.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string @@ -589,8 +590,9 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { case .generic: strongSelf.controller?.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil), elevatedLayout: false, action: { _ in return false }), with: nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = strongSelf.presentationData.strings.Premium_MaxFavedStickersFinalText } else { text = strongSelf.presentationData.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift index f66fd69d11..23a4da53d9 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -203,8 +203,9 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont case .generic: interfaceInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = presentationData.strings.Premium_MaxSavedGifsFinalText } else { text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string diff --git a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift index d13163808d..4cbfdca401 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift @@ -193,8 +193,9 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { case .generic: strongSelf.interfaceInteraction?.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.strings.Conversation_StickerAddedToFavorites : strongSelf.strings.Conversation_StickerRemovedFromFavorites, undoText: nil), elevatedLayout: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = strongSelf.strings.Premium_MaxFavedStickersFinalText } else { text = strongSelf.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string diff --git a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift index 7c9dfcfae3..74f9b3eca9 100644 --- a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift +++ b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift @@ -145,8 +145,9 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie case .generic: strongSelf.getControllerInteraction?()?.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.strings.Conversation_StickerAddedToFavorites : strongSelf.strings.Conversation_StickerRemovedFromFavorites, undoText: nil), elevatedLayout: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = strongSelf.strings.Premium_MaxFavedStickersFinalText } else { text = strongSelf.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index f4730c6517..e01e712ec4 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -119,7 +119,7 @@ public final class NotificationViewControllerImpl { }, applicationInForeground: .single(false), applicationIsActive: .single(false), clearMessageNotifications: { _ in }, pushIdleTimerExtension: { return EmptyDisposable - }, openSettings: {}, openAppStorePage: {}, registerForNotifications: { _ in }, requestSiriAuthorization: { _ in }, siriAuthorization: { return .notDetermined }, getWindowHost: { + }, openSettings: {}, openAppStorePage: {}, openSubscriptions: {}, registerForNotifications: { _ in }, requestSiriAuthorization: { _ in }, siriAuthorization: { return .notDetermined }, getWindowHost: { return nil }, presentNativeController: { _ in }, dismissNativeController: { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index dece83c726..c028aea13b 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -219,6 +219,14 @@ private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode { return contentHeight } + + func animateErrorIfNeeded() { + for (_, itemNode) in self.itemNodes { + if let itemNode = itemNode as? PeerInfoScreenMultilineInputItemNode { + itemNode.animateErrorIfNeeded() + } + } + } } final class PeerInfoSelectionPanelNode: ASDisplayNode { @@ -724,9 +732,12 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p interaction.openSettings(.language) })) - items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: "Telegram Premium", icon: PresentationResourcesSettings.premium, action: { - interaction.openSettings(.premium) - })) + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + if !premiumConfiguration.isPremiumDisabled { + items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: presentationData.strings.Settings_Premium, icon: PresentationResourcesSettings.premium, action: { + interaction.openSettings(.premium) + })) + } /*items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 100, label: .text(""), text: "Payment Method", icon: PresentationResourcesSettings.language, action: { interaction.openPaymentMethod() @@ -2633,7 +2644,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }) strongSelf.controller?.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, style: .plain, target: strongSelf, action: #selector(strongSelf.editingCancelPressed)), animated: true) case .done, .cancel: - (strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.3, curve: .linear)) strongSelf.view.endEditing(true) if case .done = key { guard let data = strongSelf.data else { @@ -2646,6 +2656,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let lastName = strongSelf.headerNode.editingContentNode.editingTextForKey(.lastName) ?? "" let bio = strongSelf.state.updatingBio + if let bio = bio { + if Int32(bio.count) > strongSelf.context.userLimits.maxAboutLength { + for (_, section) in strongSelf.editingSections { + section.animateErrorIfNeeded() + } + strongSelf.hapticFeedback?.error() + return + } + } + if peer.firstName != firstName || peer.lastName != lastName || (bio != nil && bio != cachedData.about) { var updateNameSignal: Signal = .complete() var hasProgress = false @@ -2886,6 +2906,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }, completion: nil) strongSelf.controller?.navigationItem.setLeftBarButton(nil, animated: true) } + (strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.3, curve: .linear)) case .select: strongSelf.state = strongSelf.state.withSelectedMessageIds(Set()) if let (layout, navigationHeight) = strongSelf.validLayout { @@ -2980,11 +3001,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return } - let controller = PremiumIntroScreen(context: strongSelf.context, source: .profile(strongSelf.peerId)) - controller.sourceView = sourceView - controller.containerView = strongSelf.controller?.navigationController?.view - controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor - strongSelf.controller?.push(controller) + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) + if !premiumConfiguration.isPremiumDisabled { + let controller = PremiumIntroScreen(context: strongSelf.context, source: .profile(strongSelf.peerId)) + controller.sourceView = sourceView + controller.containerView = strongSelf.controller?.navigationController?.view + controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor + strongSelf.controller?.push(controller) + } } self.headerNode.displayAvatarContextMenu = { [weak self] node, gesture in diff --git a/submodules/TelegramUI/Sources/PeerInfoScreenMultilineInputtem.swift b/submodules/TelegramUI/Sources/PeerInfoScreenMultilineInputtem.swift index 41c7c682c4..8127aa0509 100644 --- a/submodules/TelegramUI/Sources/PeerInfoScreenMultilineInputtem.swift +++ b/submodules/TelegramUI/Sources/PeerInfoScreenMultilineInputtem.swift @@ -29,7 +29,7 @@ final class PeerInfoScreenMultilineInputItem: PeerInfoScreenItem { } } -private final class PeerInfoScreenMultilineInputItemNode: PeerInfoScreenItemNode { +final class PeerInfoScreenMultilineInputItemNode: PeerInfoScreenItemNode { private let bottomSeparatorNode: ASDisplayNode private let maskNode: ASImageNode @@ -118,4 +118,8 @@ private final class PeerInfoScreenMultilineInputItemNode: PeerInfoScreenItemNode return height } + + func animateErrorIfNeeded() { + self.itemNode?.animateErrorIfNeeded() + } } diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index 3181503f85..aa7280daf8 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -193,7 +193,10 @@ public class ShareRootControllerImpl { }, applicationInForeground: .single(false), applicationIsActive: .single(false), clearMessageNotifications: { _ in }, pushIdleTimerExtension: { return EmptyDisposable - }, openSettings: {}, openAppStorePage: {}, registerForNotifications: { _ in }, requestSiriAuthorization: { _ in }, siriAuthorization: { return .notDetermined }, getWindowHost: { + }, openSettings: { + }, openAppStorePage: { + }, openSubscriptions: { + }, registerForNotifications: { _ in }, requestSiriAuthorization: { _ in }, siriAuthorization: { return .notDetermined }, getWindowHost: { return nil }, presentNativeController: { _ in }, dismissNativeController: { diff --git a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift index 2808ad948f..b31262461c 100644 --- a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift @@ -149,8 +149,9 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode { case .generic: strongSelf.interfaceInteraction?.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: item.file, title: nil, text: !isStarred ? strongSelf.strings.Conversation_StickerAddedToFavorites : strongSelf.strings.Conversation_StickerRemovedFromFavorites, undoText: nil), elevatedLayout: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) let text: String - if limit == premiumLimit { + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { text = strongSelf.strings.Premium_MaxFavedStickersFinalText } else { text = strongSelf.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string