diff --git a/Telegram/BUILD b/Telegram/BUILD index 5d0c5534be..de6d5c940d 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -337,6 +337,7 @@ swift_library( "//submodules/OverlayStatusController:OverlayStatusControllerResources", "//submodules/PasswordSetupUI:PasswordSetupUIResources", "//submodules/PasswordSetupUI:PasswordSetupUIAssets", + "//submodules/PremiumUI:PremiumUIResources", "//submodules/TelegramUI:TelegramUIResources", "//submodules/TelegramUI:TelegramUIAssets", ":GeneratedPresentationStrings/Resources/PresentationStrings.data", diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b4c13880e2..22487de47f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7548,7 +7548,7 @@ Sorry for the inconvenience."; "Chat.MultipleTypingPair" = "%@ and %@"; "Chat.MultipleTypingMore" = "%@ and %@ others"; -"DialogList.ExtendedPinLimitError" = "Sorry, you can 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."; +"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."; @@ -7564,3 +7564,35 @@ Sorry for the inconvenience."; "Premium.MaxFoldersCountText" = "You have reached the limit of **%@** folders. You can double the limit to **%@** folders by subscribing to **Telegram Premium**."; "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.Title" = "Telegram Premium"; +"Premium.Description" = "Go **beyond the limits**, get **exclusive features** and support us by subscribing to **Telegram Premium**."; + +"Premium.DoubledLimits" = "Doubled Limits"; +"Premium.DoubledLimitsInfo" = "Up to 1000 channels, 20 folders, 10 pins, 20 public links, 4 accounts and more."; + +"Premium.UploadSize" = "4 GB Upload Size"; +"Premium.UploadSizeInfo" = "Increased upload size from 2 GB to 4 GB per document, unlimited storage overall."; + +"Premium.FasterSpeed" = "Faster Download Speed"; +"Premium.FasterSpeedInfo" = "No more limits on the speed with which media and documents are downloaded."; + +"Premium.NoAds" = "No Ads"; +"Premium.NoAdsInfo" = "No more ads in public channels where Telegram sometimes shows ads."; + +"Premium.Reactions" = "Unique Reactions"; +"Premium.ReactionsInfo" = "Additional animated reactions on messages, available only to the Premium subscribers."; + +"Premium.Stickers" = "Premium Stickers"; +"Premium.StickersInfo" = "Exclusive enlarged stickers featuring additional effects, updated monthly."; + +"Premium.Badge" = "Profile Badge"; +"Premium.BadgeInfo" = "A badge next to your name showing that you are helping support Telegram."; + +"Premium.Avatar" = "Animated Profile Pictures"; +"Premium.AvatarInfo" = "Video avatars animated in chat lists and chats to allow for additional self-expression."; + +"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 addf2778a3..74850a488d 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -11,6 +11,7 @@ import OverlayStatusController import AlertUI import PresentationDataUtils import UndoUI +import PremiumUI func archiveContextMenuItems(context: AccountContext, groupId: PeerGroupId, chatListController: ChatListControllerImpl?) -> Signal<[ContextMenuItem], NoError> { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) @@ -318,29 +319,16 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch case .done: f(.default) case .limitExceeded: - var subItems: [ContextMenuItem] = [] + f(.default) - subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) - }, action: { c, _ in - c.popItems() - }))) - subItems.append(.separator) - - subItems.append(.action(ContextMenuActionItem(text: strings.DialogList_ExtendedPinLimitError("5", "10").string, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in - return nil - }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) - - subItems.append(.action(ContextMenuActionItem(text: strings.Premium_IncreaseLimit, icon: { _ in - return nil - }, action: { _, f in - f(.default) - - }))) - - c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) + let limitScreen = PremiumLimitScreen(context: context, subject: .pins, action: { + let premiumScreen = PremiumIntroScreen(context: context, action: { + + }) + chatListController?.push(premiumScreen) + }) + chatListController?.push(limitScreen) } - }) }))) } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 0ff20e81de..804d44c4c9 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -866,6 +866,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } + self.chatListDisplayNode.containerNode.push = { [weak self] c in + if let strongSelf = self { + strongSelf.push(c) + } + } + self.chatListDisplayNode.containerNode.toggleArchivedFolderHiddenByDefault = { [weak self] in guard let strongSelf = self else { return diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index c091b3d921..39ba025625 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -468,6 +468,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { previousItemNode.listNode.activateSearch = nil previousItemNode.listNode.presentAlert = nil previousItemNode.listNode.present = nil + previousItemNode.listNode.push = nil previousItemNode.listNode.toggleArchivedFolderHiddenByDefault = nil previousItemNode.listNode.hidePsa = nil previousItemNode.listNode.deletePeerChat = nil @@ -494,6 +495,9 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { itemNode.listNode.present = { [weak self] c in self?.present?(c) } + itemNode.listNode.push = { [weak self] c in + self?.push?(c) + } itemNode.listNode.toggleArchivedFolderHiddenByDefault = { [weak self] in self?.toggleArchivedFolderHiddenByDefault?() } @@ -557,6 +561,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { var activateSearch: (() -> Void)? var presentAlert: ((String) -> Void)? var present: ((ViewController) -> Void)? + var push: ((ViewController) -> Void)? var toggleArchivedFolderHiddenByDefault: (() -> Void)? var hidePsa: ((EnginePeer.Id) -> Void)? var deletePeerChat: ((EnginePeer.Id, Bool) -> Void)? diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 6132643570..fc3f2ccc15 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -619,6 +619,7 @@ public final class ChatListNode: ListView { public var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? public var presentAlert: ((String) -> Void)? public var present: ((ViewController) -> Void)? + public var push: ((ViewController) -> Void)? public var toggleArchivedFolderHiddenByDefault: (() -> Void)? public var hidePsa: ((EnginePeer.Id) -> Void)? public var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?) -> Void)? @@ -842,15 +843,17 @@ public final class ChatListNode: ListView { case .done: break case .limitExceeded: - let controller = LimitScreen(context: strongSelf.context, subject: .pins) - strongSelf.present?(controller) -// let text: String -// if chatListFilter != nil { -// text = strongSelf.currentState.presentationData.strings.DialogList_UnknownPinLimitError -// } else { -// text = strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").string -// } -// strongSelf.presentAlert?(text) + var dismissImpl: (() -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .pins, action: { [weak self] in + let premiumScreen = PremiumIntroScreen(context: context, action: { + dismissImpl?() + }) + self?.push?(premiumScreen) + }) + dismissImpl = { [weak controller] in + controller?.dismiss() + } + strongSelf.push?(controller) } } }) diff --git a/submodules/Components/SheetComponent/BUILD b/submodules/Components/SheetComponent/BUILD new file mode 100644 index 0000000000..1b7f99b7f4 --- /dev/null +++ b/submodules/Components/SheetComponent/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "SheetComponent", + module_name = "SheetComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display:Display", + "//submodules/ComponentFlow:ComponentFlow", + "//submodules/Components/ViewControllerComponent:ViewControllerComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift new file mode 100644 index 0000000000..cffc02b23d --- /dev/null +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -0,0 +1,174 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import ViewControllerComponent + +public final class SheetComponentEnvironment: Equatable { + public let isDisplaying: Bool + public let dismiss: () -> Void + + public init(isDisplaying: Bool, dismiss: @escaping () -> Void) { + self.isDisplaying = isDisplaying + self.dismiss = dismiss + } + + public static func ==(lhs: SheetComponentEnvironment, rhs: SheetComponentEnvironment) -> Bool { + if lhs.isDisplaying != rhs.isDisplaying { + return false + } + return true + } +} + +public final class SheetComponent: Component { + public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment) + + public let content: AnyComponent + public let backgroundColor: UIColor + public let animateOut: ActionSlot> + + public init(content: AnyComponent, backgroundColor: UIColor, animateOut: ActionSlot>) { + self.content = content + self.backgroundColor = backgroundColor + self.animateOut = animateOut + } + + public static func ==(lhs: SheetComponent, rhs: SheetComponent) -> Bool { + if lhs.content != rhs.content { + return false + } + if lhs.backgroundColor != rhs.backgroundColor { + return false + } + if lhs.animateOut != rhs.animateOut { + return false + } + + return true + } + + public final class View: UIView, UIScrollViewDelegate { + private let dimView: UIView + private let scrollView: UIScrollView + private let backgroundView: UIView + private let contentView: ComponentHostView + + private var previousIsDisplaying: Bool = false + private var dismiss: (() -> Void)? + + override init(frame: CGRect) { + self.dimView = UIView() + self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.4) + + self.scrollView = UIScrollView() + self.scrollView.delaysContentTouches = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceVertical = true + + self.backgroundView = UIView() + self.backgroundView.layer.cornerRadius = 10.0 + self.backgroundView.layer.masksToBounds = true + + self.contentView = ComponentHostView() + + super.init(frame: frame) + + self.addSubview(self.dimView) + + self.scrollView.addSubview(self.backgroundView) + self.scrollView.addSubview(self.contentView) + self.addSubview(self.scrollView) + + self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimViewTapGesture(_:)))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func dimViewTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.dismiss?() + } + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + } + + public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + } + + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.backgroundView.bounds.contains(self.convert(point, to: self.backgroundView)) { + return self.dimView + } + + return super.hitTest(point, with: event) + } + + private func animateOut(completion: @escaping () -> Void) { + self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) + self.scrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.bounds.height - self.scrollView.contentInset.top), duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + } + + func update(component: SheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + component.animateOut.connect { [weak self] completion in + guard let strongSelf = self else { + return + } + strongSelf.animateOut { + completion(Void()) + } + } + + if self.backgroundView.backgroundColor != component.backgroundColor { + self.backgroundView.backgroundColor = component.backgroundColor + } + + transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) + + let contentSize = self.contentView.update( + transition: transition, + component: component.content, + environment: { + environment[ChildEnvironmentType.self] + }, + containerSize: CGSize(width: availableSize.width, height: .greatestFiniteMagnitude) + ) + + transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) + transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) + self.scrollView.contentSize = contentSize + self.scrollView.contentInset = UIEdgeInsets(top: max(0.0, availableSize.height - contentSize.height), left: 0.0, bottom: 0.0, right: 0.0) + + 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) + } + self.previousIsDisplaying = environment[SheetComponentEnvironment.self].value.isDisplaying + + self.dismiss = environment[SheetComponentEnvironment.self].value.dismiss + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/GameUI/Sources/GameControllerNode.swift b/submodules/GameUI/Sources/GameControllerNode.swift index 551c9fcd88..5ef9eab990 100644 --- a/submodules/GameUI/Sources/GameControllerNode.swift +++ b/submodules/GameUI/Sources/GameControllerNode.swift @@ -149,7 +149,7 @@ final class GameControllerNode: ViewControllerTracingNode { self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, text, account, _ in if let strongSelf = self, let message = strongSelf.message { let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: message.id, to: $0, as: nil) } - return .single(.preparing) + return .single(.preparing(false)) |> then( combineLatest(signals) |> mapToSignal { _ -> Signal in return .complete() } diff --git a/submodules/PaymentMethodUI/BUILD b/submodules/PaymentMethodUI/BUILD index ceeeb902d4..50a49ff478 100644 --- a/submodules/PaymentMethodUI/BUILD +++ b/submodules/PaymentMethodUI/BUILD @@ -14,6 +14,7 @@ swift_library( "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/ComponentFlow:ComponentFlow", "//submodules/Components/ViewControllerComponent:ViewControllerComponent", + "//submodules/Components/SheetComponent:SheetComponent", "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", "//submodules/Components/AnimatedStickerComponent:AnimatedStickerComponent", "//submodules/Components/BundleIconComponent:BundleIconComponent", diff --git a/submodules/PaymentMethodUI/Sources/AddPaymentMethodScheetScreen.swift b/submodules/PaymentMethodUI/Sources/AddPaymentMethodSheetScreen.swift similarity index 55% rename from submodules/PaymentMethodUI/Sources/AddPaymentMethodScheetScreen.swift rename to submodules/PaymentMethodUI/Sources/AddPaymentMethodSheetScreen.swift index aced0021f1..9fcd6e7172 100644 --- a/submodules/PaymentMethodUI/Sources/AddPaymentMethodScheetScreen.swift +++ b/submodules/PaymentMethodUI/Sources/AddPaymentMethodSheetScreen.swift @@ -4,180 +4,12 @@ import Display import ComponentFlow import ViewControllerComponent import AccountContext +import SheetComponent import AnimatedStickerComponent import SolidRoundedButtonComponent import MultilineTextComponent import PresentationDataUtils -public final class SheetComponentEnvironment: Equatable { - public let isDisplaying: Bool - public let dismiss: () -> Void - - public init(isDisplaying: Bool, dismiss: @escaping () -> Void) { - self.isDisplaying = isDisplaying - self.dismiss = dismiss - } - - public static func ==(lhs: SheetComponentEnvironment, rhs: SheetComponentEnvironment) -> Bool { - if lhs.isDisplaying != rhs.isDisplaying { - return false - } - return true - } -} - -public final class SheetComponent: Component { - public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment) - - public let content: AnyComponent - public let backgroundColor: UIColor - public let animateOut: ActionSlot> - - public init(content: AnyComponent, backgroundColor: UIColor, animateOut: ActionSlot>) { - self.content = content - self.backgroundColor = backgroundColor - self.animateOut = animateOut - } - - public static func ==(lhs: SheetComponent, rhs: SheetComponent) -> Bool { - if lhs.content != rhs.content { - return false - } - if lhs.backgroundColor != rhs.backgroundColor { - return false - } - if lhs.animateOut != rhs.animateOut { - return false - } - - return true - } - - public final class View: UIView, UIScrollViewDelegate { - private let dimView: UIView - private let scrollView: UIScrollView - private let backgroundView: UIView - private let contentView: ComponentHostView - - private var previousIsDisplaying: Bool = false - private var dismiss: (() -> Void)? - - override init(frame: CGRect) { - self.dimView = UIView() - self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.4) - - self.scrollView = UIScrollView() - self.scrollView.delaysContentTouches = false - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.scrollView.contentInsetAdjustmentBehavior = .never - } - self.scrollView.showsVerticalScrollIndicator = false - self.scrollView.showsHorizontalScrollIndicator = false - self.scrollView.alwaysBounceVertical = true - - self.backgroundView = UIView() - self.backgroundView.layer.cornerRadius = 10.0 - self.backgroundView.layer.masksToBounds = true - - self.contentView = ComponentHostView() - - super.init(frame: frame) - - self.addSubview(self.dimView) - - self.scrollView.addSubview(self.backgroundView) - self.scrollView.addSubview(self.contentView) - self.addSubview(self.scrollView) - - self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimViewTapGesture(_:)))) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc private func dimViewTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.dismiss?() - } - } - - public func scrollViewDidScroll(_ scrollView: UIScrollView) { - } - - public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - } - - public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - } - - override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.backgroundView.bounds.contains(self.convert(point, to: self.backgroundView)) { - return self.dimView - } - - return super.hitTest(point, with: event) - } - - private func animateOut(completion: @escaping () -> Void) { - self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - self.scrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.bounds.height - self.scrollView.contentInset.top), duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, removeOnCompletion: false, additive: true, completion: { _ in - completion() - }) - } - - func update(component: SheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - component.animateOut.connect { [weak self] completion in - guard let strongSelf = self else { - return - } - strongSelf.animateOut { - completion(Void()) - } - } - - if self.backgroundView.backgroundColor != component.backgroundColor { - self.backgroundView.backgroundColor = component.backgroundColor - } - - transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) - - let contentSize = self.contentView.update( - transition: transition, - component: component.content, - environment: { - environment[ChildEnvironmentType.self] - }, - containerSize: CGSize(width: availableSize.width, height: .greatestFiniteMagnitude) - ) - - transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) - transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) - self.scrollView.contentSize = contentSize - self.scrollView.contentInset = UIEdgeInsets(top: max(0.0, availableSize.height - contentSize.height), left: 0.0, bottom: 0.0, right: 0.0) - - 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) - } - self.previousIsDisplaying = environment[SheetComponentEnvironment.self].value.isDisplaying - - self.dismiss = environment[SheetComponentEnvironment.self].value.dismiss - - return availableSize - } - } - - public func makeView() -> View { - return View(frame: CGRect()) - } - - public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} - private final class AddPaymentMethodSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment diff --git a/submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift b/submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift index 940c50071d..ab330f00c9 100644 --- a/submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift +++ b/submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift @@ -80,16 +80,18 @@ final class IncreaseLimitFooterItemNode: ItemListControllerFooterItemNode { let backgroundColor = self.item.theme.list.itemCheckColors.fillColor let backgroundColors: [UIColor] + let icon: UIImage? if self.item.colorful { backgroundColors = [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)] - self.buttonNode.icon = UIImage(bundleImageName: "Premium/X2") + icon = UIImage(bundleImageName: "Premium/X2") } else { backgroundColors = [] - self.buttonNode.icon = nil + icon = nil } self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: backgroundColor, backgroundColors: backgroundColors, foregroundColor: self.item.theme.list.itemCheckColors.foregroundColor), animated: true) self.buttonNode.title = self.item.title + self.buttonNode.icon = icon self.buttonNode.pressed = { [weak self] in self?.item.action() diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index dafcf76cd0..6a0a5e17a1 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -1,5 +1,13 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") +filegroup( + name = "PremiumUIResources", + srcs = glob([ + "Resources/**/*", + ], exclude = ["Resources/**/.*"]), + visibility = ["//visibility:public"], +) + swift_library( name = "PremiumUI", module_name = "PremiumUI", @@ -28,8 +36,10 @@ swift_library( "//submodules/ComponentFlow:ComponentFlow", "//submodules/Components/ViewControllerComponent:ViewControllerComponent", "//submodules/Components/MultilineTextComponent:MultilineTextComponent", + "//submodules/Components/SheetComponent:SheetComponent", "//submodules/Components/BundleIconComponent:BundleIconComponent", "//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent", + "//submodules/Components/Forms/PrefixSectionGroupComponent:PrefixSectionGroupComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PremiumUI/Resources/flecks.png b/submodules/PremiumUI/Resources/flecks.png new file mode 100644 index 0000000000..cf180c7b1d Binary files /dev/null and b/submodules/PremiumUI/Resources/flecks.png differ diff --git a/submodules/PremiumUI/Resources/shine.png b/submodules/PremiumUI/Resources/shine.png new file mode 100644 index 0000000000..860b317f17 Binary files /dev/null and b/submodules/PremiumUI/Resources/shine.png differ diff --git a/submodules/PremiumUI/Resources/speckle.png b/submodules/PremiumUI/Resources/speckle.png new file mode 100644 index 0000000000..df10412377 Binary files /dev/null and b/submodules/PremiumUI/Resources/speckle.png differ diff --git a/submodules/PremiumUI/Resources/star.scn b/submodules/PremiumUI/Resources/star.scn new file mode 100644 index 0000000000..ad5f05949a Binary files /dev/null and b/submodules/PremiumUI/Resources/star.scn differ diff --git a/submodules/PremiumUI/Resources/texture.png b/submodules/PremiumUI/Resources/texture.png new file mode 100644 index 0000000000..351250b51e Binary files /dev/null and b/submodules/PremiumUI/Resources/texture.png differ diff --git a/submodules/PremiumUI/Sources/LimitScreen.swift b/submodules/PremiumUI/Sources/LimitScreen.swift deleted file mode 100644 index b171a138f6..0000000000 --- a/submodules/PremiumUI/Sources/LimitScreen.swift +++ /dev/null @@ -1,614 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import Postbox -import TelegramCore -import SwiftSignalKit -import AccountContext -import TelegramPresentationData -import PresentationDataUtils -import ComponentFlow -import ViewControllerComponent -import MultilineTextComponent -import BundleIconComponent -import SolidRoundedButtonComponent -import Markdown - -private final class LimitScreenComponent: CombinedComponent { - typealias EnvironmentType = ViewControllerComponentContainer.Environment - - let context: AccountContext - let subject: LimitScreen.Subject - let proceed: () -> Void - - init(context: AccountContext, subject: LimitScreen.Subject, proceed: @escaping () -> Void) { - self.context = context - self.subject = subject - self.proceed = proceed - } - - static func ==(lhs: LimitScreenComponent, rhs: LimitScreenComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.subject != rhs.subject { - return false - } - return true - } - - final class State: ComponentState { - private let context: AccountContext - - private var disposable: Disposable? - var limits: EngineConfiguration.UserLimits - var premiumLimits: EngineConfiguration.UserLimits - - init(context: AccountContext, subject: LimitScreen.Subject) { - self.context = context - self.limits = EngineConfiguration.UserLimits.defaultValue - self.premiumLimits = EngineConfiguration.UserLimits.defaultValue - - super.init() - - self.disposable = (context.engine.data.get( - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), - TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) - ) |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - let (limits, premiumLimits) = result - strongSelf.limits = limits - strongSelf.premiumLimits = premiumLimits - strongSelf.updated(transition: .immediate) - } - }) - } - - deinit { - self.disposable?.dispose() - } - } - - func makeState() -> State { - return State(context: self.context, subject: self.subject) - } - - static var body: Body { - let badgeBackground = Child(RoundedRectangle.self) - let badgeIcon = Child(BundleIconComponent.self) - let badgeText = Child(MultilineTextComponent.self) - - let title = Child(MultilineTextComponent.self) - let text = Child(MultilineTextComponent.self) - - let button = Child(SolidRoundedButtonComponent.self) - let cancel = Child(Button.self) - - return { context in - let environment = context.environment[ViewControllerComponentContainer.Environment.self].value - let component = context.component - let theme = environment.theme - let strings = environment.strings - - let state = context.state - let subject = component.subject - - let topInset: CGFloat = 34.0 + 38.0 - let sideInset: CGFloat = 16.0 + environment.safeInsets.left - let textSideInset: CGFloat = 24.0 + environment.safeInsets.left - - let iconName: String - let badgeString: String - let string: String - switch subject { - case .folders: - let limit = state.limits.maxFoldersCount - let premiumLimit = state.premiumLimits.maxFoldersCount - iconName = "Premium/Folder" - badgeString = "\(limit)" - string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string - case .chatsInFolder: - let limit = state.limits.maxFolderChatsCount - let premiumLimit = state.premiumLimits.maxFolderChatsCount - iconName = "Premium/Chat" - badgeString = "\(limit)" - string = strings.Premium_MaxChatsInFolderCountText("\(limit)", "\(premiumLimit)").string - case .pins: - let limit = state.limits.maxPinnedChatCount - let premiumLimit = state.premiumLimits.maxPinnedChatCount - iconName = "Premium/Pin" - badgeString = "\(limit)" - string = strings.DialogList_ExtendedPinLimitError("\(limit)", "\(premiumLimit)").string - case .files: - let limit = 2048 * 1024 * 1024 //state.limits.maxPinnedChatCount - let premiumLimit = 4096 * 1024 * 1024 //state.premiumLimits.maxPinnedChatCount - iconName = "Premium/File" - badgeString = 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 - } - - let badgeIcon = badgeIcon.update( - component: BundleIconComponent( - name: iconName, - tintColor: .white - ), - availableSize: context.availableSize, - transition: .immediate - ) - - let badgeText = badgeText.update( - component: MultilineTextComponent( - text: NSAttributedString( - string: badgeString, - font: Font.with(size: 24.0, design: .round, weight: .semibold, traits: []), - textColor: .white, - paragraphAlignment: .center - ), - horizontalAlignment: .center, - maximumNumberOfLines: 1 - ), - availableSize: context.availableSize, - transition: .immediate - ) - - let badgeBackground = badgeBackground.update( - component: RoundedRectangle( - colors: [UIColor(rgb: 0xa34fcf), UIColor(rgb: 0xc8498a), UIColor(rgb: 0xff7a23)], - cornerRadius: 23.5 - ), - availableSize: CGSize(width: badgeText.size.width + 67.0, height: 47.0), - transition: .immediate - ) - - let title = title.update( - component: MultilineTextComponent( - text: .plain(NSAttributedString( - string: strings.Premium_LimitReached, - font: Font.semibold(17.0), - textColor: theme.actionSheet.primaryTextColor, - paragraphAlignment: .center - )), - horizontalAlignment: .center, - maximumNumberOfLines: 1 - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude), - transition: .immediate - ) - - let textFont = Font.regular(16.0) - let boldTextFont = Font.semibold(16.0) - - let textColor = theme.actionSheet.primaryTextColor - let attributedText = parseMarkdownIntoAttributedString(string, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in - return nil - })) - - let text = text.update( - component: MultilineTextComponent( - text: .plain(attributedText), - horizontalAlignment: .center, - maximumNumberOfLines: 0, - lineSpacing: 0.2 - ), - availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), - transition: .immediate - ) - - let button = button.update( - component: SolidRoundedButtonComponent( - title: strings.Premium_IncreaseLimit, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: .black, - backgroundColors: [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)], - foregroundColor: .white - ), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, - gloss: false, - iconName: "Premium/X2", - iconPosition: .right, - action: { [weak component] in - guard let component = component else { - return - } - component.proceed() - } - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), - transition: context.transition - ) - - let cancel = cancel.update(component: Button( - content: AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)), - action: { - - } - ), - availableSize: context.availableSize, - transition: context.transition) - - let width = context.availableSize.width - - let badgeFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - badgeBackground.size.width) / 2.0), y: 33.0), size: badgeBackground.size) - context.add(badgeBackground - .position(CGPoint(x: badgeFrame.midX, y: badgeFrame.midY)) - ) - - let badgeIconFrame = CGRect(origin: CGPoint(x: badgeFrame.minX + 18.0, y: badgeFrame.minY + floor((badgeFrame.height - badgeIcon.size.height) / 2.0)), size: badgeIcon.size) - context.add(badgeIcon - .position(CGPoint(x: badgeIconFrame.midX, y: badgeIconFrame.midY)) - ) - - let badgeTextFrame = CGRect(origin: CGPoint(x: badgeFrame.maxX - badgeText.size.width - 15.0, y: badgeFrame.minY + floor((badgeFrame.height - badgeText.size.height) / 2.0)), size: badgeText.size) - context.add(badgeText - .position(CGPoint(x: badgeTextFrame.midX, y: badgeTextFrame.midY)) - ) - - context.add(title - .position(CGPoint(x: width / 2.0, y: topInset + 39.0)) - ) - context.add(text - .position(CGPoint(x: width / 2.0, y: topInset + 101.0)) - ) - - let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + 76.0 + text.size.height + 27.0), size: button.size) - context.add(button - .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) - ) - - context.add(cancel - .position(CGPoint(x: width / 2.0, y: topInset + 76.0 + text.size.height + 20.0 + button.size.height + 40.0)) - ) - - let contentSize = CGSize(width: context.availableSize.width, height: topInset + 76.0 + text.size.height + 20.0 + button.size.height + 40.0 + 33.0 + environment.safeInsets.bottom) - - return contentSize - } - } -} - -public class LimitScreen: ViewController { - final class Node: ViewControllerTracingNode, UIGestureRecognizerDelegate { - private var presentationData: PresentationData - private weak var controller: LimitScreen? - - private let component: AnyComponent - private let theme: PresentationTheme? - - let dim: ASDisplayNode - let wrappingView: UIView - let containerView: UIView - let hostView: ComponentHostView - - private var panGestureRecognizer: UIPanGestureRecognizer? - - private var currentIsVisible: Bool = false - private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? - - fileprivate var temporaryDismiss = false - - init(context: AccountContext, controller: LimitScreen, component: AnyComponent, theme: PresentationTheme?) { - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - - self.controller = controller - - self.component = component - self.theme = theme - - self.dim = ASDisplayNode() - self.dim.alpha = 0.0 - self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) - - self.wrappingView = UIView() - self.containerView = UIView() - self.hostView = ComponentHostView() - - super.init() - - self.containerView.clipsToBounds = true - self.containerView.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor - - self.addSubnode(self.dim) - - self.view.addSubview(self.wrappingView) - self.wrappingView.addSubview(self.containerView) - self.containerView.addSubview(self.hostView) - } - - override func didLoad() { - super.didLoad() - - let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) - panRecognizer.delegate = self - panRecognizer.delaysTouchesBegan = false - panRecognizer.cancelsTouchesInView = true - self.panGestureRecognizer = panRecognizer - self.wrappingView.addGestureRecognizer(panRecognizer) - - self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - } - - @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.controller?.dismiss(animated: true) - } - } - - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - if let (layout, _) = self.currentLayout { - if case .regular = layout.metrics.widthClass { - return false - } else { - let location = gestureRecognizer.location(in: self.containerView) - if !self.hostView.frame.contains(location) { - return false - } - } - } - return true - } - - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { - return true - } - return false - } - - private var isDismissing = false - func animateIn() { - ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) - - let targetPosition = self.containerView.center - let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height) - - self.containerView.center = startPosition - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) - transition.animateView(allowUserInteraction: true, { - self.containerView.center = targetPosition - }, completion: { _ in - }) - } - - func animateOut(completion: @escaping () -> Void = {}) { - self.isDismissing = true - - let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) - positionTransition.updatePosition(layer: self.containerView.layer, position: CGPoint(x: self.containerView.center.x, y: self.bounds.height + self.containerView.bounds.height / 2.0), completion: { [weak self] _ in - self?.controller?.dismiss(animated: false, completion: completion) - }) - let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) - alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) - } - - func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) { - self.currentLayout = (layout, navigationHeight) - - if let controller = self.controller, let navigationBar = controller.navigationBar, navigationBar.view.superview !== self.wrappingView { - self.containerView.addSubview(navigationBar.view) - } - - self.dim.frame = CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 3.0)) - - let isLandscape = layout.orientation == .landscape - - transition.setFrame(view: self.wrappingView, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil) - - var clipFrame: CGRect - if layout.metrics.widthClass == .compact { - self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25) - if isLandscape { - self.containerView.layer.cornerRadius = 0.0 - } else { - self.containerView.layer.cornerRadius = 10.0 - } - - if #available(iOS 11.0, *) { - if layout.safeInsets.bottom.isZero { - self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - } else { - self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] - } - } - - if isLandscape { - clipFrame = CGRect(origin: CGPoint(), size: layout.size) - } else { - let coveredByModalTransition: CGFloat = 0.0 - var containerTopInset: CGFloat = 10.0 - if let statusBarHeight = layout.statusBarHeight { - containerTopInset += statusBarHeight - } - - let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: CGSize(width: layout.size.width, height: layout.size.height - containerTopInset)) - let maxScale: CGFloat = (layout.size.width - 16.0 * 2.0) / layout.size.width - let containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition - let maxScaledTopInset: CGFloat = containerTopInset - 10.0 - let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition - let containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) - - clipFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height) - } - } else { - self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4) - self.containerView.layer.cornerRadius = 10.0 - - let verticalInset: CGFloat = 44.0 - - let maxSide = max(layout.size.width, layout.size.height) - let minSide = min(layout.size.width, layout.size.height) - let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) - clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) - } - - let environment = ViewControllerComponentContainer.Environment( - statusBarHeight: 0.0, - navigationHeight: navigationHeight, - safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), - isVisible: self.currentIsVisible, - theme: self.theme ?? self.presentationData.theme, - strings: self.presentationData.strings, - dateTimeFormat: self.presentationData.dateTimeFormat, - controller: { [weak self] in - return self?.controller - } - ) - let contentSize = self.hostView.update( - transition: transition, - component: self.component, - environment: { - environment - }, - forceUpdate: true, - containerSize: CGSize(width: clipFrame.size.width, height: clipFrame.size.height) - ) - transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) - - if !isLandscape { - clipFrame.origin.y = layout.size.height - contentSize.height - transition.setFrame(view: self.containerView, frame: clipFrame) - } else { - - } - } - - private var didPlayAppearAnimation = false - func updateIsVisible(isVisible: Bool) { - if self.currentIsVisible == isVisible { - return - } - self.currentIsVisible = isVisible - - guard let currentLayout = self.currentLayout else { - return - } - self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: .immediate) - - if !self.didPlayAppearAnimation { - self.didPlayAppearAnimation = true - self.animateIn() - } - } - - @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { - switch recognizer.state { - case .began: - break - case .changed: - let translation = recognizer.translation(in: self.view).y - - var bounds = self.bounds - bounds.origin.y = -translation - bounds.origin.y = min(0.0, bounds.origin.y) - self.bounds = bounds - case .ended: - let translation = recognizer.translation(in: self.view).y - let velocity = recognizer.velocity(in: self.view) - - var bounds = self.bounds - bounds.origin.y = -translation - bounds.origin.y = min(0.0, bounds.origin.y) - - if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) { - self.controller?.dismiss(animated: true, completion: nil) - } else { - var bounds = self.bounds - let previousBounds = bounds - bounds.origin.y = 0.0 - self.bounds = bounds - self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) - } - case .cancelled: - var bounds = self.bounds - let previousBounds = bounds - bounds.origin.y = 0.0 - self.bounds = bounds - self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) - default: - break - } - } - } - - var node: Node { - return self.displayNode as! Node - } - - private let context: AccountContext - private let theme: PresentationTheme? - private let component: AnyComponent - private var isInitiallyExpanded = false - - private var currentLayout: ContainerViewLayout? - - public var pushController: (ViewController) -> Void = { _ in } - public var presentController: (ViewController) -> Void = { _ in } - - public enum Subject { - case folders - case chatsInFolder - case pins - case files - } - - public convenience init(context: AccountContext, subject: Subject) { - self.init(context: context, component: LimitScreenComponent(context: context, subject: subject, proceed: {})) - } - - private init(context: AccountContext, component: C, theme: PresentationTheme? = nil) where C.EnvironmentType == ViewControllerComponentContainer.Environment { - self.context = context - self.component = AnyComponent(component) - self.theme = nil - - super.init(navigationBarPresentationData: nil) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc private func cancelPressed() { - self.dismiss(animated: true, completion: nil) - } - - override open func loadDisplayNode() { - self.displayNode = Node(context: self.context, controller: self, component: self.component, theme: self.theme) - self.displayNodeDidLoad() - } - - public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { - self.view.endEditing(true) - if flag { - self.node.animateOut(completion: { - super.dismiss(animated: false, completion: {}) - completion?() - }) - } else { - super.dismiss(animated: false, completion: {}) - completion?() - } - } - - override open func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - self.node.updateIsVisible(isVisible: true) - } - - override open func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - self.node.updateIsVisible(isVisible: false) - } - - override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.currentLayout = layout - super.containerLayoutUpdated(layout, transition: transition) - - let navigationHeight: CGFloat = 56.0 - self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) - } -} diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index fecc4ab449..da17b54bd5 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1 +1,1153 @@ import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import ViewControllerComponent +import AccountContext +import SolidRoundedButtonComponent +import MultilineTextComponent +import PresentationDataUtils +import PrefixSectionGroupComponent +import BundleIconComponent +import SolidRoundedButtonComponent +import Markdown +import SceneKit + +private class StarComponent: Component { + static func ==(lhs: StarComponent, rhs: StarComponent) -> Bool { + return true + } + + final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { + final class Tag { + } + + func matches(tag: Any) -> Bool { + if let _ = tag as? Tag { + return true + } + return false + } + + private var _ready = Promise() + var ready: Signal { + return self._ready.get() + } + + private let sceneView: SCNView + + override init(frame: CGRect) { + self.sceneView = SCNView(frame: frame) + self.sceneView.backgroundColor = .clear + self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) + + super.init(frame: frame) + + self.addSubview(self.sceneView) + + self.setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + guard let scene = SCNScene(named: "star.scn") else { + return + } + self.sceneView.scene = scene + self.sceneView.delegate = self + + let _ = self.sceneView.snapshot() + } + + private var didSetReady = false + func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { + if !self.didSetReady { + self.didSetReady = true + + self._ready.set(.single(true)) + self.onReady() + } + } + + private func onReady() { + self.setupGradientAnimation() + self.setupShineAnimation() + + self.playAppearanceAnimation() + } + + private func setupGradientAnimation() { + guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: true) else { + return + } + guard let initial = node.geometry?.materials.first?.diffuse.contentsTransform else { + return + } + + let animation = CABasicAnimation(keyPath: "contentsTransform") + animation.duration = 4.5 + animation.fromValue = NSValue(scnMatrix4: initial) + animation.toValue = NSValue(scnMatrix4: SCNMatrix4Translate(initial, -0.35, 0.35, 0)) + animation.timingFunction = CAMediaTimingFunction(name: .linear) + animation.autoreverses = true + animation.repeatCount = .infinity + + node.geometry?.materials.first?.diffuse.addAnimation(animation, forKey: "gradient") + } + + private func setupShineAnimation() { + guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else { + return + } + guard let initial = node.geometry?.materials.first?.emission.contentsTransform else { + return + } + + let animation = CABasicAnimation(keyPath: "contentsTransform") + animation.fillMode = .forwards + animation.fromValue = NSValue(scnMatrix4: initial) + animation.toValue = NSValue(scnMatrix4: SCNMatrix4Translate(initial, -1.6, 0.0, 0.0)) + animation.timingFunction = CAMediaTimingFunction(name: .easeOut) + animation.beginTime = 0.6 + animation.duration = 0.9 + + let group = CAAnimationGroup() + group.animations = [animation] + group.beginTime = 1.0 + group.duration = 3.0 + group.repeatCount = .infinity + + node.geometry?.materials.first?.emission.addAnimation(group, forKey: "shimmer") + } + + private func playAppearanceAnimation() { + guard let scene = self.sceneView.scene, let starNode = scene.rootNode.childNode(withName: "star", recursively: false) else { + return + } + + let springAnimation = CASpringAnimation(keyPath: "rotation") + springAnimation.fromValue = NSValue(scnVector4: SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0)) + springAnimation.toValue = NSValue(scnVector4: SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: .pi * 2.0)) + springAnimation.mass = 1.0 + springAnimation.stiffness = 21.0 + springAnimation.damping = 5.8 + springAnimation.duration = 1.5 + springAnimation.initialVelocity = 1.7 + + starNode.addAnimation(springAnimation, forKey: "rotate") + } + + func update(component: StarComponent, availableSize: CGSize, transition: Transition) -> CGSize { + self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0)) + self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0) + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} + + +private final class SectionGroupComponent: Component { + public final class Item: Equatable { + public let content: AnyComponentWithIdentity + + public init(_ content: AnyComponentWithIdentity) { + self.content = content + } + + public static func ==(lhs: Item, rhs: Item) -> Bool { + if lhs.content != rhs.content { + return false + } + + return true + } + } + + public let items: [Item] + public let backgroundColor: UIColor + public let separatorColor: UIColor + + public init( + items: [Item], + backgroundColor: UIColor, + separatorColor: UIColor + ) { + self.items = items + self.backgroundColor = backgroundColor + self.separatorColor = separatorColor + } + + public static func ==(lhs: SectionGroupComponent, rhs: SectionGroupComponent) -> Bool { + if lhs.items != rhs.items { + return false + } + if lhs.backgroundColor != rhs.backgroundColor { + return false + } + if lhs.separatorColor != rhs.separatorColor { + return false + } + return true + } + + public final class View: UIView { + private let backgroundView: UIView + private var itemViews: [AnyHashable: ComponentHostView] = [:] + private var separatorViews: [UIView] = [] + + override init(frame: CGRect) { + self.backgroundView = UIView() + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.backgroundView.layer.cornerRadius = 10.0 + self.backgroundView.layer.masksToBounds = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: SectionGroupComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let sideInset: CGFloat = 16.0 + + self.backgroundView.backgroundColor = component.backgroundColor + + var size = CGSize(width: availableSize.width, height: 0.0) + + var validIds: [AnyHashable] = [] + + var i = 0 + for item in component.items { + validIds.append(item.content.id) + + let itemView: ComponentHostView + var itemTransition = transition + if let current = self.itemViews[item.content.id] { + itemView = current + } else { + itemTransition = transition.withAnimation(.none) + itemView = ComponentHostView() + self.itemViews[item.content.id] = itemView + self.addSubview(itemView) + } + let itemSize = itemView.update( + transition: itemTransition, + component: item.content.component, + environment: {}, + containerSize: CGSize(width: size.width - sideInset, height: .greatestFiniteMagnitude) + ) + + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: itemSize) + itemView.frame = CGRect(origin: CGPoint(x: itemFrame.minX + sideInset, y: itemFrame.minY + floor((itemFrame.height - itemSize.height) / 2.0)), size: itemSize) + + size.height += itemSize.height + + if i != component.items.count - 1 { + let separatorView: UIView + if self.separatorViews.count > i { + separatorView = self.separatorViews[i] + } else { + separatorView = UIView() + self.separatorViews.append(separatorView) + self.addSubview(separatorView) + } + separatorView.backgroundColor = component.separatorColor + + separatorView.frame = CGRect(origin: CGPoint(x: itemFrame.minX + sideInset * 2.0 + 30.0, y: itemFrame.maxY), size: CGSize(width: size.width - sideInset * 2.0 - 30.0, height: UIScreenPixel)) + } + i += 1 + } + + var removeIds: [AnyHashable] = [] + for (id, itemView) in self.itemViews { + if !validIds.contains(id) { + removeIds.append(id) + itemView.removeFromSuperview() + } + } + for id in removeIds { + self.itemViews.removeValue(forKey: id) + } + + if self.separatorViews.count > component.items.count - 1 { + for i in (component.items.count - 1) ..< self.separatorViews.count { + self.separatorViews[i].removeFromSuperview() + } + self.separatorViews.removeSubrange((component.items.count - 1) ..< self.separatorViews.count) + } + + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size), completion: nil) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + + +private final class ScrollChildEnvironment: Equatable { + public let insets: UIEdgeInsets + + public init(insets: UIEdgeInsets) { + self.insets = insets + } + + public static func ==(lhs: ScrollChildEnvironment, rhs: ScrollChildEnvironment) -> Bool { + if lhs.insets != rhs.insets { + return false + } + + return true + } +} + +private final class ScrollComponent: Component { + public typealias EnvironmentType = ChildEnvironment + + public let content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)> + public let contentInsets: UIEdgeInsets + public let contentOffsetUpdated: (_ top: CGFloat, _ bottom: CGFloat) -> Void + + public init( + content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>, + contentInsets: UIEdgeInsets, + contentOffsetUpdated: @escaping (_ top: CGFloat, _ bottom: CGFloat) -> Void + ) { + self.content = content + self.contentInsets = contentInsets + self.contentOffsetUpdated = contentOffsetUpdated + } + + public static func ==(lhs: ScrollComponent, rhs: ScrollComponent) -> Bool { + if lhs.content != rhs.content { + return false + } + if lhs.contentInsets != rhs.contentInsets { + return false + } + + return true + } + + public final class View: UIScrollView, UIScrollViewDelegate { + private var component: ScrollComponent? + private let contentView: ComponentHostView<(ChildEnvironment, ScrollChildEnvironment)> + + override init(frame: CGRect) { + self.contentView = ComponentHostView() + + super.init(frame: frame) + + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.contentInsetAdjustmentBehavior = .never + } + self.delegate = self + self.showsVerticalScrollIndicator = false + + self.addSubview(self.contentView) + } + + private var ignoreDidScroll = false + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + guard let component = self.component, !self.ignoreDidScroll else { + return + } + + let topOffset = scrollView.contentOffset.y + let bottomOffset = max(0.0, scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.height) + component.contentOffsetUpdated(topOffset, bottomOffset) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ScrollComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let contentSize = self.contentView.update( + transition: transition, + component: component.content, + environment: { + environment[ChildEnvironment.self] + ScrollChildEnvironment(insets: component.contentInsets) + }, + containerSize: CGSize(width: availableSize.width, height: .greatestFiniteMagnitude) + ) + transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil) + + if self.contentSize != contentSize { + self.ignoreDidScroll = true + self.contentSize = contentSize + self.ignoreDidScroll = false + } + if self.scrollIndicatorInsets != component.contentInsets { + self.scrollIndicatorInsets = component.contentInsets + } + + self.component = component + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +final class PerkComponent: CombinedComponent { + public let iconName: String + public let iconBackgroundColors: [UIColor] + public let title: String + public let titleColor: UIColor + public let subtitle: String + public let subtitleColor: UIColor + public let arrowColor: UIColor + + public init( + iconName: String, + iconBackgroundColors: [UIColor], + title: String, + titleColor: UIColor, + subtitle: String, + subtitleColor: UIColor, + arrowColor: UIColor + ) { + self.iconName = iconName + self.iconBackgroundColors = iconBackgroundColors + self.title = title + self.titleColor = titleColor + self.subtitle = subtitle + self.subtitleColor = subtitleColor + self.arrowColor = arrowColor + } + + public static func ==(lhs: PerkComponent, rhs: PerkComponent) -> Bool { + if lhs.iconName != rhs.iconName { + return false + } + if lhs.iconBackgroundColors != rhs.iconBackgroundColors { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.titleColor != rhs.titleColor { + return false + } + if lhs.subtitle != rhs.subtitle { + return false + } + if lhs.subtitleColor != rhs.subtitleColor { + return false + } + if lhs.arrowColor != rhs.arrowColor { + return false + } + return true + } + + static var body: Body { + let iconBackground = Child(RoundedRectangle.self) + let icon = Child(BundleIconComponent.self) + let title = Child(Text.self) + let subtitle = Child(MultilineTextComponent.self) + let arrow = Child(BundleIconComponent.self) + + return { context in + let component = context.component + + let sideInset: CGFloat = 16.0 + let iconTopInset: CGFloat = 15.0 + let textTopInset: CGFloat = 9.0 + let textBottomInset: CGFloat = 9.0 + let spacing: CGFloat = 3.0 + let iconSize = CGSize(width: 30.0, height: 30.0) + + let iconBackground = iconBackground.update( + component: RoundedRectangle( + colors: component.iconBackgroundColors, + cornerRadius: 7.0, + gradientDirection: .vertical), + availableSize: iconSize, transition: context.transition + ) + + let icon = icon.update( + component: BundleIconComponent( + name: component.iconName, + tintColor: .white + ), + availableSize: iconSize, + transition: context.transition + ) + + let arrow = arrow.update( + component: BundleIconComponent( + name: "Item List/DisclosureArrow", + tintColor: component.arrowColor + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let title = title.update( + component: Text( + text: component.title, + font: Font.regular(17.0), + color: component.titleColor + ), + availableSize: CGSize(width: context.availableSize.width - iconBackground.size.width - sideInset * 2.83, height: context.availableSize.height), + transition: context.transition + ) + + let subtitle = subtitle.update( + component: MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.subtitle, + font: Font.regular(13), + textColor: component.subtitleColor + ) + ), + maximumNumberOfLines: 0, + lineSpacing: 0.1 + ), + availableSize: CGSize(width: context.availableSize.width - iconBackground.size.width - sideInset * 2.83, height: context.availableSize.height), + transition: context.transition + ) + + let iconPosition = CGPoint(x: iconBackground.size.width / 2.0, y: iconTopInset + iconBackground.size.height / 2.0) + context.add(iconBackground + .position(iconPosition) + ) + + context.add(icon + .position(iconPosition) + ) + + context.add(title + .position(CGPoint(x: iconBackground.size.width + sideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0)) + ) + + context.add(subtitle + .position(CGPoint(x: iconBackground.size.width + sideInset + subtitle.size.width / 2.0, y: textTopInset + title.size.height + spacing + subtitle.size.height / 2.0)) + ) + + let size = CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + spacing + subtitle.size.height + textBottomInset) + context.add(arrow + .position(CGPoint(x: context.availableSize.width - 7.0 - arrow.size.width / 2.0, y: size.height / 2.0)) + ) + + return size + } + } +} + + +private final class PremiumIntroScreenContentComponent: CombinedComponent { + typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment) + + let context: AccountContext + + init(context: AccountContext) { + self.context = context + } + + static func ==(lhs: PremiumIntroScreenContentComponent, rhs: PremiumIntroScreenContentComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + + return true + } + + static var body: Body { + let overscroll = Child(Rectangle.self) + let fade = Child(RoundedRectangle.self) + let text = Child(MultilineTextComponent.self) + let section = Child(SectionGroupComponent.self) + let infoText = Child(MultilineTextComponent.self) + + return { context in + let sideInset: CGFloat = 16.0 + + let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + + let theme = environment.theme + let strings = environment.strings + + let availableWidth = context.availableSize.width + let sideInsets = sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right + var size = CGSize(width: context.availableSize.width, height: 0.0) + + let overscroll = overscroll.update( + component: Rectangle(color: theme.list.plainBackgroundColor), + availableSize: CGSize(width: context.availableSize.width, height: 1000), + transition: context.transition + ) + context.add(overscroll + .position(CGPoint(x: overscroll.size.width / 2.0, y: -overscroll.size.height / 2.0)) + ) + + let fade = fade.update( + component: RoundedRectangle( + colors: [ + theme.list.plainBackgroundColor, + theme.list.blocksBackgroundColor + ], + cornerRadius: 0.0, + gradientDirection: .vertical + ), + availableSize: CGSize(width: availableWidth, height: 300), + transition: context.transition + ) + context.add(fade + .position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0)) + ) + + size.height += 183.0 + + let textColor = theme.list.itemPrimaryTextColor + let titleColor = theme.list.itemPrimaryTextColor + let subtitleColor = theme.list.itemSecondaryTextColor + let arrowColor = theme.list.disclosureArrowColor + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in + return nil + }) + let text = text.update( + component: MultilineTextComponent( + text: .markdown( + text: strings.Premium_Description, + attributes: markdownAttributes + ), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets, height: 240.0), + transition: context.transition + ) + context.add(text + .position(CGPoint(x: size.width / 2.0, y: size.height + text.size.height / 2.0)) + ) + size.height += text.size.height + size.height += 21.0 + + let section = section.update( + component: SectionGroupComponent( + items: [ + SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: "limits", + component: AnyComponent( + PerkComponent( + iconName: "Premium/Perk/Limits", + iconBackgroundColors: [ + UIColor(rgb: 0xF28528), + UIColor(rgb: 0xEF7633) + ], + title: strings.Premium_DoubledLimits, + titleColor: titleColor, + subtitle: strings.Premium_DoubledLimitsInfo, + subtitleColor: subtitleColor, + arrowColor: arrowColor + ) + ) + ) + ), + SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: "upload", + component: AnyComponent( + PerkComponent( + iconName: "Premium/Perk/Upload", + iconBackgroundColors: [ + UIColor(rgb: 0xEA5F43), + UIColor(rgb: 0xE7504E) + ], + title: strings.Premium_UploadSize, + titleColor: titleColor, + subtitle: strings.Premium_UploadSizeInfo, + subtitleColor: subtitleColor, + arrowColor: arrowColor + ) + ) + ) + ), + SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: "speed", + component: AnyComponent( + PerkComponent( + iconName: "Premium/Perk/Speed", + iconBackgroundColors: [ + UIColor(rgb: 0xDE4768), + UIColor(rgb: 0xD54D82) + ], + title: strings.Premium_FasterSpeed, + titleColor: titleColor, + subtitle: strings.Premium_FasterSpeedInfo, + subtitleColor: subtitleColor, + arrowColor: arrowColor + ) + ) + ) + ), + SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: "noAds", + component: AnyComponent( + PerkComponent( + iconName: "Premium/Perk/NoAds", + iconBackgroundColors: [ + UIColor(rgb: 0xC654A8), + UIColor(rgb: 0xBE5AC2) + ], + title: strings.Premium_NoAds, + titleColor: titleColor, + subtitle: strings.Premium_NoAdsInfo, + subtitleColor: subtitleColor, + arrowColor: arrowColor + ) + ) + ) + ), + SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: "reactions", + component: AnyComponent( + PerkComponent( + iconName: "Premium/Perk/Reactions", + iconBackgroundColors: [ + UIColor(rgb: 0xAF62E9), + UIColor(rgb: 0xA668FF) + ], + title: strings.Premium_Reactions, + titleColor: titleColor, + subtitle: strings.Premium_ReactionsInfo, + subtitleColor: subtitleColor, + arrowColor: arrowColor + ) + ) + ) + ), + SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: "stickers", + component: AnyComponent( + PerkComponent( + iconName: "Premium/Perk/Stickers", + iconBackgroundColors: [ + UIColor(rgb: 0x9674FF), + UIColor(rgb: 0x8C7DFF) + ], + title: strings.Premium_Stickers, + titleColor: titleColor, + subtitle: strings.Premium_StickersInfo, + subtitleColor: subtitleColor, + arrowColor: arrowColor + ) + ) + ) + ), + SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: "badge", + component: AnyComponent( + PerkComponent( + iconName: "Premium/Perk/Badge", + iconBackgroundColors: [ + UIColor(rgb: 0x7B88FF), + UIColor(rgb: 0x7091FF) + ], + title: strings.Premium_Badge, + titleColor: titleColor, + subtitle: strings.Premium_BadgeInfo, + subtitleColor: subtitleColor, + arrowColor: arrowColor + ) + ) + ) + ), + SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: "avatar", + component: AnyComponent( + PerkComponent( + iconName: "Premium/Perk/Avatar", + iconBackgroundColors: [ + UIColor(rgb: 0x609DFF), + UIColor(rgb: 0x56A5FF) + ], + title: strings.Premium_Avatar, + titleColor: titleColor, + subtitle: strings.Premium_AvatarInfo, + subtitleColor: subtitleColor, + arrowColor: arrowColor + ) + ) + ) + ), + ], + backgroundColor: environment.theme.list.itemBlocksBackgroundColor, + separatorColor: environment.theme.list.itemBlocksSeparatorColor + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude), + transition: context.transition + ) + context.add(section + .position(CGPoint(x: availableWidth / 2.0, y: size.height + section.size.height / 2.0)) + ) + size.height += section.size.height + size.height += 17.0 + + let infoText = infoText.update( + component: MultilineTextComponent( + text: .plain( + NSAttributedString( + string: strings.Premium_HelpUs, + font: Font.regular(15.0), + textColor: environment.theme.list.freeTextColor + ) + ), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets, height: 120.0), + transition: context.transition + ) + context.add(infoText + .position(CGPoint(x: size.width / 2.0, y: size.height + infoText.size.height / 2.0)) + ) + size.height += text.size.height + size.height += 3.0 + size.height += scrollEnvironment.insets.bottom + + return size + } + } +} + +private class BlurredRectangle: Component { + let color: UIColor + + init(color: UIColor) { + self.color = color + } + + static func ==(lhs: BlurredRectangle, rhs: BlurredRectangle) -> Bool { + if !lhs.color.isEqual(rhs.color) { + return false + } + return true + } + + final class View: UIView { + private let background: NavigationBackgroundNode + + init() { + self.background = NavigationBackgroundNode(color: .clear) + + super.init(frame: CGRect()) + + self.addSubview(self.background.view) + } + + required init?(coder aDecoder: NSCoder) { + preconditionFailure() + } + + func update(component: BlurredRectangle, availableSize: CGSize, transition: Transition) -> CGSize { + transition.setFrame(view: self.background.view, frame: CGRect(origin: CGPoint(), size: availableSize)) + self.background.updateColor(color: component.color, transition: .immediate) + self.background.update(size: availableSize, cornerRadius: 0.0, transition: .immediate) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} + +private final class PremiumIntroScreenComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let action: () -> Void + + init(context: AccountContext, action: @escaping () -> Void) { + self.context = context + self.action = action + } + + static func ==(lhs: PremiumIntroScreenComponent, rhs: PremiumIntroScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + return true + } + + final class State: ComponentState { + var topContentOffset: CGFloat? + var bottomContentOffset: CGFloat? + } + + func makeState() -> State { + return State() + } + + static var body: Body { + let background = Child(Rectangle.self) + let scrollContent = Child(ScrollComponent.self) + let star = Child(StarComponent.self) + let topPanel = Child(BlurredRectangle.self) + let topSeparator = Child(Rectangle.self) + let title = Child(Text.self) + let bottomPanel = Child(BlurredRectangle.self) + let bottomSeparator = Child(Rectangle.self) + let button = Child(SolidRoundedButtonComponent.self) + + return { context in + let environment = context.environment[EnvironmentType.self].value + let state = context.state + + let background = background.update(component: Rectangle(color: environment.theme.list.blocksBackgroundColor), environment: {}, availableSize: context.availableSize, transition: context.transition) + + let star = star.update( + component: StarComponent(), + availableSize: CGSize(width: 260.0, height: 180.0), + transition: context.transition + ) + + let topPanel = topPanel.update( + component: BlurredRectangle( + color: environment.theme.rootController.navigationBar.blurredBackgroundColor + ), + availableSize: CGSize(width: context.availableSize.width, height: environment.navigationHeight), + transition: context.transition + ) + + let topSeparator = topSeparator.update( + component: Rectangle( + color: environment.theme.rootController.navigationBar.separatorColor + ), + availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel), + transition: context.transition + ) + + let title = title.update( + component: Text( + text: environment.strings.Premium_Title, + font: Font.bold(28.0), + color: environment.theme.rootController.navigationBar.primaryTextColor + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let sideInset: CGFloat = 16.0 + let button = button.update( + component: SolidRoundedButtonComponent( + title: environment.strings.Premium_SubscribeFor("$5").string, + theme: SolidRoundedButtonComponent.Theme( + backgroundColor: .black, + backgroundColors: [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ], + foregroundColor: .white + ), + height: 50.0, + cornerRadius: 10.0, + gloss: true, + action: context.component.action + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 50.0), + transition: context.transition) + + let bottomPanelPadding: CGFloat = 12.0 + let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding + let bottomPanel = bottomPanel.update( + component: BlurredRectangle( + color: environment.theme.rootController.tabBar.backgroundColor + ), + availableSize: CGSize(width: context.availableSize.width, height: bottomPanelPadding + button.size.height + bottomInset), + transition: context.transition + ) + + let bottomSeparator = bottomSeparator.update( + component: Rectangle( + color: environment.theme.rootController.tabBar.separatorColor + ), + availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel), + transition: context.transition + ) + + let scrollContent = scrollContent.update( + component: ScrollComponent( + content: AnyComponent(PremiumIntroScreenContentComponent( + context: context.component.context + )), + contentInsets: UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: bottomPanel.size.height, right: 0.0), + contentOffsetUpdated: { [weak state] topContentOffset, bottomContentOffset in + state?.topContentOffset = topContentOffset + state?.bottomContentOffset = bottomContentOffset + state?.updated(transition: .immediate) + } + ), + environment: { environment }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + context.add(scrollContent + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + let topPanelAlpha: CGFloat + let titleOffset: CGFloat + let titleScale: CGFloat + let titleOffsetDelta = 150.0 - environment.navigationHeight / 2.0 + + if let topContentOffset = state.topContentOffset { + topPanelAlpha = min(30.0, max(0.0, topContentOffset - 64.0)) / 30.0 + titleOffset = topContentOffset + titleScale = 1.0 - max(0.0, min(1.0, titleOffset / titleOffsetDelta)) * 0.36 + } else { + topPanelAlpha = 0.0 + titleOffset = 0.0 + titleScale = 1.0 + } + + context.add(star + .position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 18.0 - titleOffset * titleScale)) + .scale(titleScale) + ) + + context.add(topPanel + .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height / 2.0)) + .opacity(topPanelAlpha) + ) + context.add(topSeparator + .position(CGPoint(x: context.availableSize.width / 2.0, y: topPanel.size.height)) + .opacity(topPanelAlpha) + ) + + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: max(150.0 - titleOffset, environment.navigationHeight / 2.0))) + .scale(titleScale) + ) + + let bottomPanelAlpha: CGFloat + if let bottomContentOffset = state.bottomContentOffset { + bottomPanelAlpha = min(16.0, bottomContentOffset) / 16.0 + } else { + bottomPanelAlpha = 1.0 + } + + context.add(bottomPanel + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height / 2.0)) + .opacity(bottomPanelAlpha) + ) + context.add(bottomSeparator + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height - bottomSeparator.size.height)) + .opacity(bottomPanelAlpha) + ) + context.add(button + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height + bottomPanelPadding + button.size.height / 2.0)) + ) + + return context.availableSize + } + } +} + +public final class PremiumIntroScreen: ViewControllerComponentContainer { + private let context: AccountContext + private let action: () -> Void + + private var didSetReady = false + private let _ready = Promise() + public override var ready: Promise { + return self._ready + } + + public init(context: AccountContext, action: @escaping () -> Void) { + self.context = context + self.action = action + + super.init(context: context, component: PremiumIntroScreenComponent(context: context, action: action), navigationBarAppearance: .transparent) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.setLeftBarButton(cancelItem, animated: false) + + self.navigationPresentation = .modal + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func cancelPressed() { + self.dismiss() + } + + public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + if !self.didSetReady { + if let view = self.node.hostView.findTaggedView(tag: StarComponent.View.Tag()) as? StarComponent.View { + self.didSetReady = true + self._ready.set(view.ready) + } + } + } +} diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift new file mode 100644 index 0000000000..318907fbc2 --- /dev/null +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -0,0 +1,360 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import PresentationDataUtils +import ComponentFlow +import ViewControllerComponent +import SheetComponent +import MultilineTextComponent +import BundleIconComponent +import SolidRoundedButtonComponent +import Markdown + +private final class LimitSheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: PremiumLimitScreen.Subject + let action: () -> Void + let dismiss: () -> Void + + init(context: AccountContext, subject: PremiumLimitScreen.Subject, action: @escaping () -> Void, dismiss: @escaping () -> Void) { + self.context = context + self.subject = subject + self.action = action + self.dismiss = dismiss + } + + static func ==(lhs: LimitSheetContent, rhs: LimitSheetContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.subject != rhs.subject { + return false + } + return true + } + + final class State: ComponentState { + private let context: AccountContext + + private var disposable: Disposable? + var limits: EngineConfiguration.UserLimits + var premiumLimits: EngineConfiguration.UserLimits + + init(context: AccountContext, subject: PremiumLimitScreen.Subject) { + self.context = context + self.limits = EngineConfiguration.UserLimits.defaultValue + self.premiumLimits = EngineConfiguration.UserLimits.defaultValue + + super.init() + + self.disposable = (context.engine.data.get( + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ) |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + let (limits, premiumLimits) = result + strongSelf.limits = limits + strongSelf.premiumLimits = premiumLimits + strongSelf.updated(transition: .immediate) + } + }) + } + + deinit { + self.disposable?.dispose() + } + } + + func makeState() -> State { + return State(context: self.context, subject: self.subject) + } + + static var body: Body { + let badgeBackground = Child(RoundedRectangle.self) + let badgeIcon = Child(BundleIconComponent.self) + let badgeText = Child(MultilineTextComponent.self) + + let title = Child(MultilineTextComponent.self) + let text = Child(MultilineTextComponent.self) + + let button = Child(SolidRoundedButtonComponent.self) + + return { context in + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + let component = context.component + let theme = environment.theme + let strings = environment.strings + + let state = context.state + let subject = component.subject + + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let textSideInset: CGFloat = 24.0 + environment.safeInsets.left + + let iconName: String + let badgeString: String + let string: String + switch subject { + case .folders: + let limit = state.limits.maxFoldersCount + let premiumLimit = state.premiumLimits.maxFoldersCount + iconName = "Premium/Folder" + badgeString = "\(limit)" + string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string + case .chatsInFolder: + let limit = state.limits.maxFolderChatsCount + let premiumLimit = state.premiumLimits.maxFolderChatsCount + iconName = "Premium/Chat" + badgeString = "\(limit)" + string = strings.Premium_MaxChatsInFolderCountText("\(limit)", "\(premiumLimit)").string + case .pins: + let limit = state.limits.maxPinnedChatCount + let premiumLimit = state.premiumLimits.maxPinnedChatCount + iconName = "Premium/Pin" + badgeString = "\(limit)" + string = strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string + case .files: + let limit = 2048 * 1024 * 1024 //state.limits.maxPinnedChatCount + let premiumLimit = 4096 * 1024 * 1024 //state.premiumLimits.maxPinnedChatCount + iconName = "Premium/File" + badgeString = 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 + } + + let badgeIcon = badgeIcon.update( + component: BundleIconComponent( + name: iconName, + tintColor: .white + ), + availableSize: context.availableSize, + transition: .immediate + ) + + let badgeText = badgeText.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: badgeString, + font: Font.with(size: 24.0, design: .round, weight: .semibold, traits: []), + textColor: .white, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: context.availableSize, + transition: .immediate + ) + + let badgeBackground = badgeBackground.update( + component: RoundedRectangle( + colors: [UIColor(rgb: 0xa34fcf), UIColor(rgb: 0xc8498a), UIColor(rgb: 0xff7a23)], + cornerRadius: 23.5 + ), + availableSize: CGSize(width: badgeText.size.width + 67.0, height: 47.0), + transition: .immediate + ) + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.Premium_LimitReached, + font: Font.semibold(17.0), + textColor: theme.actionSheet.primaryTextColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + + let textFont = Font.regular(17.0) + let boldTextFont = Font.semibold(17.0) + let textColor = theme.actionSheet.primaryTextColor + let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in + return nil + }) + + let text = text.update( + component: MultilineTextComponent( + text: .markdown(text: string, attributes: markdownAttributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.0 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + + let button = button.update( + component: SolidRoundedButtonComponent( + title: strings.Premium_IncreaseLimit, + theme: SolidRoundedButtonComponent.Theme( + backgroundColor: .black, + backgroundColors: [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)], + foregroundColor: .white + ), + font: .bold, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + gloss: false, + iconName: "Premium/X2", + iconPosition: .right, + action: { [weak component] in + guard let component = component else { + return + } + component.dismiss() + component.action() + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + transition: context.transition + ) + + let width = context.availableSize.width + + let badgeFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - badgeBackground.size.width) / 2.0), y: 33.0), size: badgeBackground.size) + context.add(badgeBackground + .position(CGPoint(x: badgeFrame.midX, y: badgeFrame.midY)) + ) + + let badgeIconFrame = CGRect(origin: CGPoint(x: badgeFrame.minX + 18.0, y: badgeFrame.minY + floor((badgeFrame.height - badgeIcon.size.height) / 2.0)), size: badgeIcon.size) + context.add(badgeIcon + .position(CGPoint(x: badgeIconFrame.midX, y: badgeIconFrame.midY)) + ) + + let badgeTextFrame = CGRect(origin: CGPoint(x: badgeFrame.maxX - badgeText.size.width - 15.0, y: badgeFrame.minY + floor((badgeFrame.height - badgeText.size.height) / 2.0)), size: badgeText.size) + context.add(badgeText + .position(CGPoint(x: badgeTextFrame.midX, y: badgeTextFrame.midY)) + ) + + context.add(title + .position(CGPoint(x: width / 2.0, y: 28.0)) + ) + context.add(text + .position(CGPoint(x: 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) + context.add(button + .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) + ) + + let contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + 5.0 + environment.safeInsets.bottom) + + return contentSize + } + } +} + +private final class LimitSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: PremiumLimitScreen.Subject + let action: () -> Void + + init(context: AccountContext, subject: PremiumLimitScreen.Subject, action: @escaping () -> Void) { + self.context = context + self.subject = subject + self.action = action + } + + static func ==(lhs: LimitSheetComponent, rhs: LimitSheetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.subject != rhs.subject { + return false + } + + return true + } + + static var body: Body { + let sheet = Child(SheetComponent.self) + let animateOut = StoredActionSlot(Action.self) + + return { context in + let environment = context.environment[EnvironmentType.self] + + let controller = environment.controller + + let sheet = sheet.update( + component: SheetComponent( + content: AnyComponent(LimitSheetContent( + context: context.component.context, + subject: context.component.subject, + action: context.component.action, + dismiss: { + animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + } + )), + backgroundColor: environment.theme.actionSheet.opaqueItemBackgroundColor, + animateOut: animateOut + ), + environment: { + environment + SheetComponentEnvironment( + isDisplaying: environment.value.isVisible, + dismiss: { + animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + } + ) + }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(sheet + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + return context.availableSize + } + } +} + +public class PremiumLimitScreen: ViewControllerComponentContainer { + public enum Subject { + case folders + case chatsInFolder + case pins + case files + } + + public init(context: AccountContext, subject: PremiumLimitScreen.Subject, action: @escaping () -> Void) { + super.init(context: context, component: LimitSheetComponent(context: context, subject: subject, action: action), navigationBarAppearance: .none) + + self.navigationPresentation = .flatModal + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewDidLoad() { + super.viewDidLoad() + + self.view.disablesInteractiveModalDismiss = true + } +} diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 3d27df31d4..1b16d8d81f 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -34,7 +34,7 @@ public enum ShareControllerPreferredAction { } public enum ShareControllerExternalStatus { - case preparing + case preparing(Bool) case progress(Float) case done } @@ -638,8 +638,8 @@ public final class ShareController: ViewController { return f(peerIds, text, strongSelf.currentAccount, silently) |> map { state -> ShareState in switch state { - case .preparing: - return .preparing + case let .preparing(long): + return .preparing(long) case let .progress(value): return .progress(value) case .done: diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 61bcd76f50..976a9ab3be 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -11,7 +11,7 @@ import TelegramIntents import ContextUI enum ShareState { - case preparing + case preparing(Bool) case progress(Float) case done } @@ -672,9 +672,9 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate }) }) } - if self.fromForeignApp { - self.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: self.presentationData.theme, strings: self.presentationData.strings, forceNativeAppearance: true), fastOut: true) - } else { + + // + if !self.fromForeignApp { self.animateOut(shared: true, completion: { }) self.completed?(peerIds) @@ -686,6 +686,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate self.hapticFeedback?.success() } } + var transitioned = false let fromForeignApp = self.fromForeignApp self.shareDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] status in @@ -693,11 +694,20 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate return } + if fromForeignApp, case let .preparing(long) = status, !transitioned { + transitioned = true + if long { + strongSelf.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, forceNativeAppearance: true), fastOut: true) + } else { + strongSelf.transitionToContentNode(ShareLoadingContainerNode(theme: strongSelf.presentationData.theme, forceNativeAppearance: true), fastOut: true) + } + } + if case .done = status, !fromForeignApp { strongSelf.dismiss?(true) return } - + guard let contentNode = strongSelf.contentNode as? ShareLoadingContainer else { return } diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 9dedf94b50..5ca8f2193a 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -21,14 +21,14 @@ public enum PreparedShareItemContent { } public enum PreparedShareItem { - case preparing + case preparing(Bool) case progress(Float) case userInteractionRequired(UnpreparedShareItemContent) case done(PreparedShareItemContent) } public enum PreparedShareItems { - case preparing + case preparing(Bool) case progress(Float) case userInteractionRequired([UnpreparedShareItemContent]) case done([PreparedShareItemContent]) @@ -50,7 +50,7 @@ private func scalePhotoImage(_ image: UIImage, dimensions: CGSize) -> UIImage? { private func preparedShareItem(account: Account, to peerId: PeerId, value: [String: Any]) -> Signal { if let imageData = value["scaledImageData"] as? Data, let dimensions = value["scaledImageDimensions"] as? NSValue { let diminsionsSize = dimensions.cgSizeValue - return .single(.preparing) + return .single(.preparing(false)) |> then( standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(diminsionsSize.width), height: Int32(diminsionsSize.height))) |> mapError { _ -> Void in @@ -69,8 +69,9 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri let nativeImageSize = CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale) let dimensions = nativeImageSize.fitted(CGSize(width: 1280.0, height: 1280.0)) if let scaledImage = scalePhotoImage(image, dimensions: dimensions), let imageData = scaledImage.jpegData(compressionQuality: 0.52) { - return .single(.preparing) - |> then(standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))) + return .single(.preparing(false)) + |> then( + standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))) |> mapError { _ -> Void in return Void() } @@ -119,38 +120,41 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri } } - return loadValues(asset) - |> mapToSignal { asset -> Signal in - let preset = adjustments?.preset ?? TGMediaVideoConversionPresetCompressedMedium - let finalDimensions = TGMediaVideoConverter.dimensions(for: asset.originalSize, adjustments: adjustments, preset: preset) - - var resourceAdjustments: VideoMediaResourceAdjustments? - if let adjustments = adjustments { - if adjustments.trimApplied() { - finalDuration = adjustments.trimEndValue - adjustments.trimStartValue + return .single(.preparing(true)) + |> then( + loadValues(asset) + |> mapToSignal { asset -> Signal in + let preset = adjustments?.preset ?? TGMediaVideoConversionPresetCompressedMedium + let finalDimensions = TGMediaVideoConverter.dimensions(for: asset.originalSize, adjustments: adjustments, preset: preset) + + var resourceAdjustments: VideoMediaResourceAdjustments? + if let adjustments = adjustments { + if adjustments.trimApplied() { + finalDuration = adjustments.trimEndValue - adjustments.trimStartValue + } + + let adjustmentsData = MemoryBuffer(data: NSKeyedArchiver.archivedData(withRootObject: adjustments.dictionary()!)) + let digest = MemoryBuffer(data: adjustmentsData.md5Digest()) + resourceAdjustments = VideoMediaResourceAdjustments(data: adjustmentsData, digest: digest) } - let adjustmentsData = MemoryBuffer(data: NSKeyedArchiver.archivedData(withRootObject: adjustments.dictionary()!)) - let digest = MemoryBuffer(data: adjustmentsData.md5Digest()) - resourceAdjustments = VideoMediaResourceAdjustments(data: adjustmentsData, digest: digest) - } - - let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true) - - let resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: asset.url.path, adjustments: resourceAdjustments) - return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024) - |> mapError { _ -> Void in - return Void() - } - |> mapToSignal { event -> Signal in - switch event { - case let .progress(value): - return .single(.progress(value)) - case let .result(media): - return .single(.done(.media(media))) + let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true) + + let resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: asset.url.path, adjustments: resourceAdjustments) + return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024) + |> mapError { _ -> Void in + return Void() + } + |> mapToSignal { event -> Signal in + switch event { + case let .progress(value): + return .single(.progress(value)) + case let .result(media): + return .single(.done(.media(media))) + } } } - } + ) } else if let data = value["data"] as? Data { let fileName = value["fileName"] as? String let mimeType = (value["mimeType"] as? String) ?? "application/octet-stream" @@ -194,19 +198,38 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri return disposable } - return convertedData - |> castError(Void.self) - |> mapToSignal { data, dimensions, duration, converted in - var attributes: [TelegramMediaFileAttribute] = [] - let mimeType: String - if converted { - mimeType = "video/mp4" - attributes = [.Video(duration: Int(duration), size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)), flags: [.supportsStreaming]), .Animated, .FileName(fileName: "animation.mp4")] - } else { - mimeType = "animation/gif" - attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "animation.gif")] + return .single(.preparing(true)) + |> then( + convertedData + |> castError(Void.self) + |> mapToSignal { data, dimensions, duration, converted in + var attributes: [TelegramMediaFileAttribute] = [] + let mimeType: String + if converted { + mimeType = "video/mp4" + attributes = [.Video(duration: Int(duration), size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)), flags: [.supportsStreaming]), .Animated, .FileName(fileName: "animation.mp4")] + } else { + mimeType = "animation/gif" + attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "animation.gif")] + } + return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), mimeType: mimeType, attributes: attributes, hintFileIsLarge: data.count > 10 * 1024 * 1024) + |> mapError { _ -> Void in return Void() } + |> mapToSignal { event -> Signal in + switch event { + case let .progress(value): + return .single(.progress(value)) + case let .result(media): + return .single(.done(.media(media))) + } + } } - return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), mimeType: mimeType, attributes: attributes, hintFileIsLarge: data.count > 10 * 1024 * 1024) + ) + } else { + let scaledImage = TGScaleImageToPixelSize(image, CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale).fitted(CGSize(width: 1280.0, height: 1280.0)))! + let imageData = scaledImage.jpegData(compressionQuality: 0.54)! + return .single(.preparing(false)) + |> then( + standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(scaledImage.size.width), height: Int32(scaledImage.size.height))) |> mapError { _ -> Void in return Void() } |> mapToSignal { event -> Signal in switch event { @@ -216,11 +239,18 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri return .single(.done(.media(media))) } } - } - } else { - let scaledImage = TGScaleImageToPixelSize(image, CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale).fitted(CGSize(width: 1280.0, height: 1280.0)))! - let imageData = scaledImage.jpegData(compressionQuality: 0.54)! - return standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(scaledImage.size.width), height: Int32(scaledImage.size.height))) + ) + } + } else { + var thumbnailData: Data? + if mimeType == "application/pdf", let image = generatePdfPreviewImage(data: data, size: CGSize(width: 256.0, height: 256.0)), let jpegData = image.jpegData(compressionQuality: 0.5) { + thumbnailData = jpegData + } + + let long = data.count > Int32(1.5 * 1024 * 1024) + return .single(.preparing(long)) + |> then( + standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), thumbnailData: thumbnailData, mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 10 * 1024 * 1024) |> mapError { _ -> Void in return Void() } |> mapToSignal { event -> Signal in switch event { @@ -230,23 +260,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri return .single(.done(.media(media))) } } - } - } else { - var thumbnailData: Data? - if mimeType == "application/pdf", let image = generatePdfPreviewImage(data: data, size: CGSize(width: 256.0, height: 256.0)), let jpegData = image.jpegData(compressionQuality: 0.5) { - thumbnailData = jpegData - } - - return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), thumbnailData: thumbnailData, mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 10 * 1024 * 1024) - |> mapError { _ -> Void in return Void() } - |> mapToSignal { event -> Signal in - switch event { - case let .progress(value): - return .single(.progress(value)) - case let .result(media): - return .single(.done(.media(media))) - } - } + ) } } else if let url = value["audio"] as? URL { if let audioData = try? Data(contentsOf: url, options: [.mappedIfSafe]) { @@ -262,25 +276,32 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri waveform = MemoryBuffer(data: waveformData) } - return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(audioData), mimeType: mimeType, attributes: [.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: artist, waveform: waveform?.makeData()), .FileName(fileName: fileName)], hintFileIsLarge: audioData.count > 10 * 1024 * 1024) - |> mapError { _ -> Void in return Void() } - |> mapToSignal { event -> Signal in - switch event { - case let .progress(value): - return .single(.progress(value)) - case let .result(media): - return .single(.done(.media(media))) + let long = audioData.count > Int32(1.5 * 1024 * 1024) + return .single(.preparing(long)) + |> then( + standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(audioData), mimeType: mimeType, attributes: [.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: artist, waveform: waveform?.makeData()), .FileName(fileName: fileName)], hintFileIsLarge: audioData.count > 10 * 1024 * 1024) + |> mapError { _ -> Void in return Void() } + |> mapToSignal { event -> Signal in + switch event { + case let .progress(value): + return .single(.progress(value)) + case let .result(media): + return .single(.done(.media(media))) + } } - } + ) } else { return .never() } } else if let text = value["text"] as? String { - return .single(.done(.text(text))) + return .single(.preparing(false)) + |> then( + .single(.done(.text(text))) + ) } else if let url = value["url"] as? URL { if TGShareLocationSignals.isLocationURL(url) { return Signal { subscriber in - subscriber.putNext(.preparing) + subscriber.putNext(.preparing(false)) let disposable = TGShareLocationSignals.locationMessageContent(for: url).start(next: { value in if let value = value as? TGShareLocationResult { if let title = value.title { @@ -299,7 +320,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri } } } else { - return .single(.done(.text(url.absoluteString))) + return .single(.preparing(false)) + |> then( + .single(.done(.text(url.absoluteString))) + ) } } else if let vcard = value["contact"] as? Data, let contactData = DeviceContactExtendedData(vcard: vcard) { return .single(.userInteractionRequired(.contact(contactData))) @@ -347,8 +371,8 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [ var progresses: [Float] = [] for item in items { switch item { - case .preparing: - return .preparing + case let .preparing(long): + return .preparing(long) case let .progress(value): progresses.append(value) case let .userInteractionRequired(value): diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 178133c6f4..fa164d897a 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -248,8 +248,8 @@ public final class SolidRoundedButtonNode: ASDisplayNode { compositingFilter = nil } - shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, duration: 2.4, horizontal: true) - borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, duration: 2.4, horizontal: true) + shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, duration: 3.0, horizontal: true) + borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, duration: 3.0, horizontal: true) shimmerView.layer.compositingFilter = compositingFilter borderShimmerView.layer.compositingFilter = compositingFilter @@ -259,15 +259,42 @@ public final class SolidRoundedButtonNode: ASDisplayNode { guard theme !== self.theme else { return } + let previousTheme = self.theme self.theme = theme - if animated { + let animateTransition = previousTheme.backgroundColors.count != theme.backgroundColors.count + + if animated && animateTransition { if let snapshotView = self.buttonBackgroundNode.view.snapshotView(afterScreenUpdates: false) { self.view.insertSubview(snapshotView, aboveSubview: self.buttonBackgroundNode.view) snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() }) } + + if let snapshotView = self.titleNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.titleNode.frame + + self.view.insertSubview(snapshotView, aboveSubview: self.titleNode.view) + snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 15.0), duration: 0.2, removeOnCompletion: false, additive: true) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + self.titleNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -15.0), to: CGPoint(), duration: 0.2, additive: true) + self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + if let snapshotView = self.iconNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.iconNode.frame + + self.view.insertSubview(snapshotView, aboveSubview: self.iconNode.view) + snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 15.0), duration: 0.2, removeOnCompletion: false, additive: true) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + self.iconNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -15.0), to: CGPoint(), duration: 0.2, additive: true) + self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } } if theme.backgroundColors.count > 1 { self.buttonBackgroundNode.backgroundColor = nil @@ -317,8 +344,8 @@ public final class SolidRoundedButtonNode: ASDisplayNode { transition.updateFrame(view: borderMaskView, frame: buttonFrame) transition.updateFrame(view: borderShimmerView, frame: buttonFrame) - shimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: width * 3.0, y: 0.0), size: buttonSize), within: CGSize(width: width * 7.0, height: buttonHeight)) - borderShimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: width * 3.0, y: 0.0), size: buttonSize), within: CGSize(width: width * 7.0, height: buttonHeight)) + shimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: width * 4.0, y: 0.0), size: buttonSize), within: CGSize(width: width * 9.0, height: buttonHeight)) + borderShimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: width * 4.0, y: 0.0), size: buttonSize), within: CGSize(width: width * 9.0, height: buttonHeight)) } transition.updateFrame(node: self.buttonNode, frame: buttonFrame) @@ -435,7 +462,12 @@ public final class SolidRoundedButtonView: UIView { private let buttonBackgroundNode: UIImageView private var buttonBackgroundAnimationView: UIImageView? - private let buttonGlossView: SolidRoundedButtonGlossView? + + private var shimmerView: ShimmerEffectForegroundView? + private var borderView: UIView? + private var borderMaskView: UIView? + private var borderShimmerView: ShimmerEffectForegroundView? + private let buttonNode: HighlightTrackingButton private let titleNode: ImmediateTextView private let subtitleNode: ImmediateTextView @@ -515,13 +547,7 @@ public final class SolidRoundedButtonView: UIView { } else { self.buttonBackgroundNode.backgroundColor = theme.backgroundColor } - - if gloss { - self.buttonGlossView = SolidRoundedButtonGlossView(color: theme.foregroundColor, cornerRadius: cornerRadius) - } else { - self.buttonGlossView = nil - } - + self.buttonNode = HighlightTrackingButton() self.titleNode = ImmediateTextView() @@ -536,9 +562,6 @@ public final class SolidRoundedButtonView: UIView { super.init(frame: CGRect()) self.addSubview(self.buttonBackgroundNode) - if let buttonGlossView = self.buttonGlossView { - self.addSubview(buttonGlossView) - } self.addSubview(self.buttonNode) self.addSubview(self.titleNode) self.addSubview(self.subtitleNode) @@ -575,6 +598,36 @@ public final class SolidRoundedButtonView: UIView { if #available(iOS 13.0, *) { self.buttonBackgroundNode.layer.cornerCurve = .continuous } + + if gloss { + let shimmerView = ShimmerEffectForegroundView() + self.shimmerView = shimmerView + + if #available(iOS 13.0, *) { + shimmerView.layer.cornerCurve = .continuous + shimmerView.layer.cornerRadius = self.buttonCornerRadius + } + + let borderView = UIView() + borderView.isUserInteractionEnabled = false + self.borderView = borderView + + let borderMaskView = UIView() + borderMaskView.layer.borderWidth = 1.0 + UIScreenPixel + borderMaskView.layer.borderColor = UIColor.white.cgColor + borderMaskView.layer.cornerRadius = self.buttonCornerRadius + borderView.mask = borderMaskView + self.borderMaskView = borderMaskView + + let borderShimmerView = ShimmerEffectForegroundView() + self.borderShimmerView = borderShimmerView + borderView.addSubview(borderShimmerView) + + self.insertSubview(shimmerView, belowSubview: self.buttonNode) + self.insertSubview(borderView, belowSubview: self.buttonNode) + + self.updateShimmerParameters() + } } required public init(coder: NSCoder) { @@ -601,7 +654,7 @@ public final class SolidRoundedButtonView: UIView { CATransaction.begin() let animation = CABasicAnimation(keyPath: "position.x") - animation.duration = Double.random(in: 1.8 ..< 2.3) + animation.duration = 4.5 animation.fromValue = previousValue animation.toValue = newValue animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) @@ -660,6 +713,33 @@ public final class SolidRoundedButtonView: UIView { self.subtitleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2) } + func updateShimmerParameters() { + guard let shimmerView = self.shimmerView, let borderShimmerView = self.borderShimmerView else { + return + } + + let color = self.theme.foregroundColor + let alpha: CGFloat + let borderAlpha: CGFloat + let compositingFilter: String? + if color.lightness > 0.5 { + alpha = 0.5 + borderAlpha = 0.75 + compositingFilter = "overlayBlendMode" + } else { + alpha = 0.2 + borderAlpha = 0.3 + compositingFilter = nil + } + + shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, duration: 3.0, horizontal: true) + borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, duration: 3.0, horizontal: true) + + shimmerView.layer.compositingFilter = compositingFilter + borderShimmerView.layer.compositingFilter = compositingFilter + } + + public func updateTheme(_ theme: SolidRoundedButtonTheme) { guard theme !== self.theme else { return @@ -680,7 +760,6 @@ public final class SolidRoundedButtonView: UIView { self.buttonBackgroundNode.image = nil } - self.buttonGlossView?.color = theme.foregroundColor self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor) @@ -689,6 +768,8 @@ public final class SolidRoundedButtonView: UIView { if let width = self.validLayout { _ = self.updateLayout(width: width, transition: .immediate) } + + self.updateShimmerParameters() } public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { @@ -702,14 +783,21 @@ public final class SolidRoundedButtonView: UIView { let buttonFrame = CGRect(origin: CGPoint(), size: buttonSize) transition.updateFrame(view: self.buttonBackgroundNode, frame: buttonFrame) + if let shimmerView = self.shimmerView, let borderView = self.borderView, let borderMaskView = self.borderMaskView, let borderShimmerView = self.borderShimmerView { + transition.updateFrame(view: shimmerView, frame: buttonFrame) + transition.updateFrame(view: borderView, frame: buttonFrame) + transition.updateFrame(view: borderMaskView, frame: buttonFrame) + transition.updateFrame(view: borderShimmerView, frame: buttonFrame) + + shimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: width * 4.0, y: 0.0), size: buttonSize), within: CGSize(width: width * 9.0, height: buttonHeight)) + borderShimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: width * 4.0, y: 0.0), size: buttonSize), within: CGSize(width: width * 9.0, height: buttonHeight)) + } + if let buttonBackgroundAnimationView = self.buttonBackgroundAnimationView { transition.updateFrame(view: buttonBackgroundAnimationView, frame: CGRect(origin: CGPoint(), size: CGSize(width: buttonSize.width * 2.4, height: buttonSize.height))) self.setupGradientAnimations() } - - if let buttonGlossView = self.buttonGlossView { - transition.updateFrame(view: buttonGlossView, frame: buttonFrame) - } + transition.updateFrame(view: self.buttonNode, frame: buttonFrame) if self.title != self.titleNode.attributedText?.string { @@ -770,112 +858,3 @@ public final class SolidRoundedButtonView: UIView { self.pressed?() } } - -private final class SolidRoundedButtonGlossViewParameters: NSObject { - let gradientColors: NSArray? - let cornerRadius: CGFloat - let progress: CGFloat - - init(gradientColors: NSArray?, cornerRadius: CGFloat, progress: CGFloat) { - self.gradientColors = gradientColors - self.cornerRadius = cornerRadius - self.progress = progress - } -} - -public final class SolidRoundedButtonGlossView: UIView { - public var color: UIColor { - didSet { - self.updateGradientColors() - self.setNeedsDisplay() - } - } - private var progress: CGFloat = 0.0 - private var animator: ConstantDisplayLinkAnimator? - private let buttonCornerRadius: CGFloat - private var gradientColors: NSArray? - - private let trackingLayer: HierarchyTrackingLayer - - public init(color: UIColor, cornerRadius: CGFloat) { - self.color = color - self.buttonCornerRadius = cornerRadius - - self.trackingLayer = HierarchyTrackingLayer() - - super.init(frame: CGRect()) - - self.layer.addSublayer(self.trackingLayer) - - self.isOpaque = false - - var previousTime: CFAbsoluteTime? - self.animator = ConstantDisplayLinkAnimator(update: { [weak self] in - guard let strongSelf = self else { - return - } - let currentTime = CFAbsoluteTimeGetCurrent() - if let previousTime = previousTime { - var delta: CGFloat - if strongSelf.progress < 0.05 || strongSelf.progress > 0.95 { - delta = 0.001 - } else { - delta = 0.009 - } - delta *= CGFloat(currentTime - previousTime) * 60.0 - var newProgress = strongSelf.progress + delta - if newProgress > 1.0 { - newProgress = 0.0 - } - strongSelf.progress = newProgress - strongSelf.setNeedsDisplay() - } - previousTime = currentTime - }) - - self.updateGradientColors() - - self.trackingLayer.didEnterHierarchy = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.animator?.isPaused = false - } - - self.trackingLayer.didExitHierarchy = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.animator?.isPaused = true - } - } - - required public init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func updateGradientColors() { - let transparentColor = self.color.withAlphaComponent(0.0).cgColor - self.gradientColors = [transparentColor, transparentColor, self.color.withAlphaComponent(0.12).cgColor, transparentColor, transparentColor] - } - - override public func draw(_ rect: CGRect) { - let parameters = SolidRoundedButtonGlossViewParameters(gradientColors: self.gradientColors, cornerRadius: self.buttonCornerRadius, progress: self.progress) - guard let gradientColors = parameters.gradientColors else { - return - } - - let context = UIGraphicsGetCurrentContext()! - - let path = UIBezierPath(roundedRect: bounds, cornerRadius: parameters.cornerRadius) - context.addPath(path.cgPath) - context.clip() - - var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0] - let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! - - let x = -4.0 * bounds.size.width + 8.0 * bounds.size.width * parameters.progress - context.drawLinearGradient(gradient, start: CGPoint(x: x, y: 0.0), end: CGPoint(x: x + bounds.size.width, y: 0.0), options: CGGradientDrawingOptions()) - } -} diff --git a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift index 7a2b336fb3..f58dadb194 100644 --- a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift +++ b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift @@ -9,7 +9,8 @@ public struct UserLimitsConfiguration: Equatable { public let maxFavedStickerCount: Int32 public let maxFoldersCount: Int32 public let maxFolderChatsCount: Int32 - public let maxTextLengthCount: Int32 + public let maxCaptionLengthCount: Int32 + public let maxUploadFileParts: Int32 public static var defaultValue: UserLimitsConfiguration { return UserLimitsConfiguration( @@ -20,7 +21,8 @@ public struct UserLimitsConfiguration: Equatable { maxFavedStickerCount: 5, maxFoldersCount: 10, maxFolderChatsCount: 100, - maxTextLengthCount: 4096 + maxCaptionLengthCount: 1024, + maxUploadFileParts: 4000 ) } @@ -32,7 +34,8 @@ public struct UserLimitsConfiguration: Equatable { maxFavedStickerCount: Int32, maxFoldersCount: Int32, maxFolderChatsCount: Int32, - maxTextLengthCount: Int32 + maxCaptionLengthCount: Int32, + maxUploadFileParts: Int32 ) { self.maxPinnedChatCount = maxPinnedChatCount self.maxChannelsCount = maxChannelsCount @@ -41,7 +44,8 @@ public struct UserLimitsConfiguration: Equatable { self.maxFavedStickerCount = maxFavedStickerCount self.maxFoldersCount = maxFoldersCount self.maxFolderChatsCount = maxFolderChatsCount - self.maxTextLengthCount = maxTextLengthCount + self.maxCaptionLengthCount = maxCaptionLengthCount + self.maxUploadFileParts = maxUploadFileParts } } @@ -65,16 +69,7 @@ extension UserLimitsConfiguration { self.maxFavedStickerCount = getValue("stickers_faved_limit", orElse: defaultValue.maxPinnedChatCount) self.maxFoldersCount = getValue("dialog_filters_limit", orElse: defaultValue.maxPinnedChatCount) self.maxFolderChatsCount = getValue("dialog_filters_chats_limit", orElse: defaultValue.maxPinnedChatCount) - self.maxTextLengthCount = getValue("message_text_length_limit", orElse: defaultValue.maxPinnedChatCount) - } -} - -public func getUserLimits(postbox: Postbox) -> Signal { - return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) - |> mapToSignal { preferencesView -> Signal in - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue - let configuration = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false) - print(configuration) - return .never() + self.maxCaptionLengthCount = getValue("caption_length_limit", orElse: defaultValue.maxCaptionLengthCount) + self.maxUploadFileParts = getValue("upload_max_fileparts", orElse: defaultValue.maxUploadFileParts) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/Configuration.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/Configuration.swift index 04d201154e..4efd91fe1d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/Configuration.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/Configuration.swift @@ -55,7 +55,8 @@ public enum EngineConfiguration { public let maxFavedStickerCount: Int32 public let maxFoldersCount: Int32 public let maxFolderChatsCount: Int32 - public let maxTextLengthCount: Int32 + public let maxCaptionLengthCount: Int32 + public let maxUploadFileParts: Int32 public static var defaultValue: UserLimits { return UserLimits(UserLimitsConfiguration.defaultValue) @@ -69,7 +70,8 @@ public enum EngineConfiguration { maxFavedStickerCount: Int32, maxFoldersCount: Int32, maxFolderChatsCount: Int32, - maxTextLengthCount: Int32 + maxCaptionLengthCount: Int32, + maxUploadFileParts: Int32 ) { self.maxPinnedChatCount = maxPinnedChatCount self.maxChannelsCount = maxChannelsCount @@ -78,7 +80,8 @@ public enum EngineConfiguration { self.maxFavedStickerCount = maxFavedStickerCount self.maxFoldersCount = maxFoldersCount self.maxFolderChatsCount = maxFolderChatsCount - self.maxTextLengthCount = maxTextLengthCount + self.maxCaptionLengthCount = maxCaptionLengthCount + self.maxUploadFileParts = maxUploadFileParts } } } @@ -112,7 +115,8 @@ extension EngineConfiguration.UserLimits { maxFavedStickerCount: userLimitsConfiguration.maxFavedStickerCount, maxFoldersCount: userLimitsConfiguration.maxFoldersCount, maxFolderChatsCount: userLimitsConfiguration.maxFolderChatsCount, - maxTextLengthCount: userLimitsConfiguration.maxTextLengthCount + maxCaptionLengthCount: userLimitsConfiguration.maxCaptionLengthCount, + maxUploadFileParts: userLimitsConfiguration.maxUploadFileParts ) } } diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Avatar.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Avatar.imageset/Contents.json new file mode 100644 index 0000000000..3b3fd8aee4 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Avatar.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Profile.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Avatar.imageset/Profile.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Avatar.imageset/Profile.pdf new file mode 100644 index 0000000000..8d6de21a15 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Avatar.imageset/Profile.pdf @@ -0,0 +1,79 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.000000 5.000000 cm +1.000000 1.000000 1.000000 scn +10.000000 0.000000 m +15.522847 0.000000 20.000000 4.477153 20.000000 10.000000 c +20.000000 15.522848 15.522847 20.000000 10.000000 20.000000 c +4.477152 20.000000 0.000000 15.522848 0.000000 10.000000 c +0.000000 4.477153 4.477152 0.000000 10.000000 0.000000 c +h +14.751925 9.167950 m +8.554701 5.036467 l +7.890146 4.593431 7.000000 5.069821 7.000000 5.868517 c +7.000000 14.131483 l +7.000000 14.930179 7.890145 15.406569 8.554700 14.963533 c +14.751925 10.832050 l +15.345657 10.436228 15.345657 9.563771 14.751925 9.167950 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 648 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000738 00000 n +0000000760 00000 n +0000000933 00000 n +0000001007 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1066 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Badge.imageset/Badge.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Badge.imageset/Badge.pdf new file mode 100644 index 0000000000..fe2136f94c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Badge.imageset/Badge.pdf @@ -0,0 +1,97 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.575439 5.396620 cm +1.000000 1.000000 1.000000 scn +9.882595 3.317793 m +5.183871 0.439333 l +4.695292 0.140028 4.056584 0.293465 3.757279 0.782043 c +3.611097 1.020666 3.567514 1.308224 3.636422 1.579445 c +4.363782 4.442359 l +4.626348 5.475822 5.333547 6.339716 6.294793 6.801228 c +11.420868 9.262346 l +11.659848 9.377085 11.760565 9.663830 11.645826 9.902809 c +11.552907 10.096345 11.342772 10.204644 11.131233 10.168021 c +5.425255 9.180172 l +4.265362 8.979365 3.075904 9.299660 2.173681 10.055747 c +0.371115 11.566348 l +-0.068036 11.934370 -0.125698 12.588713 0.242323 13.027864 c +0.421316 13.241451 0.678710 13.374050 0.956533 13.395792 c +6.463908 13.826798 l +6.852990 13.857248 7.192065 14.103476 7.341429 14.464033 c +9.466071 19.592789 l +9.685358 20.122133 10.292245 20.373486 10.821590 20.154200 c +11.075765 20.048904 11.277706 19.846962 11.383000 19.592789 c +13.507643 14.464033 l +13.657007 14.103476 13.996081 13.857248 14.385163 13.826798 c +19.922800 13.393423 l +20.494022 13.348721 20.920851 12.849413 20.876146 12.278191 c +20.854641 12.003400 20.724669 11.748462 20.514915 11.569643 c +16.291594 7.969165 l +15.994287 7.715703 15.864580 7.316709 15.956014 6.936873 c +17.254391 1.543129 l +17.388485 0.986073 17.045605 0.425785 16.488548 0.291691 c +16.220879 0.227257 15.938575 0.271862 15.703809 0.415680 c +10.966477 3.317793 l +10.633905 3.521528 10.215167 3.521528 9.882595 3.317793 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1468 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001558 00000 n +0000001581 00000 n +0000001754 00000 n +0000001828 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1887 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Badge.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Badge.imageset/Contents.json new file mode 100644 index 0000000000..8cb366c891 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Badge.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Badge.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Limits.imageset/2x.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Limits.imageset/2x.pdf new file mode 100644 index 0000000000..3838d738e7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Limits.imageset/2x.pdf @@ -0,0 +1,107 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.500000 8.728230 cm +1.000000 1.000000 1.000000 scn +0.426623 12.091039 m +0.879073 12.407753 1.502603 12.297718 1.819318 11.845269 c +4.500086 8.015600 l +7.180854 11.845269 l +7.497569 12.297718 8.121099 12.407753 8.573548 12.091039 c +9.025997 11.774324 9.136032 11.150794 8.819318 10.698344 c +5.720742 6.271807 l +8.819318 1.845269 l +9.136032 1.392819 9.025997 0.769289 8.573548 0.452575 c +8.121099 0.135860 7.497569 0.245895 7.180854 0.698344 c +4.500086 4.528013 l +1.819318 0.698344 l +1.502603 0.245895 0.879073 0.135860 0.426623 0.452575 c +-0.025826 0.769289 -0.135861 1.392819 0.180854 1.845269 c +3.279430 6.271807 l +0.180854 10.698344 l +-0.135861 11.150794 -0.025826 11.774324 0.426623 12.091039 c +h +15.000086 10.271807 m +13.895516 10.271807 13.000086 9.376376 13.000086 8.271807 c +13.000086 7.771807 l +13.000086 7.219522 12.552371 6.771807 12.000086 6.771807 c +11.447801 6.771807 11.000086 7.219522 11.000086 7.771807 c +11.000086 8.271807 l +11.000086 10.480946 12.790947 12.271807 15.000086 12.271807 c +17.209225 12.271807 19.000086 10.480946 19.000086 8.271807 c +19.000086 8.125909 l +19.000086 6.610820 18.144077 5.225768 16.788940 4.548200 c +13.552872 2.930165 l +13.286202 2.796831 13.096846 2.553910 13.028315 2.271807 c +18.000086 2.271807 l +18.552370 2.271807 19.000086 1.824092 19.000086 1.271807 c +19.000086 0.719522 18.552370 0.271807 18.000086 0.271807 c +12.000086 0.271807 l +11.447801 0.271807 11.000086 0.719522 11.000086 1.271807 c +11.000086 2.035739 l +11.000086 3.172054 11.642092 4.210844 12.658444 4.719020 c +15.894513 6.337054 l +16.572081 6.675838 17.000086 7.368365 17.000086 8.125909 c +17.000086 8.271807 l +17.000086 9.376376 16.104656 10.271807 15.000086 10.271807 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1763 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001853 00000 n +0000001876 00000 n +0000002049 00000 n +0000002123 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2182 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/ContextX2.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Limits.imageset/Contents.json similarity index 80% rename from submodules/TelegramUI/Images.xcassets/Premium/ContextX2.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Premium/Perk/Limits.imageset/Contents.json index 6fe332016d..372cafec39 100644 --- a/submodules/TelegramUI/Images.xcassets/Premium/ContextX2.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Limits.imageset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "2x.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoAds.imageset/Ads.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoAds.imageset/Ads.pdf new file mode 100644 index 0000000000..db109152b7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoAds.imageset/Ads.pdf @@ -0,0 +1,127 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 7.000000 4.804735 cm +1.000000 1.000000 1.000000 scn +0.707107 18.902372 m +0.316583 19.292896 -0.316583 19.292896 -0.707107 18.902372 c +-1.097631 18.511847 -1.097631 17.878683 -0.707107 17.488157 c +0.707107 18.902372 l +h +15.292893 1.488157 m +15.683417 1.097633 16.316582 1.097633 16.707108 1.488157 c +17.097631 1.878683 17.097631 2.511847 16.707108 2.902371 c +15.292893 1.488157 l +h +-0.707107 17.488157 m +15.292893 1.488157 l +16.707108 2.902371 l +0.707107 18.902372 l +-0.707107 17.488157 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 6.000000 5.493464 cm +1.000000 1.000000 1.000000 scn +6.634244 6.005881 m +4.000000 6.005881 l +1.790861 6.005881 0.000000 7.796742 0.000000 10.005881 c +0.000000 11.259813 0.576984 12.378983 1.479941 13.112381 c +10.638983 3.953340 l +9.078063 5.167388 l +8.815662 5.371477 8.684463 5.473521 8.546436 5.558720 c +8.166211 5.793416 7.738360 5.940215 7.294138 5.988393 c +7.132880 6.005881 6.966668 6.005881 6.634244 6.005881 c +h +16.036222 1.384529 m +3.452085 13.968666 l +3.631217 13.993204 3.814128 14.005881 4.000000 14.005881 c +6.634244 14.005881 l +6.966668 14.005881 7.132880 14.005881 7.294138 14.023371 c +7.738360 14.071548 8.166211 14.218346 8.546436 14.453043 c +8.684463 14.538241 8.815662 14.640285 9.078062 14.844374 c +11.835389 16.988962 l +11.835398 16.988970 l +11.835412 16.988979 l +13.492673 18.277960 14.321305 18.922451 15.017017 18.916531 c +15.622235 18.911381 16.192566 18.632441 16.568199 18.157871 c +17.000000 17.612343 17.000000 16.562572 17.000000 14.463034 c +17.000000 5.548728 l +17.000000 3.449189 17.000000 2.399420 16.568199 1.853891 c +16.418444 1.664694 16.237743 1.506588 16.036222 1.384529 c +h +4.000000 3.119659 m +4.000000 3.639083 4.000000 3.898794 4.094136 4.100118 c +4.193412 4.312436 4.364113 4.483137 4.576432 4.582415 c +4.777756 4.676550 5.037467 4.676550 5.556890 4.676550 c +6.443110 4.676550 l +6.962533 4.676550 7.222244 4.676550 7.423568 4.582415 c +7.635887 4.483137 7.806588 4.312436 7.905864 4.100118 c +8.000000 3.898794 8.000000 3.639083 8.000000 3.119659 c +8.000000 2.005878 l +8.000000 0.901310 7.104569 0.005878 6.000000 0.005878 c +4.895431 0.005878 4.000000 0.901310 4.000000 2.005878 c +4.000000 3.119659 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2250 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002340 00000 n +0000002363 00000 n +0000002536 00000 n +0000002610 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2669 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoAds.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoAds.imageset/Contents.json new file mode 100644 index 0000000000..535c776e40 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/NoAds.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Ads.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Reactions.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Reactions.imageset/Contents.json new file mode 100644 index 0000000000..89bcf20292 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Reactions.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Reactions.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Reactions.imageset/Reactions.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Reactions.imageset/Reactions.pdf new file mode 100644 index 0000000000..5575250238 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Reactions.imageset/Reactions.pdf @@ -0,0 +1,75 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 6.000000 6.327723 cm +1.000000 1.000000 1.000000 scn +9.000000 0.000000 m +9.231684 0.000000 9.561386 0.169308 9.828713 0.338614 c +14.809901 3.546535 18.000000 7.306931 18.000000 11.120792 c +18.000000 14.391089 15.745544 16.672277 12.902970 16.672277 c +11.129704 16.672277 9.801980 15.692080 9.000000 14.221783 c +8.215841 15.683168 6.879208 16.672277 5.105941 16.672277 c +2.263366 16.672277 0.000000 14.391089 0.000000 11.120792 c +0.000000 7.306931 3.190099 3.546535 8.171288 0.338614 c +8.447525 0.169308 8.777228 0.000000 9.000000 0.000000 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 615 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000705 00000 n +0000000727 00000 n +0000000900 00000 n +0000000974 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1033 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Speed.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Speed.imageset/Contents.json new file mode 100644 index 0000000000..44f9f25baa --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Speed.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Speed.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Speed.imageset/Speed.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Speed.imageset/Speed.pdf new file mode 100644 index 0000000000..f297be3520 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Speed.imageset/Speed.pdf @@ -0,0 +1,112 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 12.294678 9.000013 cm +1.000000 1.000000 1.000000 scn +2.804684 0.000000 m +4.353669 0.000000 5.609368 1.274049 5.609368 2.845666 c +5.609368 4.417284 4.353669 5.691332 2.804684 5.691332 c +1.255700 5.691332 0.000000 4.417284 0.000000 2.845666 c +0.000000 1.274049 1.255700 0.000000 2.804684 0.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 17.217842 12.915028 cm +1.000000 1.000000 1.000000 scn +0.202088 2.121548 m +0.590437 1.919203 0.904981 1.668863 1.145721 1.370527 c +1.385267 1.073672 1.586731 0.733007 1.750114 0.348534 c +1.831343 0.157340 2.052196 0.068219 2.243381 0.149469 c +2.291679 0.169995 2.335148 0.200401 2.369815 0.239834 c +9.410900 7.784840 l +9.694361 8.088588 9.677914 8.564614 9.374166 8.848076 c +9.108430 9.096064 8.703391 9.118585 8.411800 8.901587 c +0.153150 2.755597 l +-0.014851 2.633397 -0.049773 2.397816 0.073990 2.230975 c +0.107876 2.185294 0.151681 2.147893 0.202088 2.121548 c +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 4.000000 9.669685 cm +1.000000 1.000000 1.000000 scn +22.003891 1.104424 m +22.003891 0.423388 21.718653 -0.000001 20.321684 0.037580 c +18.924715 0.075160 18.613684 0.350485 18.613684 1.031520 c +18.613684 2.190733 18.463017 3.313196 18.169523 4.353136 c +20.529579 7.033363 l +21.472769 5.266989 22.003891 3.231236 22.003891 1.104424 c +h +11.001945 12.973248 m +13.460424 12.973248 15.679217 12.126817 17.469568 10.716667 c +15.011708 9.067489 l +13.884562 9.852736 12.507846 10.307699 10.902919 10.307699 c +5.969246 10.307699 2.068742 6.093097 2.068742 1.116306 c +2.068742 0.101034 1.795549 0.039256 1.200545 0.035501 c +0.968838 0.035501 l +0.363240 0.039256 0.000000 0.101034 0.000000 1.116306 c +0.000000 7.455166 4.718006 12.973248 11.001945 12.973248 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 1778 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001868 00000 n +0000001891 00000 n +0000002064 00000 n +0000002138 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2197 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Stickers.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Stickers.imageset/Contents.json new file mode 100644 index 0000000000..8537b89c0b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Stickers.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Stickers.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Stickers.imageset/Stickers.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Stickers.imageset/Stickers.pdf new file mode 100644 index 0000000000..f22c436ff8 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Stickers.imageset/Stickers.pdf @@ -0,0 +1,149 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 6.000000 6.000000 cm +1.000000 1.000000 1.000000 scn +0.435974 15.815962 m +0.000000 14.960315 0.000000 13.840210 0.000000 11.600000 c +0.000000 5.538462 l +0.000000 4.106961 0.000000 3.391212 0.181116 2.809988 c +0.572199 1.554960 1.554961 0.572199 2.809988 0.181116 c +3.391211 0.000000 4.106961 0.000000 5.538461 0.000000 c +7.132836 0.000000 8.332593 0.000000 9.296358 0.049431 c +9.392109 0.065231 9.458178 0.084789 9.510882 0.106621 c +9.837995 0.242115 10.097885 0.502007 10.233379 0.829117 c +10.269915 0.917324 10.300633 1.046162 10.317467 1.296604 c +10.334650 1.552246 10.335000 1.881901 10.335000 2.365276 c +10.335000 2.390583 l +10.334996 3.020454 10.334994 3.541773 10.356327 3.976810 c +9.927901 3.881188 9.474939 3.834996 8.999999 3.834996 c +7.566772 3.834998 6.432307 4.408777 5.671366 4.962191 c +5.289614 5.239828 4.994541 5.517408 4.793315 5.727383 c +4.692429 5.832656 4.614337 5.921766 4.559963 5.986553 c +4.532754 6.018972 4.511416 6.045382 4.496066 6.064772 c +4.477521 6.088497 l +4.471629 6.096197 l +4.469531 6.098966 l +4.468694 6.100076 l +4.468329 6.100561 l +4.468162 6.100784 4.468000 6.101000 5.000000 6.500000 c +5.532000 6.899000 l +5.531705 6.899393 l +5.538846 6.890306 l +5.546690 6.880400 5.560019 6.863840 5.578709 6.841572 c +5.616131 6.796984 5.674759 6.729844 5.753560 6.647617 c +5.911709 6.482592 6.147886 6.260172 6.453634 6.037808 c +7.067693 5.591221 7.933228 5.164998 9.000001 5.164996 c +9.630135 5.164996 10.171014 5.264124 10.633532 5.448587 c +10.639013 5.461485 10.644600 5.474353 10.650297 5.487185 c +11.018882 6.317377 11.682623 6.981118 12.512815 7.349703 c +12.904016 7.523386 13.325486 7.596285 13.811561 7.631045 c +14.286482 7.665009 14.873948 7.665005 15.609417 7.665000 c +15.634724 7.665000 l +16.118099 7.665000 16.447754 7.665350 16.703396 7.682533 c +16.953838 7.699367 17.082676 7.730085 17.170883 7.766621 c +17.497993 7.902115 17.757885 8.162005 17.893379 8.489118 c +17.915211 8.541822 17.934769 8.607892 17.950569 8.703643 c +18.000000 9.667408 18.000000 10.867164 18.000000 12.461538 c +18.000000 13.893039 18.000000 14.608788 17.818884 15.190012 c +17.427801 16.445040 16.445040 17.427801 15.190012 17.818884 c +14.608788 18.000000 13.893039 18.000000 12.461538 18.000000 c +6.400000 18.000000 l +4.159790 18.000000 3.039685 18.000000 2.184038 17.564026 c +1.431390 17.180532 0.819467 16.568611 0.435974 15.815962 c +h +17.652130 6.526627 m +17.381193 6.419186 17.097967 6.376053 16.792589 6.355527 c +16.487034 6.334990 16.112497 6.334994 15.657384 6.335000 c +15.634724 6.335000 l +14.868204 6.335000 14.328320 6.334603 13.906430 6.304433 c +13.490259 6.274672 13.242615 6.218527 13.052504 6.134122 c +12.523582 5.899294 12.100706 5.476418 11.865878 4.947496 c +11.781473 4.757385 11.725328 4.509741 11.695567 4.093570 c +11.665397 3.671680 11.665000 3.131796 11.665000 2.365276 c +11.665000 2.342616 l +11.665006 1.887503 11.665010 1.512966 11.644473 1.207411 c +11.623947 0.902033 11.580814 0.618807 11.473373 0.347870 c +11.542342 0.366713 11.610334 0.386574 11.677527 0.407513 c +14.501338 1.287447 16.712553 3.498662 17.592487 6.322473 c +17.613426 6.389666 17.633287 6.457658 17.652130 6.526627 c +h +5.531581 6.899558 m +5.311111 7.192892 4.894629 7.252222 4.601000 7.032000 c +4.307185 6.811639 4.247638 6.394815 4.468000 6.101000 c +5.000000 6.500000 l +5.532000 6.899000 5.531850 6.899200 5.531705 6.899393 c +5.531581 6.899558 l +h +7.500000 11.187500 m +7.500000 10.324555 6.940356 9.625000 6.250000 9.625000 c +5.559644 9.625000 5.000000 10.324555 5.000000 11.187500 c +5.000000 12.050446 5.559644 12.750000 6.250000 12.750000 c +6.940356 12.750000 7.500000 12.050446 7.500000 11.187500 c +h +13.000000 11.187500 m +13.000000 10.324555 12.440355 9.625000 11.750000 9.625000 c +11.059645 9.625000 10.500000 10.324555 10.500000 11.187500 c +10.500000 12.050446 11.059645 12.750000 11.750000 12.750000 c +12.440355 12.750000 13.000000 12.050446 13.000000 11.187500 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 3949 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000004039 00000 n +0000004062 00000 n +0000004235 00000 n +0000004309 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +4368 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Upload.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Upload.imageset/Contents.json new file mode 100644 index 0000000000..d89e9f97b8 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Upload.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Files.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Perk/Upload.imageset/Files.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Upload.imageset/Files.pdf new file mode 100644 index 0000000000..3029b60167 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Perk/Upload.imageset/Files.pdf @@ -0,0 +1,119 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 7.000000 6.000000 cm +1.000000 1.000000 1.000000 scn +5.330000 18.000000 m +5.807298 18.000000 6.123703 17.999550 6.367812 17.982895 c +6.604466 17.966749 6.711959 17.938187 6.777740 17.910938 c +7.064423 17.792191 7.292191 17.564423 7.410939 17.277740 c +7.438186 17.211960 7.466748 17.104465 7.482895 16.867813 c +7.499550 16.623703 7.500000 16.307299 7.500000 15.830000 c +7.500000 13.530003 l +7.500000 13.496872 l +7.499987 12.965037 7.499976 12.516354 7.530005 12.148819 c +7.561447 11.763987 7.629904 11.395631 7.808452 11.045210 c +8.079773 10.512712 8.512709 10.079777 9.045207 9.808455 c +9.395627 9.629908 9.763983 9.561451 10.148815 9.530008 c +10.516351 9.499980 10.965034 9.499990 11.496870 9.500004 c +11.530001 9.500004 l +13.829997 9.500004 l +14.307296 9.500004 14.623703 9.499555 14.867812 9.482899 c +15.104466 9.466752 15.211960 9.438190 15.277740 9.410942 c +15.564423 9.292194 15.792191 9.064426 15.910938 8.777744 c +15.938186 8.711964 15.966748 8.604470 15.982895 8.367816 c +15.999551 8.123706 16.000000 7.807300 16.000000 7.330000 c +16.000000 4.800000 l +16.000000 3.119843 16.000000 2.279763 15.673019 1.638029 c +15.385400 1.073542 14.926457 0.614601 14.361972 0.326981 c +13.720237 0.000000 12.880157 0.000000 11.200000 0.000000 c +4.800000 0.000000 l +3.119843 0.000000 2.279764 0.000000 1.638029 0.326981 c +1.073542 0.614601 0.614601 1.073542 0.326980 1.638029 c +0.000000 2.279763 0.000000 3.119843 0.000000 4.800000 c +0.000000 13.200001 l +0.000000 14.880157 0.000000 15.720236 0.326980 16.361971 c +0.614601 16.926458 1.073542 17.385399 1.638029 17.673019 c +2.279764 18.000000 3.119843 18.000000 4.800000 18.000000 c +5.330000 18.000000 l +h +15.621112 11.042418 m +15.558301 11.176268 15.488032 11.306717 15.410561 11.433140 c +15.163194 11.836806 14.817290 12.182710 14.125484 12.874516 c +10.874516 16.125484 l +10.182710 16.817291 9.836806 17.163193 9.433140 17.410561 c +9.306716 17.488033 9.176266 17.558304 9.042415 17.621115 c +9.097363 17.412268 9.124138 17.199299 9.139045 16.980810 c +9.160017 16.673437 9.160009 16.300030 9.160000 15.857494 c +9.160000 15.830000 l +9.160000 13.530003 l +9.160000 12.956255 9.160645 12.575861 9.184492 12.283996 c +9.207546 12.001820 9.248083 11.876238 9.287522 11.798835 c +9.399694 11.578686 9.578682 11.399698 9.798831 11.287526 c +9.876234 11.248087 10.001816 11.207550 10.283993 11.184496 c +10.575858 11.160649 10.956251 11.160004 11.530001 11.160004 c +13.829997 11.160004 l +13.857491 11.160004 l +14.300028 11.160013 14.673436 11.160021 14.980811 11.139048 c +15.199297 11.124141 15.412265 11.097366 15.621112 11.042418 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2637 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002727 00000 n +0000002750 00000 n +0000002923 00000 n +0000002997 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3056 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index f6622e108e..6a0f1c40a5 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -375,8 +375,8 @@ public class ShareRootControllerImpl { return .single(.done) } switch state { - case .preparing: - return .single(.preparing) + case let .preparing(long): + return .single(.preparing(long)) case let .progress(value): return .single(.progress(value)) case let .userInteractionRequired(value): diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 26d8124ec5..1652870db1 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -15,6 +15,7 @@ import AppBundle import DatePickerNode import DebugSettingsUI import TabBarUI +import PremiumUI public final class TelegramRootController: NavigationController { private let context: AccountContext @@ -129,8 +130,12 @@ public final class TelegramRootController: NavigationController { self.accountSettingsController = accountSettingsController self.rootTabController = tabBarController self.pushViewController(tabBarController, animated: false) - - let _ = getUserLimits(postbox: self.context.account.postbox).start() + + Queue.mainQueue().after(1.0) { +// let screen = PremiumLimitScreen(context: self.context, subject: .pins, action: {}) + let screen = PremiumIntroScreen(context: self.context, action: {}) + self.chatListController?.push(screen) + } } public func updateRootControllers(showCallsTab: Bool) {