From f8c5bf2a6b52038df118cb4b177527ceca83f424 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 25 Oct 2022 19:56:38 +0300 Subject: [PATCH] Move limits page to horizontal scroll --- .../PremiumUI/Sources/PremiumDemoScreen.swift | 58 +- .../PremiumUI/Sources/PremiumGiftScreen.swift | 52 +- .../Sources/PremiumIntroScreen.swift | 59 +- .../Sources/PremiumLimitsListScreen.swift | 974 ++++++++++++++---- .../PremiumUI/Sources/ScrollComponent.swift | 20 +- .../Sources/GenerateTextEntities.swift | 3 +- .../UrlWhitelist/Sources/UrlWhitelist.swift | 3 +- 7 files changed, 878 insertions(+), 291 deletions(-) diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index ccf4920171..3f6d8c6c24 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -17,7 +17,7 @@ import SolidRoundedButtonComponent import Markdown import TelegramUIPreferences -private final class GradientBackgroundComponent: Component { +final class GradientBackgroundComponent: Component { public let colors: [UIColor] public init( @@ -153,7 +153,7 @@ final class DemoPageEnvironment: Equatable { } } -private final class PageComponent: CombinedComponent { +final class PageComponent: CombinedComponent { typealias EnvironmentType = ChildEnvironment private let content: AnyComponent @@ -263,7 +263,7 @@ private final class PageComponent: CombinedComponen } } -private final class DemoPagerComponent: Component { +final class DemoPagerComponent: Component { public final class Item: Equatable { public let content: AnyComponentWithIdentity @@ -282,40 +282,29 @@ private final class DemoPagerComponent: Component { let items: [Item] let index: Int - let activeColor: UIColor - let inactiveColor: UIColor + let updated: (CGFloat, Int) -> Void public init( items: [Item], index: Int = 0, - activeColor: UIColor, - inactiveColor: UIColor + updated: @escaping (CGFloat, Int) -> Void ) { self.items = items self.index = index - self.activeColor = activeColor - self.inactiveColor = inactiveColor + self.updated = updated } public static func ==(lhs: DemoPagerComponent, rhs: DemoPagerComponent) -> Bool { if lhs.items != rhs.items { return false } - if !lhs.activeColor.isEqual(rhs.activeColor) { - return false - } - if !lhs.inactiveColor.isEqual(rhs.inactiveColor) { - return false - } return true } - fileprivate final class View: UIView, UIScrollViewDelegate { + final class View: UIView, UIScrollViewDelegate { private let scrollView: UIScrollView private var itemViews: [AnyHashable: ComponentHostView] = [:] - private let pageIndicatorView: ComponentHostView - private var component: DemoPagerComponent? override init(frame: CGRect) { @@ -327,15 +316,11 @@ private final class DemoPagerComponent: Component { self.scrollView.bounces = false self.scrollView.layer.cornerRadius = 10.0 - self.pageIndicatorView = ComponentHostView() - self.pageIndicatorView.isUserInteractionEnabled = false - super.init(frame: frame) self.scrollView.delegate = self self.addSubview(self.scrollView) - self.addSubview(self.pageIndicatorView) } required init?(coder: NSCoder) { @@ -350,6 +335,7 @@ private final class DemoPagerComponent: Component { self.ignoreContentOffsetChange = true let _ = self.update(component: component, availableSize: self.bounds.size, transition: .immediate) + component.updated(self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - self.scrollView.frame.width), component.items.count) self.ignoreContentOffsetChange = false } @@ -369,6 +355,7 @@ private final class DemoPagerComponent: Component { if firstTime { self.scrollView.contentOffset = CGPoint(x: CGFloat(component.index) * availableSize.width, y: 0.0) + component.updated(self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - self.scrollView.frame.width), component.items.count) } let viewportCenter = self.scrollView.contentOffset.x + availableSize.width * 0.5 @@ -398,7 +385,6 @@ private final class DemoPagerComponent: Component { itemView = ComponentHostView() self.itemViews[item.content.id] = itemView - if item.content.id == (PremiumDemoScreen.Subject.fasterDownload as AnyHashable) { self.scrollView.insertSubview(itemView, at: 0) } else { @@ -430,33 +416,15 @@ private final class DemoPagerComponent: Component { self.component = component - if component.items.count > 1 { - let pageIndicatorComponent = PageIndicatorComponent( - pageCount: component.items.count, - position: self.scrollView.contentOffset.x / (self.scrollView.contentSize.width - availableSize.width), - inactiveColor: component.inactiveColor, - activeColor: component.activeColor - ) - let indicatorSize = self.pageIndicatorView.update( - transition: .immediate, - component: AnyComponent( - pageIndicatorComponent - ), - environment: {}, - containerSize: availableSize - ) - self.pageIndicatorView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - indicatorSize.width) / 2.0), y: availableSize.height - indicatorSize.height - 11.0), size: indicatorSize) - } - return availableSize } } - public func makeView() -> View { + func makeView() -> View { return View(frame: CGRect()) } - public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { return view.update(component: self, availableSize: availableSize, transition: transition) } } @@ -949,8 +917,7 @@ private final class DemoSheetContent: CombinedComponent { component: DemoPagerComponent( items: items, index: index, - activeColor: UIColor(rgb: 0x7169ff), - inactiveColor: theme.list.disclosureArrowColor + updated: { _, _ in } ), availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.width + 154.0), transition: context.transition @@ -1177,6 +1144,7 @@ private final class DemoSheetComponent: CombinedComponent { public class PremiumDemoScreen: ViewControllerComponentContainer { public enum Subject { + case doubleLimits case moreUpload case fasterDownload case voiceToText diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index baecdf49a9..c6499849bf 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -361,21 +361,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { var demoSubject: PremiumDemoScreen.Subject switch perk { case .doubleLimits: - var dismissImpl: (() -> Void)? - let controller = PremimLimitsListScreen(context: accountContext, buttonText: strings.Premium_Gift_GiftSubscription(state?.price ?? "–").string, isPremium: false) - controller.action = { - dismissImpl?() - buy() - } - controller.disposed = { -// updateIsFocused(false) - } - present(controller) - dismissImpl = { [weak controller] in - controller?.dismiss(animated: true, completion: nil) - } -// updateIsFocused(true) - return + demoSubject = .doubleLimits case .moreUpload: demoSubject = .moreUpload case .fasterDownload: @@ -402,20 +388,34 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { demoSubject = .emojiStatus } - let controller = PremiumDemoScreen( - context: accountContext, - subject: demoSubject, - source: .gift(state?.price), - order: state?.configuration.perks, - action: { - buy() - } - ) + var dismissImpl: (() -> Void)? + let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .gift(state?.price), order: state?.configuration.perks, buttonText: strings.Premium_Gift_GiftSubscription(state?.price ?? "–").string, isPremium: false) + controller.action = { + dismissImpl?() + buy() + } controller.disposed = { -// updateIsFocused(false) +// updateIsFocused(false) } present(controller) -// updateIsFocused(true) + dismissImpl = { [weak controller] in + controller?.dismiss(animated: true, completion: nil) + } + +// let controller = PremiumDemoScreen( +// context: accountContext, +// subject: demoSubject, +// source: .gift(state?.price), +// order: state?.configuration.perks, +// action: { +// buy() +// } +// ) +// controller.disposed = { +//// updateIsFocused(false) +// } +// present(controller) +//// updateIsFocused(true) addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier]) } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 7e1cf67650..e25230a7af 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1556,25 +1556,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { var demoSubject: PremiumDemoScreen.Subject switch perk { case .doubleLimits: - let isPremium = state?.isPremium == true - - var dismissImpl: (() -> Void)? - let controller = PremimLimitsListScreen(context: accountContext, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "–").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium) - controller.action = { [weak state] in - dismissImpl?() - if state?.isPremium == false { - buy() - } - } - controller.disposed = { - updateIsFocused(false) - } - present(controller) - dismissImpl = { [weak controller] in - controller?.dismiss(animated: true, completion: nil) - } - updateIsFocused(true) - return + demoSubject = .doubleLimits case .moreUpload: demoSubject = .moreUpload case .fasterDownload: @@ -1601,23 +1583,42 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { demoSubject = .emojiStatus } - let controller = PremiumDemoScreen( - context: accountContext, - subject: demoSubject, - source: .intro(state?.price), - order: state?.configuration.perks, - action: { - if state?.isPremium == false { - buy() - } + let isPremium = state?.isPremium == true + + var dismissImpl: (() -> Void)? + let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: state?.configuration.perks, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "–").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium) + controller.action = { [weak state] in + dismissImpl?() + if state?.isPremium == false { + buy() } - ) + } controller.disposed = { updateIsFocused(false) } present(controller) + dismissImpl = { [weak controller] in + controller?.dismiss(animated: true, completion: nil) + } updateIsFocused(true) +// let controller = PremiumDemoScreen( +// context: accountContext, +// subject: demoSubject, +// source: .intro(state?.price), +// order: state?.configuration.perks, +// action: { +// if state?.isPremium == false { +// buy() +// } +// } +// ) +// controller.disposed = { +// updateIsFocused(false) +// } +// present(controller) +// updateIsFocused(true) + addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier]) } )) diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 7eef3f2c21..70155ed74e 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -7,6 +7,7 @@ import TelegramCore import SwiftSignalKit import AccountContext import TelegramPresentationData +import TelegramUIPreferences import PresentationDataUtils import ComponentFlow import ViewControllerComponent @@ -281,23 +282,29 @@ private enum Limit: CaseIterable { } } -private final class PremimLimitsListScreenComponent: CombinedComponent { - typealias EnvironmentType = ViewControllerComponentContainer.Environment +private final class LimitsListComponent: CombinedComponent { + typealias EnvironmentType = (Empty, ScrollChildEnvironment) let context: AccountContext - let expand: () -> Void + let topInset: CGFloat + let bottomInset: CGFloat - var disposable: Disposable? - - init(context: AccountContext, expand: @escaping () -> Void) { + init(context: AccountContext, topInset: CGFloat, bottomInset: CGFloat) { self.context = context - self.expand = expand + self.topInset = topInset + self.bottomInset = bottomInset } - static func ==(lhs: PremimLimitsListScreenComponent, rhs: PremimLimitsListScreenComponent) -> Bool { + static func ==(lhs: LimitsListComponent, rhs: LimitsListComponent) -> Bool { if lhs.context !== rhs.context { return false } + if lhs.topInset != rhs.topInset { + return false + } + if lhs.bottomInset != rhs.bottomInset { + return false + } return true } @@ -339,10 +346,9 @@ private final class PremimLimitsListScreenComponent: CombinedComponent { let list = Child(List.self) return { context in - let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let state = context.state - let theme = environment.theme - let strings = environment.strings + let theme = context.component.context.sharedContext.currentPresentationData.with { $0 }.theme + let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings let colors = [ UIColor(rgb: 0x5ba0ff), @@ -378,35 +384,222 @@ private final class PremimLimitsListScreenComponent: CombinedComponent { ) ) } - + let list = list.update( component: List(items), - availableSize: context.availableSize, + availableSize: CGSize(width: context.availableSize.width, height: 10000.0), transition: context.transition ) + let contentHeight = context.component.topInset + list.size.height + context.component.bottomInset context.add(list - .position(CGPoint(x: context.availableSize.width / 2.0, y: environment.navigationHeight + list.size.height / 2.0)) + .position(CGPoint(x: list.size.width / 2.0, y: context.component.topInset + list.size.height / 2.0)) ) - return CGSize(width: context.availableSize.width, height: environment.navigationHeight + list.size.height + environment.safeInsets.bottom) + return CGSize(width: context.availableSize.width, height: contentHeight) } } } -public class PremimLimitsListScreen: ViewController { +private final class LimitsPageComponent: CombinedComponent { + typealias EnvironmentType = DemoPageEnvironment + + let context: AccountContext + let bottomInset: CGFloat + let updatedBottomAlpha: (CGFloat) -> Void + let updatedDismissOffset: (CGFloat) -> Void + let updatedIsDisplaying: (Bool) -> Void + + init(context: AccountContext, bottomInset: CGFloat, updatedBottomAlpha: @escaping (CGFloat) -> Void, updatedDismissOffset: @escaping (CGFloat) -> Void, updatedIsDisplaying: @escaping (Bool) -> Void) { + self.context = context + self.bottomInset = bottomInset + self.updatedBottomAlpha = updatedBottomAlpha + self.updatedDismissOffset = updatedDismissOffset + self.updatedIsDisplaying = updatedIsDisplaying + } + + static func ==(lhs: LimitsPageComponent, rhs: LimitsPageComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.bottomInset != rhs.bottomInset { + return false + } + return true + } + + final class State: ComponentState { + let updateBottomAlpha: (CGFloat) -> Void + let updateDismissOffset: (CGFloat) -> Void + let updatedIsDisplaying: (Bool) -> Void + + var resetScroll: ActionSlot? + + var topContentOffset: CGFloat = 0.0 + var bottomContentOffset: CGFloat = 100.0 { + didSet { + self.updateAlpha() + } + } + + var position: CGFloat? { + didSet { + self.updateAlpha() + } + } + + var isDisplaying = false { + didSet { + if oldValue != self.isDisplaying { + self.updatedIsDisplaying(self.isDisplaying) + + if !self.isDisplaying { + self.resetScroll?.invoke(Void()) + } + } + } + } + + init(updateBottomAlpha: @escaping (CGFloat) -> Void, updateDismissOffset: @escaping (CGFloat) -> Void, updateIsDisplaying: @escaping (Bool) -> Void) { + self.updateBottomAlpha = updateBottomAlpha + self.updateDismissOffset = updateDismissOffset + self.updatedIsDisplaying = updateIsDisplaying + + super.init() + } + + func updateAlpha() { + let dismissPosition = min(1.0, abs(self.position ?? 0.0) / 1.3333) + let position = min(1.0, abs(self.position ?? 0.0)) + self.updateDismissOffset(dismissPosition) + + let verticalPosition = 1.0 - min(30.0, self.bottomContentOffset) / 30.0 + + let backgroundAlpha: CGFloat = max(position, verticalPosition) + self.updateBottomAlpha(backgroundAlpha) + } + } + + func makeState() -> State { + return State(updateBottomAlpha: self.updatedBottomAlpha, updateDismissOffset: self.updatedDismissOffset, updateIsDisplaying: self.updatedIsDisplaying) + } + + static var body: Body { + let background = Child(Rectangle.self) + let scroll = Child(ScrollComponent.self) + let topPanel = Child(BlurredRectangle.self) + let topSeparator = Child(Rectangle.self) + let title = Child(MultilineTextComponent.self) + + let resetScroll = ActionSlot() + + return { context in + let state = context.state + + let environment = context.environment[DemoPageEnvironment.self].value + state.resetScroll = resetScroll + state.position = environment.position + state.isDisplaying = environment.isDisplaying + + let theme = context.component.context.sharedContext.currentPresentationData.with { $0 }.theme + let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings + + let topInset: CGFloat = 56.0 + + let scroll = scroll.update( + component: ScrollComponent( + content: AnyComponent( + LimitsListComponent( + context: context.component.context, + topInset: topInset, + bottomInset: context.component.bottomInset + ) + ), + contentInsets: UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0), + contentOffsetUpdated: { [weak state] topContentOffset, bottomContentOffset in + state?.topContentOffset = topContentOffset + state?.bottomContentOffset = bottomContentOffset + Queue.mainQueue().justDispatch { + state?.updated(transition: .immediate) + } + }, + contentOffsetWillCommit: { _ in }, + resetScroll: resetScroll + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let background = background.update( + component: Rectangle(color: theme.list.plainBackgroundColor), + availableSize: scroll.size, + transition: context.transition + ) + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0)) + ) + + context.add(scroll + .position(CGPoint(x: context.availableSize.width / 2.0, y: scroll.size.height / 2.0)) + ) + + let topPanel = topPanel.update( + component: BlurredRectangle( + color: theme.rootController.navigationBar.blurredBackgroundColor + ), + availableSize: CGSize(width: context.availableSize.width, height: topInset), + transition: context.transition + ) + + let topSeparator = topSeparator.update( + component: Rectangle( + color: theme.rootController.navigationBar.separatorColor + ), + availableSize: CGSize(width: context.availableSize.width, height: UIScreenPixel), + transition: context.transition + ) + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString(string: strings.Premium_DoubledLimits, font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center, + truncationType: .end, + maximumNumberOfLines: 1 + ), + availableSize: context.availableSize, + transition: context.transition + ) + + let topPanelAlpha: CGFloat = min(30.0, state.topContentOffset) / 30.0 + 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: topPanel.size.height / 2.0)) + ) + + return scroll.size + } + } +} + +public class PremiumLimitsListScreen: ViewController { final class Node: ViewControllerTracingNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private var presentationData: PresentationData - private weak var controller: PremimLimitsListScreen? - - private let component: AnyComponent - private let theme: PresentationTheme? - + private weak var controller: PremiumLimitsListScreen? + let dim: ASDisplayNode let wrappingView: UIView let containerView: UIView - let scrollView: UIScrollView - let hostView: ComponentHostView + + let backgroundView: ComponentHostView + let pagerView: ComponentHostView + let closeView: ComponentHostView fileprivate let footerNode: FooterNode @@ -415,33 +608,37 @@ public class PremimLimitsListScreen: ViewController { private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? private var currentIsVisible: Bool = false - private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? + private var currentLayout: ContainerViewLayout? + + var isPremium: Bool? + var reactions: [AvailableReactions.Reaction]? + var stickers: [TelegramMediaFile]? + var appIcons: [PresentationAppIcon]? + var disposable: Disposable? + var promoConfiguration: PremiumPromoConfiguration? - fileprivate var temporaryDismiss = false - - init(context: AccountContext, controller: PremimLimitsListScreen, component: AnyComponent, theme: PresentationTheme?, buttonTitle: String, gloss: Bool) { + init(context: AccountContext, controller: PremiumLimitsListScreen, buttonTitle: String, gloss: Bool) { 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.scrollView = UIScrollView() - self.hostView = ComponentHostView() +// self.scrollView = UIScrollView() + self.backgroundView = ComponentHostView() + self.pagerView = ComponentHostView() + self.closeView = ComponentHostView() self.footerNode = FooterNode(theme: self.presentationData.theme, title: buttonTitle, gloss: gloss) super.init() - self.scrollView.delegate = self - self.scrollView.showsVerticalScrollIndicator = false +// self.scrollView.delegate = self +// self.scrollView.showsVerticalScrollIndicator = false self.containerView.clipsToBounds = true self.containerView.backgroundColor = self.presentationData.theme.list.plainBackgroundColor @@ -450,13 +647,110 @@ public class PremimLimitsListScreen: ViewController { self.view.addSubview(self.wrappingView) self.wrappingView.addSubview(self.containerView) - self.containerView.addSubview(self.scrollView) + self.containerView.addSubview(self.backgroundView) + self.containerView.addSubview(self.pagerView) self.containerView.addSubnode(self.footerNode) - self.scrollView.addSubview(self.hostView) + self.containerView.addSubview(self.closeView) +// self.scrollView.addSubview(self.hostView) self.footerNode.action = { [weak self] in self?.controller?.action() } + + let context = controller.context + + let accountSpecificStickerOverrides: [ExperimentalUISettings.AccountReactionOverrides.Item] + if context.sharedContext.immediateExperimentalUISettings.enableReactionOverrides, let value = context.sharedContext.immediateExperimentalUISettings.accountStickerEffectOverrides.first(where: { $0.accountId == context.account.id.int64 }) { + accountSpecificStickerOverrides = value.items + } else { + accountSpecificStickerOverrides = [] + } + let stickerOverrideMessages = context.engine.data.get( + EngineDataMap(accountSpecificStickerOverrides.map(\.messageId).map(TelegramEngine.EngineData.Item.Messages.Message.init)) + ) + + self.appIcons = controller.context.sharedContext.applicationBindings.getAvailableAlternateIcons() + + let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers) + self.disposable = (combineLatest( + queue: Queue.mainQueue(), + context.account.postbox.combinedView(keys: [stickersKey]) + |> map { views -> [OrderedItemListEntry]? in + if let view = views.views[stickersKey] as? OrderedItemListView { + return view.items + } else { + return nil + } + } + |> filter { items in + return items != nil + } + |> take(1), + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), + TelegramEngine.EngineData.Item.Configuration.PremiumPromo() + ), + stickerOverrideMessages + ) + |> map { items, data, stickerOverrideMessages -> ([TelegramMediaFile], Bool?, PremiumPromoConfiguration?) in + var stickerOverrides: [MessageReaction.Reaction: TelegramMediaFile] = [:] + for item in accountSpecificStickerOverrides { + if let maybeMessage = stickerOverrideMessages[item.messageId], let message = maybeMessage { + for media in message.media { + if let file = media as? TelegramMediaFile, file.fileId == item.mediaId { + stickerOverrides[item.key] = file + } + } + } + } + + var result: [TelegramMediaFile] = [] + if let items = items { + for item in items { + if let mediaItem = item.contents.get(RecentMediaItem.self) { + result.append(mediaItem.media) + } + } + } + return (result.map { file -> TelegramMediaFile in + for attribute in file.attributes { + switch attribute { + case let .Sticker(displayText, _, _): + if let replacementFile = stickerOverrides[.builtin(displayText)], let dimensions = replacementFile.dimensions { + let _ = dimensions + return TelegramMediaFile( + fileId: file.fileId, + partialReference: file.partialReference, + resource: file.resource, + previewRepresentations: file.previewRepresentations, + videoThumbnails: [TelegramMediaFile.VideoThumbnail(dimensions: dimensions, resource: replacementFile.resource)], + immediateThumbnailData: file.immediateThumbnailData, + mimeType: file.mimeType, + size: file.size, + attributes: file.attributes + ) + } + default: + break + } + } + return file + }, data.0?.isPremium ?? false, data.1) + }).start(next: { [weak self] stickers, isPremium, promoConfiguration in + guard let strongSelf = self else { + return + } + strongSelf.stickers = stickers + strongSelf.isPremium = isPremium + strongSelf.promoConfiguration = promoConfiguration + if !stickers.isEmpty { + strongSelf.updated(transition: Transition(.immediate).withUserData(DemoAnimateInTransition())) + } + }) + } + + deinit { + self.disposable?.dispose() } override func didLoad() { @@ -481,7 +775,7 @@ public class PremimLimitsListScreen: ViewController { } override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - if let (layout, _) = self.currentLayout { + if let layout = self.currentLayout { if case .regular = layout.metrics.widthClass { return false } @@ -489,18 +783,13 @@ public class PremimLimitsListScreen: ViewController { return true } - func scrollViewDidScroll(_ scrollView: UIScrollView) { - let contentOffset = self.scrollView.contentOffset.y - self.controller?.navigationBar?.updateBackgroundAlpha(min(30.0, contentOffset) / 30.0, transition: .immediate) - - let bottomOffsetY = max(0.0, self.scrollView.contentSize.height - contentOffset - self.scrollView.frame.height) - let backgroundAlpha: CGFloat = min(30.0, bottomOffsetY) / 30.0 - - self.footerNode.updateBackgroundAlpha(backgroundAlpha, transition: .immediate) - } - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { + if let scrollView = otherGestureRecognizer.view as? UIScrollView { + if scrollView.contentSize.width > scrollView.contentSize.height || scrollView.contentSize.height > 1500.0 { + return false + } + } return true } return false @@ -530,18 +819,11 @@ public class PremimLimitsListScreen: ViewController { }) let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) - - if !self.temporaryDismiss { - self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) - } } - 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) - } + private var dismissOffset: CGFloat? + func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) { + self.currentLayout = layout 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)) @@ -559,6 +841,8 @@ public class PremimLimitsListScreen: ViewController { } else { topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) } + } else if let dismissOffset = self.dismissOffset { + topInset = edgeTopInset * dismissOffset } else { topInset = effectiveExpanded ? 0.0 : edgeTopInset } @@ -615,7 +899,7 @@ public class PremimLimitsListScreen: ViewController { } transition.setFrame(view: self.containerView, frame: clipFrame) - transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: clipFrame.size), completion: nil) +// transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: clipFrame.size), completion: nil) var clipLayout = layout.withUpdatedSize(clipFrame.size) if case .regular = layout.metrics.widthClass { @@ -626,36 +910,359 @@ public class PremimLimitsListScreen: ViewController { let convertedFooterFrame = self.view.convert(CGRect(origin: CGPoint(x: clipFrame.minX, y: clipFrame.maxY - footerHeight), size: CGSize(width: clipFrame.width, height: footerHeight)), to: self.containerView) transition.setFrame(view: self.footerNode.view, frame: convertedFooterFrame) - let environment = ViewControllerComponentContainer.Environment( - statusBarHeight: 0.0, - navigationHeight: navigationHeight, - safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: footerHeight, right: layout.safeInsets.right), - inputHeight: layout.inputHeight ?? 0.0, - metrics: layout.metrics, - deviceMetrics: layout.deviceMetrics, - isVisible: self.currentIsVisible, - theme: self.theme ?? self.presentationData.theme, - strings: self.presentationData.strings, - dateTimeFormat: self.presentationData.dateTimeFormat, - controller: { [weak self] in - return self?.controller - } - ) - var contentSize = self.hostView.update( - transition: transition, - component: self.component, - environment: { - environment - }, - forceUpdate: true, - containerSize: CGSize(width: clipFrame.size.width, height: 10000.0) - ) - contentSize.height = max(layout.size.height - navigationHeight, contentSize.height) - transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) - - self.scrollView.contentSize = contentSize + self.updated(transition: transition) } + func updated(transition: Transition) { + guard let controller = self.controller, let layout = self.currentLayout else { + return + } + + let backgroundSize = self.backgroundView.update( + transition: .immediate, + component: AnyComponent( + GradientBackgroundComponent(colors: [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ]) + ), + environment: {}, + containerSize: CGSize(width: layout.size.width, height: layout.size.width) + ) + self.backgroundView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - backgroundSize.width) / 2.0), y: 0.0), size: backgroundSize) + + var isStandalone = false + if case .other = controller.source { + isStandalone = true + } + + if let stickers = self.stickers, let appIcons = self.appIcons, let configuration = self.promoConfiguration { + let context = controller.context + let theme = self.presentationData.theme + let strings = self.presentationData.strings + + let textColor = theme.actionSheet.primaryTextColor + + var availableItems: [PremiumPerk: DemoPagerComponent.Item] = [:] + availableItems[.doubleLimits] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.doubleLimits, + component: AnyComponent( + LimitsPageComponent( + context: context, + bottomInset: self.footerNode.frame.height, + updatedBottomAlpha: { [weak self] alpha in + if let strongSelf = self { + strongSelf.footerNode.updateCoverAlpha(alpha, transition: .immediate) + } + }, + updatedDismissOffset: { [weak self] offset in + if let strongSelf = self { + strongSelf.updateDismissOffset(offset) + } + }, + updatedIsDisplaying: { [weak self] isDisplaying in + if let strongSelf = self, strongSelf.isExpanded && !isDisplaying { + strongSelf.update(isExpanded: false, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + ) + ) + ) + ) + availableItems[.moreUpload] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.moreUpload, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .bottom, + videoFile: configuration.videos["more_upload"], + decoration: .dataRain + )), + title: strings.Premium_UploadSize, + text: strings.Premium_UploadSizeInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.fasterDownload] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.fasterDownload, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + videoFile: configuration.videos["faster_download"], + decoration: .fasterStars + )), + title: strings.Premium_FasterSpeed, + text: strings.Premium_FasterSpeedInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.voiceToText] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.voiceToText, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + videoFile: configuration.videos["voice_to_text"], + decoration: .badgeStars + )), + title: strings.Premium_VoiceToText, + text: strings.Premium_VoiceToTextInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.noAds] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.noAds, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .bottom, + videoFile: configuration.videos["no_ads"], + decoration: .swirlStars + )), + title: strings.Premium_NoAds, + text: isStandalone ? strings.Premium_NoAdsStandaloneInfo : strings.Premium_NoAdsInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.uniqueReactions] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.uniqueReactions, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + videoFile: configuration.videos["infinite_reactions"], + decoration: .swirlStars + )), + title: strings.Premium_InfiniteReactions, + text: strings.Premium_InfiniteReactionsInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.premiumStickers] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.premiumStickers, + component: AnyComponent( + PageComponent( + content: AnyComponent( + StickersCarouselComponent( + context: context, + stickers: stickers + ) + ), + title: strings.Premium_Stickers, + text: strings.Premium_StickersInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.emojiStatus] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.emojiStatus, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + videoFile: configuration.videos["emoji_status"], + decoration: .badgeStars + )), + title: strings.Premium_EmojiStatus, + text: strings.Premium_EmojiStatusInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.advancedChatManagement] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.advancedChatManagement, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + videoFile: configuration.videos["advanced_chat_management"], + decoration: .swirlStars + )), + title: strings.Premium_ChatManagement, + text: strings.Premium_ChatManagementInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.profileBadge] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.profileBadge, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + videoFile: configuration.videos["profile_badge"], + decoration: .badgeStars + )), + title: strings.Premium_Badge, + text: strings.Premium_BadgeInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.animatedUserpics] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.animatedUserpics, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .top, + videoFile: configuration.videos["animated_userpics"], + decoration: .swirlStars + )), + title: strings.Premium_Avatar, + text: strings.Premium_AvatarInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.appIcons] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.appIcons, + component: AnyComponent( + PageComponent( + content: AnyComponent(AppIconsDemoComponent( + context: context, + appIcons: appIcons + )), + title: isStandalone ? strings.Premium_AppIconStandalone : strings.Premium_AppIcon, + text: isStandalone ? strings.Premium_AppIconStandaloneInfo :strings.Premium_AppIconInfo, + textColor: textColor + ) + ) + ) + ) + + availableItems[.animatedEmoji] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.animatedEmoji, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: context, + position: .bottom, + videoFile: configuration.videos["animated_emoji"], + decoration: .emoji + )), + title: strings.Premium_AnimatedEmoji, + text: isStandalone ? strings.Premium_AnimatedEmojiStandaloneInfo : strings.Premium_AnimatedEmojiInfo, + textColor: textColor + ) + ) + ) + ) + + if let order = controller.order { + var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] } + let index: Int + switch controller.source { + case .intro, .gift: + index = items.firstIndex(where: { (controller.subject as AnyHashable) == $0.content.id }) ?? 0 + case .other: + items = items.filter { item in + return item.content.id == (controller.subject as AnyHashable) + } + index = 0 + } + + let pagerSize = self.pagerView.update( + transition: .immediate, + component: AnyComponent( + DemoPagerComponent( + items: items, + index: index, + updated: { [weak self] position, count in + if let strongSelf = self { + strongSelf.footerNode.updatePosition(position, count: count) + } + } + ) + ), + environment: {}, + containerSize: CGSize(width: layout.size.width, height: self.containerView.frame.height) + ) + self.pagerView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - pagerSize.width) / 2.0), y: 0.0), size: pagerSize) + } + } + + let closeImage: UIImage + if let image = self.cachedCloseImage { + closeImage = image + } else { + closeImage = generateCloseButtonImage(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xffffff))! + self.cachedCloseImage = closeImage + } + + + let closeSize = self.closeView.update( + transition: .immediate, + component: AnyComponent( + Button( + content: AnyComponent(ZStack([ + AnyComponentWithIdentity( + id: "background", + component: AnyComponent( + BlurredRectangle( + color: UIColor(rgb: 0x888888, alpha: 0.3), + radius: 15.0 + ) + ) + ), + AnyComponentWithIdentity( + id: "icon", + component: AnyComponent( + Image(image: closeImage) + ) + ), + ])), + action: { [weak self] in + self?.controller?.dismiss(animated: true, completion: nil) + } + ) + ), + environment: {}, + containerSize: CGSize(width: 30.0, height: 30.0) + ) + self.closeView.frame = CGRect(origin: CGPoint(x: layout.size.width - closeSize.width * 1.5, y: 28.0 - closeSize.height / 2.0), size: closeSize) + } + private var cachedCloseImage: UIImage? + private var didPlayAppearAnimation = false func updateIsVisible(isVisible: Bool) { if self.currentIsVisible == isVisible { @@ -663,10 +1270,10 @@ public class PremimLimitsListScreen: ViewController { } self.currentIsVisible = isVisible - guard let currentLayout = self.currentLayout else { + guard let layout = self.currentLayout else { return } - self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: .immediate) + self.containerLayoutUpdated(layout: layout, transition: .immediate) if !self.didPlayAppearAnimation { self.didPlayAppearAnimation = true @@ -675,36 +1282,36 @@ public class PremimLimitsListScreen: ViewController { } private var defaultTopInset: CGFloat { - guard let (layout, _) = self.currentLayout else{ + guard let layout = self.currentLayout else { return 210.0 } if case .compact = layout.metrics.widthClass { - var factor: CGFloat = 0.2488 - if layout.size.width <= 320.0 { - factor = 0.15 - } - return floor(max(layout.size.width, layout.size.height) * factor) + let bottomPanelPadding: CGFloat = 12.0 + let bottomInset: CGFloat = layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : bottomPanelPadding + let panelHeight: CGFloat = bottomPanelPadding + 50.0 + bottomInset + 28.0 + + return layout.size.height - layout.size.width - 178.0 - panelHeight } else { return 210.0 } } - private func findScrollView(view: UIView?) -> (UIScrollView, ListView?)? { + private func findVerticalScrollView(view: UIView?) -> (UIScrollView, ListView?)? { if let view = view { - if let view = view as? UIScrollView { + if let view = view as? UIScrollView, view.contentSize.height > view.contentSize.width && view.contentSize.height < 1500.0 { return (view, nil) } if let node = view.asyncdisplaykit_node as? ListView { return (node.scroller, node) } - return findScrollView(view: view.superview) + return findVerticalScrollView(view: view.superview) } else { return nil } } @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { - guard let (layout, navigationHeight) = self.currentLayout else { + guard let layout = self.currentLayout else { return } @@ -716,7 +1323,7 @@ public class PremimLimitsListScreen: ViewController { let point = recognizer.location(in: self.view) let currentHitView = self.hitTest(point, with: nil) - var scrollViewAndListNode = self.findScrollView(view: currentHitView) + var scrollViewAndListNode = self.findVerticalScrollView(view: currentHitView) if scrollViewAndListNode?.0.frame.height == self.frame.width { scrollViewAndListNode = nil } @@ -760,6 +1367,10 @@ public class PremimLimitsListScreen: ViewController { scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } } + + if scrollView == nil { + translation = max(0.0, translation) + } self.panGestureArguments = (topInset, translation, scrollView, listNode) @@ -778,7 +1389,7 @@ public class PremimLimitsListScreen: ViewController { bounds.origin.y = min(0.0, bounds.origin.y) self.bounds = bounds - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) + self.containerLayoutUpdated(layout: layout, transition: .immediate) case .ended: guard let (currentTopInset, panOffset, scrollView, listNode) = self.panGestureArguments else { return @@ -831,13 +1442,13 @@ public class PremimLimitsListScreen: ViewController { let initialVelocity: CGFloat = distance.isZero ? 0.0 : abs(velocity.y / distance) let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) + self.containerLayoutUpdated(layout: layout, transition: Transition(transition)) } else { self.isExpanded = true - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) + self.containerLayoutUpdated(layout: layout, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) } - } else if (velocity.y < -300.0 || offset < topInset / 2.0) { + } else if scrollView != nil, (velocity.y < -300.0 || offset < topInset / 2.0) { if velocity.y > -2200.0 && velocity.y < -300.0, let listNode = listNode { DispatchQueue.main.async { listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) @@ -848,7 +1459,7 @@ public class PremimLimitsListScreen: ViewController { let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) self.isExpanded = true - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) + self.containerLayoutUpdated(layout: layout, transition: Transition(transition)) } else { if let listNode = listNode { listNode.scroller.setContentOffset(CGPoint(), animated: false) @@ -856,7 +1467,7 @@ public class PremimLimitsListScreen: ViewController { scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) + self.containerLayoutUpdated(layout: layout, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) } if !dismissing { @@ -869,22 +1480,32 @@ public class PremimLimitsListScreen: ViewController { case .cancelled: self.panGestureArguments = nil - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) + self.containerLayoutUpdated(layout: layout, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) default: break } } + func updateDismissOffset(_ offset: CGFloat) { + guard self.isExpanded, let layout = self.currentLayout else { + return + } + + self.dismissOffset = offset + self.containerLayoutUpdated(layout: layout, transition: .immediate) + } + func update(isExpanded: Bool, transition: ContainedViewLayoutTransition) { guard isExpanded != self.isExpanded else { return } + self.dismissOffset = nil self.isExpanded = isExpanded - guard let (layout, navigationHeight) = self.currentLayout else { + guard let layout = self.currentLayout else { return } - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) + self.containerLayoutUpdated(layout: layout, transition: Transition(transition)) } } @@ -893,9 +1514,10 @@ public class PremimLimitsListScreen: ViewController { } private let context: AccountContext - private let theme: PresentationTheme? - private let component: AnyComponent - private var isInitiallyExpanded = false + + let subject: PremiumDemoScreen.Subject + let source: PremiumDemoScreen.Source + let order: [PremiumPerk]? private var currentLayout: ContainerViewLayout? @@ -905,46 +1527,22 @@ public class PremimLimitsListScreen: ViewController { var action: () -> Void = {} var disposed: () -> Void = {} - public convenience init(context: AccountContext, buttonText: String, isPremium: Bool) { - var expandImpl: (() -> Void)? - self.init(context: context, component: PremimLimitsListScreenComponent(context: context, expand: { - expandImpl?() - }), buttonText: buttonText, buttonGloss: !isPremium) - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.title = presentationData.strings.Premium_Limits_Title - - self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) - - let rightBarButtonNode = ASImageNode() - rightBarButtonNode.image = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: presentationData.theme.actionSheet.inputClearButtonColor) - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: rightBarButtonNode) - self.navigationItem.rightBarButtonItem?.target = self - self.navigationItem.rightBarButtonItem?.action = #selector(self.cancelPressed) - - self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - - expandImpl = { [weak self] in - self?.node.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) - if let currentLayout = self?.currentLayout { - self?.containerLayoutUpdated(currentLayout, transition: .animated(duration: 0.4, curve: .spring)) - } - } - } - - private init(context: AccountContext, component: C, theme: PresentationTheme? = nil, buttonText: String, buttonGloss: Bool) where C.EnvironmentType == ViewControllerComponentContainer.Environment { + init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, order: [PremiumPerk]?, buttonText: String, isPremium: Bool) { self.context = context - self.component = AnyComponent(component) - self.theme = nil + self.subject = subject + self.source = source + self.order = order self.buttonText = buttonText - self.buttonGloss = buttonGloss + self.buttonGloss = !isPremium - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: context.sharedContext.currentPresentationData.with { $0 })) + super.init(navigationBarPresentationData: nil) self.navigationPresentation = .flatModal self.statusBar.statusBarStyle = .Ignore + + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) } - + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -958,10 +1556,7 @@ public class PremimLimitsListScreen: ViewController { } override open func loadDisplayNode() { - self.displayNode = Node(context: self.context, controller: self, component: self.component, theme: self.theme, buttonTitle: self.buttonText, gloss: self.buttonGloss) - if self.isInitiallyExpanded { - (self.displayNode as! Node).update(isExpanded: true, transition: .immediate) - } + self.displayNode = Node(context: self.context, controller: self, buttonTitle: self.buttonText, gloss: self.buttonGloss) self.displayNodeDidLoad() self.view.disablesInteractiveModalDismiss = true @@ -991,48 +1586,25 @@ public class PremimLimitsListScreen: ViewController { self.node.updateIsVisible(isVisible: false) } - - override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - var navigationLayout = self.navigationLayout(layout: layout) - var navigationFrame = navigationLayout.navigationFrame - var layout = layout - if case .regular = layout.metrics.widthClass { - 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) - let clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) - navigationFrame.size.width = clipFrame.width - layout.size = clipFrame.size - } - - navigationFrame.size.height = 56.0 - navigationLayout.navigationFrame = navigationFrame - navigationLayout.defaultContentHeight = 56.0 - - layout.statusBarHeight = nil - - self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, transition: transition) - } - 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)) + + self.node.containerLayoutUpdated(layout: layout, transition: Transition(transition)) } } private class FooterNode: ASDisplayNode { private let backgroundNode: NavigationBackgroundNode private let separatorNode: ASDisplayNode + private let coverNode: ASDisplayNode private let buttonNode: SolidRoundedButtonNode + private let pageIndicatorView: ComponentHostView private var theme: PresentationTheme private var validLayout: ContainerViewLayout? + private var currentParams: (CGFloat, Int)? var action: () -> Void = {} @@ -1045,10 +1617,17 @@ private class FooterNode: ASDisplayNode { self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: gloss) self.buttonNode.title = title + self.coverNode = ASDisplayNode() + self.coverNode.backgroundColor = self.theme.list.plainBackgroundColor + + self.pageIndicatorView = ComponentHostView() + self.pageIndicatorView.isUserInteractionEnabled = false + super.init() self.addSubnode(self.backgroundNode) self.addSubnode(self.separatorNode) + self.addSubnode(self.coverNode) self.addSubnode(self.buttonNode) self.updateTheme(theme) @@ -1058,6 +1637,12 @@ private class FooterNode: ASDisplayNode { } } + override func didLoad() { + super.didLoad() + + self.view.addSubview(self.pageIndicatorView) + } + private func updateTheme(_ theme: PresentationTheme) { self.theme = theme self.backgroundNode.updateColor(color: self.theme.rootController.tabBar.backgroundColor, transition: .immediate) @@ -1073,9 +1658,15 @@ private class FooterNode: ASDisplayNode { self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0x0077ff), backgroundColors: backgroundColors, foregroundColor: .white), animated: true) } - func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { - transition.updateAlpha(node: self.backgroundNode, alpha: alpha) - transition.updateAlpha(node: self.separatorNode, alpha: alpha) + func updateCoverAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { + transition.updateAlpha(node: self.coverNode, alpha: alpha) + } + + func updatePosition(_ position: CGFloat, count: Int) { + self.currentParams = (position, count) + if let layout = self.validLayout { + let _ = self.updateLayout(layout: layout, transition: .immediate) + } } func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat { @@ -1087,14 +1678,33 @@ private class FooterNode: ASDisplayNode { let bottomPanelPadding: CGFloat = 12.0 let bottomInset: CGFloat = layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : bottomPanelPadding - let panelHeight: CGFloat = bottomPanelPadding + 50.0 + bottomInset + let panelHeight: CGFloat = bottomPanelPadding + 50.0 + bottomInset + 28.0 let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: panelHeight)) - transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: bottomPanelPadding), size: CGSize(width: buttonWidth, height: buttonHeight))) + transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: 40.0), size: CGSize(width: buttonWidth, height: buttonHeight))) transition.updateFrame(node: self.backgroundNode, frame: panelFrame) self.backgroundNode.update(size: panelFrame.size, transition: transition) - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: panelFrame.width, height: UIScreenPixel))) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: panelFrame.width, height: UIScreenPixel))) + + if let (position, count) = self.currentParams { + let indicatorSize = self.pageIndicatorView.update( + transition: .immediate, + component: AnyComponent( + PageIndicatorComponent( + pageCount: count, + position: position, + inactiveColor: self.theme.list.disclosureArrowColor, + activeColor: UIColor(rgb: 0x7169ff) + ) + ), + environment: {}, + containerSize: layout.size + ) + self.pageIndicatorView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - indicatorSize.width) / 2.0), y: 10.0), size: indicatorSize) + } + + transition.updateFrame(node: self.coverNode, frame: panelFrame) return panelHeight } diff --git a/submodules/PremiumUI/Sources/ScrollComponent.swift b/submodules/PremiumUI/Sources/ScrollComponent.swift index a0797ca1d0..127cacb3c9 100644 --- a/submodules/PremiumUI/Sources/ScrollComponent.swift +++ b/submodules/PremiumUI/Sources/ScrollComponent.swift @@ -20,23 +20,26 @@ final class ScrollChildEnvironment: Equatable { } final class ScrollComponent: Component { - public typealias EnvironmentType = ChildEnvironment + typealias EnvironmentType = ChildEnvironment - public let content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)> - public let contentInsets: UIEdgeInsets - public let contentOffsetUpdated: (_ top: CGFloat, _ bottom: CGFloat) -> Void - public let contentOffsetWillCommit: (UnsafeMutablePointer) -> Void + let content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)> + let contentInsets: UIEdgeInsets + let contentOffsetUpdated: (_ top: CGFloat, _ bottom: CGFloat) -> Void + let contentOffsetWillCommit: (UnsafeMutablePointer) -> Void + let resetScroll: ActionSlot public init( content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>, contentInsets: UIEdgeInsets, contentOffsetUpdated: @escaping (_ top: CGFloat, _ bottom: CGFloat) -> Void, - contentOffsetWillCommit: @escaping (UnsafeMutablePointer) -> Void + contentOffsetWillCommit: @escaping (UnsafeMutablePointer) -> Void, + resetScroll: ActionSlot = ActionSlot() ) { self.content = content self.contentInsets = contentInsets self.contentOffsetUpdated = contentOffsetUpdated self.contentOffsetWillCommit = contentOffsetWillCommit + self.resetScroll = resetScroll } public static func ==(lhs: ScrollComponent, rhs: ScrollComponent) -> Bool { @@ -46,7 +49,6 @@ final class ScrollComponent: Component { if lhs.contentInsets != rhs.contentInsets { return false } - return true } @@ -107,6 +109,10 @@ final class ScrollComponent: Component { ) transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil) + component.resetScroll.connect { [weak self] _ in + self?.setContentOffset(.zero, animated: false) + } + if self.contentSize != contentSize { self.ignoreDidScroll = true self.contentSize = contentSize diff --git a/submodules/TextFormat/Sources/GenerateTextEntities.swift b/submodules/TextFormat/Sources/GenerateTextEntities.swift index 87bc82400d..f30fb928c5 100644 --- a/submodules/TextFormat/Sources/GenerateTextEntities.swift +++ b/submodules/TextFormat/Sources/GenerateTextEntities.swift @@ -8,7 +8,8 @@ private let whitelistedHosts: Set = Set([ "t.me", "telegram.me", "telegra.ph", - "telesco.pe" + "telesco.pe", + "fragment.com" ]) private let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue) diff --git a/submodules/UrlWhitelist/Sources/UrlWhitelist.swift b/submodules/UrlWhitelist/Sources/UrlWhitelist.swift index 4321d495b8..2b76a6938d 100644 --- a/submodules/UrlWhitelist/Sources/UrlWhitelist.swift +++ b/submodules/UrlWhitelist/Sources/UrlWhitelist.swift @@ -4,7 +4,8 @@ private let whitelistedHosts: Set = Set([ "t.me", "telegram.me", "telegra.ph", - "telesco.pe" + "telesco.pe", + "fragment.com" ]) public func isConcealedUrlWhitelisted(_ url: URL) -> Bool {