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 } }