Various improvements

This commit is contained in:
Ilya Laktyushin 2022-06-07 22:12:20 +04:00
parent 29b05297a7
commit 3abd6dec78
18 changed files with 298 additions and 119 deletions

View File

@ -106,14 +106,19 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
let initialVelocity = !delta.isZero ? velocity.y / delta : 0.0 let initialVelocity = !delta.isZero ? velocity.y / delta : 0.0
targetContentOffset.pointee = scrollView.contentOffset let currentContentOffset = scrollView.contentOffset
targetContentOffset.pointee = currentContentOffset
if velocity.y > 300.0 { if velocity.y > 300.0 {
self.animateOut(initialVelocity: initialVelocity, completion: { self.animateOut(initialVelocity: initialVelocity, completion: {
self.dismiss?(false) self.dismiss?(false)
}) })
} else { } else {
if contentOffset < scrollView.contentSize.height * 0.333 { if contentOffset < scrollView.contentSize.height * 0.1 {
if contentOffset < 0.0 {
} else {
scrollView.setContentOffset(CGPoint(x: 0.0, y: scrollView.contentSize.height - scrollView.contentInset.top), animated: true) scrollView.setContentOffset(CGPoint(x: 0.0, y: scrollView.contentSize.height - scrollView.contentInset.top), animated: true)
}
} else { } else {
self.animateOut(initialVelocity: initialVelocity, completion: { self.animateOut(initialVelocity: initialVelocity, completion: {
self.dismiss?(false) self.dismiss?(false)

View File

@ -187,9 +187,9 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
let transactionState: TransactionState? let transactionState: TransactionState?
switch transaction.transactionState { switch transaction.transactionState {
case .purchased: case .purchased:
if transaction.original == nil { let transactionIdentifier = transaction.original?.transactionIdentifier ?? transaction.transactionIdentifier
transactionState = .purchased(transactionId: transaction.transactionIdentifier) transactionState = .purchased(transactionId: transactionIdentifier)
if let transactionIdentifier = transaction.transactionIdentifier { if let transactionIdentifier = transactionIdentifier {
self.disposableSet.set( self.disposableSet.set(
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: false).start(error: { _ in self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: false).start(error: { _ in
queue.finishTransaction(transaction) queue.finishTransaction(transaction)
@ -199,13 +199,10 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
forKey: transaction.transactionIdentifier ?? "" forKey: transaction.transactionIdentifier ?? ""
) )
} }
} else {
transactionState = nil
queue.finishTransaction(transaction)
}
case .restored: case .restored:
transactionState = .restored(transactionId: transaction.original?.transactionIdentifier) let transactionIdentifier = transaction.original?.transactionIdentifier ?? transaction.transactionIdentifier
if let transactionIdentifier = transaction.original?.transactionIdentifier { transactionState = .restored(transactionId: transactionIdentifier)
if let transactionIdentifier = transactionIdentifier {
self.disposableSet.set( self.disposableSet.set(
self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: true).start(error: { _ in self.engine.payments.assignAppStoreTransaction(transactionId: transactionIdentifier, receipt: getReceiptData() ?? Data(), restore: true).start(error: { _ in
queue.finishTransaction(transaction) queue.finishTransaction(transaction)

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -12,6 +12,7 @@ import RadialStatusNode
import UniversalMediaPlayer import UniversalMediaPlayer
import TelegramUniversalVideoContent import TelegramUniversalVideoContent
import AppBundle import AppBundle
import ShimmerEffect
private let phoneSize = CGSize(width: 262.0, height: 539.0) private let phoneSize = CGSize(width: 262.0, height: 539.0)
private var phoneBorderImage = { private var phoneBorderImage = {
@ -36,14 +37,46 @@ private var phoneBorderImage = {
}) })
}() }()
private var phoneBorderMaskImage = {
generateImage(phoneSize, rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(2.0)
context.translateBy(x: 12.0, y: 12.0 - UIScreenPixel)
try? drawSvgPath(context, path: "M1.17188,47.3156 C1.17188,39.1084 1.17265,33.013 1.56706,28.1857 C1.96052,23.3701 2.74071,19.9044 4.25094,16.9404 C6.95936,11.6248 11.2811,7.30311 16.5966,4.59469 C19.5606,3.08446 23.0263,2.30427 27.842,1.91081 C32.6693,1.5164 38.7646,1.51562 46.9719,1.51562 H64.6745 H64.6803 L64.8409,1.51754 C64.8419,1.51756 64.8429,1.51758 64.8439,1.5176 C66.0418,1.53925 66.7261,1.73731 67.3042,2.04519 L67.7736,1.16377 L67.3042,2.04519 C67.9232,2.37486 68.4036,2.8529 68.7364,3.47024 C69.0069,3.97209 69.1915,4.54972 69.2551,5.46352 C69.3102,10.9333 69.9419,13.1793 71.16,15.457 C72.4216,17.816 74.2789,19.6733 76.6379,20.9349 C79.0269,22.2126 81.3803,22.8438 87.4372,22.8438 H150.565 C156.622,22.8438 158.976,22.2126 161.364,20.9349 C163.723,19.6733 165.581,17.816 166.842,15.457 C168.061,13.1793 168.692,10.9334 168.747,5.46231 C168.811,4.54985 168.995,3.97217 169.266,3.47025 C169.599,2.8529 170.079,2.37486 170.698,2.04519 C171.276,1.7373 171.961,1.53925 173.159,1.5176 C173.16,1.51758 173.161,1.51756 173.162,1.51754 L173.322,1.51562 H173.328 H191.028 C199.235,1.51562 205.331,1.5164 210.158,1.91081 C214.974,2.30427 218.439,3.08446 221.403,4.59469 C226.719,7.30311 231.041,11.6248 233.749,16.9404 C235.259,19.9044 236.039,23.3701 236.433,28.1857 C236.827,33.013 236.828,39.1084 236.828,47.3156 V468.028 C236.828,476.235 236.827,482.331 236.433,487.158 C236.039,491.974 235.259,495.439 233.749,498.403 C231.041,503.719 226.719,508.041 221.403,510.749 C218.439,512.259 214.974,513.039 210.158,513.433 C205.331,513.827 199.235,513.828 191.028,513.828 H46.9719 C38.7646,513.828 32.6693,513.827 27.842,513.433 C23.0263,513.039 19.5606,512.259 16.5966,510.749 C11.2811,508.041 6.95936,503.719 4.25094,498.403 C2.74071,495.439 1.96052,491.974 1.56706,487.158 C1.17265,482.331 1.17188,476.235 1.17188,468.028 V47.3156 S ")
})
}()
private var starMaskImage = {
return generateImage(CGSize(width: 88.0, height: 84.0), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
try? drawSvgPath(context, path: "M41.7419,71.1897 L22.1639,83.1833 C20.1282,84.4304 17.4669,83.7911 16.2198,81.7553 C15.6107,80.7611 15.4291,79.5629 15.7162,78.4328 L18.7469,66.504 C19.8409,62.1979 22.7876,58.5983 26.7928,56.6754 L48.1514,46.4207 C49.1472,45.9426 49.5668,44.7479 49.0887,43.7521 C48.7016,42.9457 47.826,42.4945 46.9446,42.6471 L23.1697,46.7631 C18.3368,47.5998 13.3807,46.2653 9.62146,43.1149 L2.11077,36.8207 C0.28097,35.2873 0.0407101,32.5609 1.57413,30.7311 C2.31994,29.8411 3.39241,29.2886 4.55001,29.198 L27.4974,27.4022 C29.1186,27.2753 30.5314,26.2494 31.1537,24.747 L40.0064,3.37722 C40.9201,1.17161 43.4488,0.124313 45.6544,1.03801 C46.7135,1.47673 47.5549,2.31816 47.9936,3.37722 L56.8463,24.747 C57.4686,26.2494 58.8815,27.2753 60.5026,27.4022 L83.5761,29.2079 C85.9562,29.3942 87.7347,31.4746 87.5484,33.8547 C87.4588,34.9997 86.9172,36.0619 86.0433,36.807 L68.4461,51.809 C67.2073,52.8651 66.6669,54.5275 67.0478,56.1102 L72.4577,78.5841 C73.0165,80.9052 71.5878,83.2397 69.2667,83.7985 C68.1515,84.0669 66.9752,83.8811 65.997,83.2818 L46.2581,71.1897 C44.8724,70.3408 43.1277,70.3408 41.7419,71.1897 Z ")
})
}()
private final class PhoneView: UIView { private final class PhoneView: UIView {
let contentContainerView: UIView let contentContainerView: UIView
let overlayView: UIView let overlayView: UIView
let borderView: UIImageView let borderView: UIImageView
let backShimmerView: UIView
let backShimmerEffectView: ShimmerEffectForegroundView
let backShimmerFadeView: UIView
let frontShimmerView: UIView
let shimmerEffectView: ShimmerEffectForegroundView
let shimmerMaskView: UIView
let shimmerBorderView: UIImageView
let shimmerStarView: UIImageView
fileprivate var videoNode: UniversalVideoNode? fileprivate var videoNode: UniversalVideoNode?
private let statusNode: RadialStatusNode
var playbackStatus: Signal<MediaPlayerStatus?, NoError> { var playbackStatus: Signal<MediaPlayerStatus?, NoError> {
return self.playbackStatusPromise.get() return self.playbackStatusPromise.get()
@ -75,16 +108,43 @@ private final class PhoneView: UIView {
self.borderView = UIImageView(image: phoneBorderImage) self.borderView = UIImageView(image: phoneBorderImage)
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6), enableBlur: false) self.shimmerMaskView = UIView()
self.statusNode.transitionToState(.none) self.shimmerBorderView = UIImageView(image: phoneBorderMaskImage)
self.statusNode.isUserInteractionEnabled = false self.shimmerStarView = UIImageView(image: starMaskImage)
self.backShimmerView = UIView()
self.backShimmerView.alpha = 0.0
self.backShimmerEffectView = ShimmerEffectForegroundView()
self.backShimmerFadeView = UIView()
self.backShimmerFadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.2)
self.frontShimmerView = UIView()
self.frontShimmerView.alpha = 0.0
self.shimmerEffectView = ShimmerEffectForegroundView()
super.init(frame: frame) super.init(frame: frame)
self.addSubview(self.contentContainerView) self.addSubview(self.contentContainerView)
self.contentContainerView.addSubview(self.statusNode.view)
self.contentContainerView.addSubview(self.overlayView) self.contentContainerView.addSubview(self.overlayView)
self.contentContainerView.addSubview(self.backShimmerView)
self.addSubview(self.borderView) self.addSubview(self.borderView)
self.addSubview(self.frontShimmerView)
self.backShimmerView.addSubview(self.backShimmerEffectView)
self.backShimmerView.addSubview(self.backShimmerFadeView)
self.shimmerMaskView.addSubview(self.shimmerBorderView)
self.shimmerMaskView.addSubview(self.shimmerStarView)
self.frontShimmerView.mask = self.shimmerMaskView
self.frontShimmerView.addSubview(self.shimmerEffectView)
self.backShimmerEffectView.update(backgroundColor: .clear, foregroundColor: UIColor.white.withAlphaComponent(0.35), gradientSize: 70.0, globalTimeOffset: true, duration: 3.0, horizontal: true)
self.backShimmerEffectView.layer.compositingFilter = "overlayBlendMode"
self.shimmerEffectView.update(backgroundColor: .clear, foregroundColor: UIColor.white.withAlphaComponent(0.65), gradientSize: 70.0, globalTimeOffset: true, duration: 3.0, horizontal: true)
self.shimmerEffectView.layer.compositingFilter = "overlayBlendMode"
} }
deinit { deinit {
@ -143,22 +203,23 @@ private final class PhoneView: UIView {
} }
private func updatePlaybackStatus() { private func updatePlaybackStatus() {
var state: RadialStatusNodeState? var isDisplayingProgress = false
if let playbackStatus = self.playbackStatusValue { if let playbackStatus = self.playbackStatusValue {
if case let .buffering(initial, _, progress, _) = playbackStatus.status, initial || !progress.isZero { if case let .buffering(initial, _, progress, _) = playbackStatus.status, initial || !progress.isZero {
let adjustedProgress = max(progress, 0.027) isDisplayingProgress = true
state = .progress(color: .white, lineWidth: nil, value: CGFloat(adjustedProgress), cancelEnabled: false, animateRotation: true)
} else if playbackStatus.status == .playing { } else if playbackStatus.status == .playing {
state = RadialStatusNodeState.none isDisplayingProgress = false
} }
} }
if let state = state { let targetAlpha = isDisplayingProgress ? 1.0 : 0.0
self.statusNode.transitionToState(state, completion: { [weak self] in if self.frontShimmerView.alpha != targetAlpha {
if case .none = state { let sourceAlpha = self.frontShimmerView.alpha
self?.statusNode.removeFromSupernode() self.frontShimmerView.alpha = targetAlpha
} self.frontShimmerView.layer.animateAlpha(from: sourceAlpha, to: targetAlpha, duration: 0.2)
})
self.backShimmerView.alpha = targetAlpha
self.backShimmerView.layer.animateAlpha(from: sourceAlpha, to: targetAlpha, duration: 0.2)
} }
} }
@ -186,24 +247,40 @@ private final class PhoneView: UIView {
super.layoutSubviews() super.layoutSubviews()
if let phoneImage = self.borderView.image { if let phoneImage = self.borderView.image {
self.borderView.frame = CGRect(origin: .zero, size: phoneImage.size) let phoneBounds = CGRect(origin: .zero, size: phoneImage.size)
self.borderView.frame = phoneBounds
self.contentContainerView.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: CGSize(width: phoneImage.size.width - 24.0, height: phoneImage.size.height - 24.0)) self.contentContainerView.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: CGSize(width: phoneImage.size.width - 24.0, height: phoneImage.size.height - 24.0))
self.overlayView.frame = self.contentContainerView.bounds self.overlayView.frame = self.contentContainerView.bounds
if let videoNode = self.videoNode {
let videoSize = CGSize(width: self.contentContainerView.frame.width, height: 354.0) let videoSize = CGSize(width: self.contentContainerView.frame.width, height: 354.0)
if let videoNode = self.videoNode {
videoNode.view.frame = CGRect(origin: CGPoint(x: 0.0, y: self.position == .top ? 0.0 : self.contentContainerView.frame.height - videoSize.height), size: videoSize) videoNode.view.frame = CGRect(origin: CGPoint(x: 0.0, y: self.position == .top ? 0.0 : self.contentContainerView.frame.height - videoSize.height), size: videoSize)
videoNode.updateLayout(size: videoSize, transition: .immediate) videoNode.updateLayout(size: videoSize, transition: .immediate)
}
self.backShimmerView.frame = phoneBounds.insetBy(dx: -12.0, dy: -12.0)
self.backShimmerEffectView.frame = phoneBounds
self.backShimmerFadeView.frame = phoneBounds
self.frontShimmerView.frame = phoneBounds
self.shimmerEffectView.frame = phoneBounds
self.shimmerMaskView.frame = phoneBounds
self.shimmerBorderView.frame = phoneBounds
self.backShimmerEffectView.updateAbsoluteRect(CGRect(origin: CGPoint(x: phoneBounds.width * 12.0, y: 0.0), size: phoneBounds.size), within: CGSize(width: phoneBounds.width * 25.0, height: phoneBounds.height))
self.shimmerEffectView.updateAbsoluteRect(CGRect(origin: CGPoint(x: phoneBounds.width * 12.0, y: 0.0), size: phoneBounds.size), within: CGSize(width: phoneBounds.width * 25.0, height: phoneBounds.height))
let notchHeight: CGFloat = 20.0 let notchHeight: CGFloat = 20.0
let radialStatusSize: CGFloat = 40.0 if let starImage = self.shimmerStarView.image {
self.statusNode.frame = CGRect(x: floor((videoSize.width - radialStatusSize) / 2.0), y: self.position == .top ? notchHeight + floor((videoSize.height - notchHeight - radialStatusSize) / 2.0) : self.contentContainerView.frame.height - videoSize.height + floor((videoSize.height - radialStatusSize) / 2.0), width: radialStatusSize, height: radialStatusSize) let starSize = starImage.size
self.shimmerStarView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((phoneImage.size.width - starSize.width) / 2.0), y: self.position == .top ? notchHeight + floor((videoSize.height - notchHeight - starSize.height) / 2.0) : self.contentContainerView.frame.height - videoSize.height + floor((videoSize.height - starSize.height) / 2.0)), size: starSize)
} }
} }
} }
} }
private final class StarsView: UIView { private final class FasterStarsView: UIView {
private let sceneView: SCNView private let sceneView: SCNView
override init(frame: CGRect) { override init(frame: CGRect) {
@ -289,6 +366,41 @@ private final class StarsView: UIView {
} }
} }
private final class BadgeStarsView: UIView {
private let sceneView: SCNView
override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "badge", withExtension: "scn") {
self.sceneView.scene = try? SCNScene(url: url, options: nil)
}
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
super.init(frame: frame)
self.alpha = 0.0
self.addSubview(self.sceneView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setVisible(_ visible: Bool) {
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.75 : 0.0)
}
override func layoutSubviews() {
super.layoutSubviews()
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
}
}
final class PhoneDemoComponent: Component { final class PhoneDemoComponent: Component {
typealias EnvironmentType = DemoPageEnvironment typealias EnvironmentType = DemoPageEnvironment
@ -297,21 +409,27 @@ final class PhoneDemoComponent: Component {
case bottom case bottom
} }
enum BackgroundDecoration {
case none
case fasterStars
case badgeStars
}
let context: AccountContext let context: AccountContext
let position: Position let position: Position
let videoFile: TelegramMediaFile? let videoFile: TelegramMediaFile?
let hasStars: Bool let decoration: BackgroundDecoration
public init( public init(
context: AccountContext, context: AccountContext,
position: PhoneDemoComponent.Position, position: PhoneDemoComponent.Position,
videoFile: TelegramMediaFile?, videoFile: TelegramMediaFile?,
hasStars: Bool = false decoration: BackgroundDecoration = .none
) { ) {
self.context = context self.context = context
self.position = position self.position = position
self.videoFile = videoFile self.videoFile = videoFile
self.hasStars = hasStars self.decoration = decoration
} }
public static func ==(lhs: PhoneDemoComponent, rhs: PhoneDemoComponent) -> Bool { public static func ==(lhs: PhoneDemoComponent, rhs: PhoneDemoComponent) -> Bool {
@ -324,7 +442,7 @@ final class PhoneDemoComponent: Component {
if lhs.videoFile != rhs.videoFile { if lhs.videoFile != rhs.videoFile {
return false return false
} }
if lhs.hasStars != rhs.hasStars { if lhs.decoration != rhs.decoration {
return false return false
} }
return true return true
@ -346,9 +464,11 @@ final class PhoneDemoComponent: Component {
private let starsContainerView: UIView private let starsContainerView: UIView
private let containerView: UIView private let containerView: UIView
private var starsView: StarsView?
private let phoneView: PhoneView private let phoneView: PhoneView
private var fasterStarsView: FasterStarsView?
private var badgeStarsView: BadgeStarsView?
private var starsDisposable: Disposable? private var starsDisposable: Disposable?
public var ready: Signal<Bool, NoError> { public var ready: Signal<Bool, NoError> {
@ -393,26 +513,32 @@ final class PhoneDemoComponent: Component {
self.starsContainerView.frame = CGRect(origin: CGPoint(x: -availableSize.width * 0.5, y: 0.0), size: CGSize(width: availableSize.width * 2.0, height: availableSize.height)) self.starsContainerView.frame = CGRect(origin: CGPoint(x: -availableSize.width * 0.5, y: 0.0), size: CGSize(width: availableSize.width * 2.0, height: availableSize.height))
self.phoneView.bounds = CGRect(origin: .zero, size: phoneSize) self.phoneView.bounds = CGRect(origin: .zero, size: phoneSize)
if component.hasStars { switch component.decoration {
if self.starsView == nil { case .none:
let starsView = StarsView(frame: self.starsContainerView.bounds) break
self.starsView = starsView case .fasterStars:
if self.fasterStarsView == nil {
let starsView = FasterStarsView(frame: self.starsContainerView.bounds)
self.fasterStarsView = starsView
self.starsContainerView.addSubview(starsView) self.starsContainerView.addSubview(starsView)
self.starsDisposable = (self.phoneView.playbackStatus self.starsDisposable = (self.phoneView.playbackStatus
|> deliverOnMainQueue).start(next: { [weak self] status in |> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self, let status = status { if let strongSelf = self, let status = status {
if status.timestamp > 8.0 { if status.timestamp > 8.0 {
strongSelf.starsView?.stopAnimation() strongSelf.fasterStarsView?.stopAnimation()
} else if status.timestamp > 0.85 { } else if status.timestamp > 0.85 {
strongSelf.starsView?.startAnimation() strongSelf.fasterStarsView?.startAnimation()
} }
} }
}) })
} }
} else if let starsView = self.starsView { case .badgeStars:
self.starsView = nil if self.badgeStarsView == nil {
starsView.removeFromSuperview() let starsView = BadgeStarsView(frame: self.starsContainerView.bounds)
self.badgeStarsView = starsView
self.starsContainerView.addSubview(starsView)
}
} }
self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position) self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position)
@ -435,7 +561,8 @@ final class PhoneDemoComponent: Component {
let isCentral = environment[DemoPageEnvironment.self].isCentral let isCentral = environment[DemoPageEnvironment.self].isCentral
self.isCentral = isCentral self.isCentral = isCentral
self.starsView?.setVisible(isVisible && abs(mappedPosition) < 0.4) self.fasterStarsView?.setVisible(isVisible && abs(mappedPosition) < 0.4)
self.badgeStarsView?.setVisible(isVisible && abs(mappedPosition) < 0.4)
self.phoneView.center = CGPoint(x: availableSize.width / 2.0 + phoneX, y: phoneY) self.phoneView.center = CGPoint(x: availableSize.width / 2.0 + phoneX, y: phoneY)
self.phoneView.screenRotation = mappedPosition * -0.7 self.phoneView.screenRotation = mappedPosition * -0.7
@ -448,7 +575,7 @@ final class PhoneDemoComponent: Component {
self.phoneView.play() self.phoneView.play()
} else if !isVisible { } else if !isVisible {
self.phoneView.reset() self.phoneView.reset()
self.starsView?.stopAnimation() self.fasterStarsView?.stopAnimation()
} }
if let _ = transition.userData(DemoAnimateInTransition.self), abs(mappedPosition) < .ulpOfOne { if let _ = transition.userData(DemoAnimateInTransition.self), abs(mappedPosition) < .ulpOfOne {

View File

@ -341,12 +341,15 @@ private final class DemoPagerComponent: Component {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
private var ignoreContentOffsetChange = false
public func scrollViewDidScroll(_ scrollView: UIScrollView) { public func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let component = self.component else { guard let component = self.component, !self.ignoreContentOffsetChange else {
return return
} }
self.ignoreContentOffsetChange = true
let _ = self.update(component: component, availableSize: self.bounds.size, transition: .immediate) let _ = self.update(component: component, availableSize: self.bounds.size, transition: .immediate)
self.ignoreContentOffsetChange = false
} }
func update(component: DemoPagerComponent, availableSize: CGSize, transition: Transition) -> CGSize { func update(component: DemoPagerComponent, availableSize: CGSize, transition: Transition) -> CGSize {
@ -652,7 +655,7 @@ private final class DemoSheetContent: CombinedComponent {
context: component.context, context: component.context,
position: .top, position: .top,
videoFile: configuration.videos["faster_download"], videoFile: configuration.videos["faster_download"],
hasStars: true decoration: .fasterStars
)), )),
title: strings.Premium_FasterSpeed, title: strings.Premium_FasterSpeed,
text: strings.Premium_FasterSpeedInfo, text: strings.Premium_FasterSpeedInfo,
@ -757,7 +760,8 @@ private final class DemoSheetContent: CombinedComponent {
content: AnyComponent(PhoneDemoComponent( content: AnyComponent(PhoneDemoComponent(
context: component.context, context: component.context,
position: .top, position: .top,
videoFile: configuration.videos["profile_badge"] videoFile: configuration.videos["profile_badge"],
decoration: .badgeStars
)), )),
title: strings.Premium_Badge, title: strings.Premium_Badge,
text: strings.Premium_BadgeInfo, text: strings.Premium_BadgeInfo,

View File

@ -1190,10 +1190,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
tapAction: { attributes, _ in tapAction: { attributes, _ in
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
let controller = environment.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController { let controller = environment.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
if url.hasPrefix("https://") { if url.hasPrefix("https://apps.apple.com/account/subscriptions") {
controller.context.sharedContext.applicationBindings.openSubscriptions()
} else if url.hasPrefix("https://") {
controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: true, presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {}) controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: true, presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {})
} else if url == "cancel" {
} else { } else {
let context = controller.context let context = controller.context
let signal: Signal<ResolvedUrl, NoError>? let signal: Signal<ResolvedUrl, NoError>?

View File

@ -796,7 +796,7 @@ private final class LimitSheetContent: CombinedComponent {
} }
case .accounts: case .accounts:
let limit = 3 let limit = 3
let premiumLimit = component.count + 1 let premiumLimit = limit + 1
iconName = "Premium/Account" iconName = "Premium/Account"
badgeText = "\(component.count)" badgeText = "\(component.count)"
string = component.count >= premiumLimit ? strings.Premium_MaxAccountsFinalText("\(premiumLimit)").string : strings.Premium_MaxAccountsText("\(limit)").string string = component.count >= premiumLimit ? strings.Premium_MaxAccountsFinalText("\(premiumLimit)").string : strings.Premium_MaxAccountsText("\(limit)").string
@ -805,7 +805,7 @@ private final class LimitSheetContent: CombinedComponent {
if component.count == limit { if component.count == limit {
badgePosition = 0.5 badgePosition = 0.5
} else { } else {
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit) badgePosition = min(1.0, CGFloat(component.count) / CGFloat(premiumLimit))
} }
buttonAnimationName = "premium_addone" buttonAnimationName = "premium_addone"

View File

@ -427,10 +427,17 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
) )
} }
private var scrollStartPosition: (contentOffset: CGFloat, position: CGFloat)? private var scrollStartPosition: (contentOffset: CGFloat, position: CGFloat, inverse: Bool)?
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if self.scrollStartPosition == nil { var inverse = false
self.scrollStartPosition = (scrollView.contentOffset.x, self.currentPosition) let tapLocation = scrollView.panGestureRecognizer.location(in: scrollView)
if tapLocation.y < scrollView.frame.height / 2.0 {
inverse = true
}
if let scrollStartPosition = self.scrollStartPosition {
self.scrollStartPosition = (scrollStartPosition.contentOffset, scrollStartPosition.position, inverse)
} else {
self.scrollStartPosition = (scrollView.contentOffset.x, self.currentPosition, inverse)
} }
} }
@ -445,12 +452,15 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
self.animator = nil self.animator = nil
} }
guard !self.ignoreContentOffsetChange, let (startContentOffset, startPosition) = self.scrollStartPosition else { guard !self.ignoreContentOffsetChange, let (startContentOffset, startPosition, inverse) = self.scrollStartPosition else {
return return
} }
let delta = scrollView.contentOffset.x - startContentOffset let delta = scrollView.contentOffset.x - startContentOffset
let positionDelta = delta * -0.001 var positionDelta = delta * -0.001
if inverse {
positionDelta *= -1.0
}
var updatedPosition = startPosition + positionDelta var updatedPosition = startPosition + positionDelta
while updatedPosition >= 1.0 { while updatedPosition >= 1.0 {
updatedPosition -= 1.0 updatedPosition -= 1.0
@ -470,12 +480,14 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
} }
if let size = self.validLayout { if let size = self.validLayout {
self.ignoreContentOffsetChange = true
self.updateLayout(size: size, transition: .immediate) self.updateLayout(size: size, transition: .immediate)
self.ignoreContentOffsetChange = false
} }
} }
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
guard let (startContentOffset, _) = self.scrollStartPosition, abs(velocity.x) > 0.0 else { guard let (startContentOffset, _, _) = self.scrollStartPosition, abs(velocity.x) > 0.0 else {
return return
} }

View File

@ -80,7 +80,7 @@ final class StickersCarouselComponent: Component {
} }
} }
private let itemSize = CGSize(width: 220.0, height: 220.0) private let itemSize = CGSize(width: 200.0, height: 200.0)
private class StickerNode: ASDisplayNode { private class StickerNode: ASDisplayNode {
private let context: AccountContext private let context: AccountContext
@ -109,7 +109,7 @@ private class StickerNode: ASDisplayNode {
self.animationNode = animationNode self.animationNode = animationNode
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0)) let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 240.0, height: 240.0))
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(fittedDimensions.width * 1.6), height: Int(fittedDimensions.height * 1.6), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix)) animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(fittedDimensions.width * 1.6), height: Int(fittedDimensions.height * 1.6), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
@ -239,7 +239,7 @@ private class StickerNode: ASDisplayNode {
} }
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
let boundingSize = CGSize(width: 240.0, height: 240.0) let boundingSize = itemSize
if let dimensitons = self.file.dimensions { if let dimensitons = self.file.dimensions {
let imageSize = dimensitons.cgSize.aspectFitted(boundingSize) let imageSize = dimensitons.cgSize.aspectFitted(boundingSize)

View File

@ -140,16 +140,18 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
|> deliverOnMainQueue |> deliverOnMainQueue
).start(next: { accountAndPeer, accountsAndPeers in ).start(next: { accountAndPeer, accountsAndPeers in
var maximumAvailableAccounts: Int = 3 var maximumAvailableAccounts: Int = 3
if accountAndPeer?.1.isPremium == true { if accountAndPeer?.1.isPremium == true && !context.account.testingEnvironment {
maximumAvailableAccounts = 4 maximumAvailableAccounts = 4
} }
var count: Int = 1 var count: Int = 1
for (_, peer, _) in accountsAndPeers { for (accountContext, peer, _) in accountsAndPeers {
if !accountContext.account.testingEnvironment {
if peer.isPremium { if peer.isPremium {
maximumAvailableAccounts = 4 maximumAvailableAccounts = 4
} }
count += 1
}
} }
count += accountsAndPeers.count
if count >= maximumAvailableAccounts { if count >= maximumAvailableAccounts {
var replaceImpl: ((ViewController) -> Void)? var replaceImpl: ((ViewController) -> Void)?

View File

@ -186,7 +186,8 @@ private func quickReactionSetupControllerEntries(
availableReactions: AvailableReactions?, availableReactions: AvailableReactions?,
images: [String: (image: UIImage, isAnimation: Bool)], images: [String: (image: UIImage, isAnimation: Bool)],
reactionSettings: ReactionSettings, reactionSettings: ReactionSettings,
state: QuickReactionSetupControllerState state: QuickReactionSetupControllerState,
isPremium: Bool
) -> [QuickReactionSetupControllerEntry] { ) -> [QuickReactionSetupControllerEntry] {
var entries: [QuickReactionSetupControllerEntry] = [] var entries: [QuickReactionSetupControllerEntry] = []
@ -210,6 +211,10 @@ private func quickReactionSetupControllerEntries(
continue continue
} }
if !isPremium && availableReaction.isPremium {
continue
}
entries.append(.item( entries.append(.item(
index: index, index: index,
value: availableReaction.value, value: availableReaction.value,
@ -332,10 +337,12 @@ public func quickReactionSetupController(
statePromise.get(), statePromise.get(),
context.engine.stickers.availableReactions(), context.engine.stickers.availableReactions(),
settings, settings,
images images,
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
) )
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, availableReactions, settings, images -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, availableReactions, settings, images, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
let isPremium = accountPeer?.isPremium ?? false
let title: String = presentationData.strings.Settings_QuickReactionSetup_Title let title: String = presentationData.strings.Settings_QuickReactionSetup_Title
let entries = quickReactionSetupControllerEntries( let entries = quickReactionSetupControllerEntries(
@ -343,7 +350,8 @@ public func quickReactionSetupController(
availableReactions: availableReactions, availableReactions: availableReactions,
images: images, images: images,
reactionSettings: settings, reactionSettings: settings,
state: state state: state,
isPremium: isPremium
) )
let controllerState = ItemListControllerState( let controllerState = ItemListControllerState(

View File

@ -800,10 +800,6 @@ public final class SolidRoundedButtonView: UIView {
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
self.buttonBackgroundNode.layer.cornerCurve = .continuous self.buttonBackgroundNode.layer.cornerCurve = .continuous
} }
if gloss {
self.setupGloss()
}
} }
required public init(coder: NSCoder) { required public init(coder: NSCoder) {
@ -1004,8 +1000,10 @@ public final class SolidRoundedButtonView: UIView {
compositingFilter = nil compositingFilter = nil
} }
shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, globalTimeOffset: false, duration: 3.0, horizontal: true) let globalTimeOffset = self.icon == nil
borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, globalTimeOffset: false, duration: 3.0, horizontal: true)
shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, globalTimeOffset: globalTimeOffset, duration: 3.0, horizontal: true)
borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, globalTimeOffset: globalTimeOffset, duration: 3.0, horizontal: true)
shimmerView.layer.compositingFilter = compositingFilter shimmerView.layer.compositingFilter = compositingFilter
borderShimmerView.layer.compositingFilter = compositingFilter borderShimmerView.layer.compositingFilter = compositingFilter
@ -1050,6 +1048,8 @@ public final class SolidRoundedButtonView: UIView {
private func updateLayout(width: CGFloat, previousSubtitle: String?, transition: ContainedViewLayoutTransition) -> CGFloat { private func updateLayout(width: CGFloat, previousSubtitle: String?, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = width self.validLayout = width
self.setupGloss()
let buttonSize = CGSize(width: width, height: self.buttonHeight) let buttonSize = CGSize(width: width, height: self.buttonHeight)
let buttonFrame = CGRect(origin: CGPoint(), size: buttonSize) let buttonFrame = CGRect(origin: CGPoint(), size: buttonSize)
transition.updateFrame(view: self.buttonBackgroundNode, frame: buttonFrame) transition.updateFrame(view: self.buttonBackgroundNode, frame: buttonFrame)

View File

@ -232,12 +232,12 @@ public struct PresentationResourcesChatList {
context.saveGState() context.saveGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage) context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
context.setFillColor(theme.chatList.unreadBadgeActiveBackgroundColor.cgColor) context.setFillColor(theme.list.itemCheckColors.fillColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size)) context.fill(CGRect(origin: CGPoint(), size: size))
context.restoreGState() context.restoreGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage) context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
context.setFillColor(theme.chatList.unreadBadgeActiveTextColor.cgColor) context.setFillColor(theme.list.itemCheckColors.foregroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size)) context.fill(CGRect(origin: CGPoint(), size: size))
} }
}, opaque: false) }, opaque: false)
@ -255,7 +255,7 @@ public struct PresentationResourcesChatList {
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage) context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
context.setFillColor(theme.chatList.unreadBadgeActiveBackgroundColor.cgColor) context.setFillColor(theme.list.itemCheckColors.fillColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size)) context.fill(CGRect(origin: CGPoint(), size: size))
} }
}, opaque: false) }, opaque: false)

View File

@ -638,6 +638,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
} }
if let starStatus = data.starStatus { if let starStatus = data.starStatus {
var isPremiumSticker = false
for media in messages[0].media {
if let file = media as? TelegramMediaFile, file.isPremiumSticker {
isPremiumSticker = true
break
}
}
if !isPremiumSticker || chatPresentationInterfaceState.isPremium {
actions.append(.action(ContextMenuActionItem(text: starStatus ? chatPresentationInterfaceState.strings.Stickers_RemoveFromFavorites : chatPresentationInterfaceState.strings.Stickers_AddToFavorites, icon: { theme in actions.append(.action(ContextMenuActionItem(text: starStatus ? chatPresentationInterfaceState.strings.Stickers_RemoveFromFavorites : chatPresentationInterfaceState.strings.Stickers_AddToFavorites, icon: { theme in
return generateTintedImage(image: starStatus ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: starStatus ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
@ -645,6 +653,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
f(.default) f(.default)
}))) })))
} }
}
if data.messageActions.options.contains(.rateCall) { if data.messageActions.options.contains(.rateCall) {
var callId: CallId? var callId: CallId?

View File

@ -485,6 +485,7 @@ private final class PeerInfoInteraction {
let openAddMember: () -> Void let openAddMember: () -> Void
let openQrCode: () -> Void let openQrCode: () -> Void
let editingOpenReactionsSetup: () -> Void let editingOpenReactionsSetup: () -> Void
let dismissInput: () -> Void
init( init(
openUsername: @escaping (String) -> Void, openUsername: @escaping (String) -> Void,
@ -527,7 +528,8 @@ private final class PeerInfoInteraction {
openFaq: @escaping (String?) -> Void, openFaq: @escaping (String?) -> Void,
openAddMember: @escaping () -> Void, openAddMember: @escaping () -> Void,
openQrCode: @escaping () -> Void, openQrCode: @escaping () -> Void,
editingOpenReactionsSetup: @escaping () -> Void editingOpenReactionsSetup: @escaping () -> Void,
dismissInput: @escaping () -> Void
) { ) {
self.openUsername = openUsername self.openUsername = openUsername
self.openPhone = openPhone self.openPhone = openPhone
@ -570,6 +572,7 @@ private final class PeerInfoInteraction {
self.openAddMember = openAddMember self.openAddMember = openAddMember
self.openQrCode = openQrCode self.openQrCode = openQrCode
self.editingOpenReactionsSetup = editingOpenReactionsSetup self.editingOpenReactionsSetup = editingOpenReactionsSetup
self.dismissInput = dismissInput
} }
} }
@ -817,6 +820,8 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
if let cachedData = data.cachedData as? CachedUserData { if let cachedData = data.cachedData as? CachedUserData {
items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: state.updatingBio ?? (cachedData.about ?? ""), placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: state.updatingBio ?? (cachedData.about ?? ""), placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in
interaction.updateBio(updatedText) interaction.updateBio(updatedText)
}, action: {
interaction.dismissInput()
}, maxLength: Int(data.globalSettings?.userLimits.maxAboutLength ?? 70))) }, maxLength: Int(data.globalSettings?.userLimits.maxAboutLength ?? 70)))
items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_Help)) items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_Help))
} }
@ -1838,6 +1843,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, },
editingOpenReactionsSetup: { [weak self] in editingOpenReactionsSetup: { [weak self] in
self?.editingOpenReactionsSetup() self?.editingOpenReactionsSetup()
},
dismissInput: { [weak self] in
self?.view.endEditing(true)
} }
) )
@ -6268,17 +6276,19 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
push(usernameSetupController(context: self.context)) push(usernameSetupController(context: self.context))
case .addAccount: case .addAccount:
var maximumAvailableAccounts: Int = 3 var maximumAvailableAccounts: Int = 3
if self.data?.peer?.isPremium == true { if self.data?.peer?.isPremium == true && !self.context.account.testingEnvironment {
maximumAvailableAccounts = 4 maximumAvailableAccounts = 4
} }
var count: Int = 1 var count: Int = 1
if let settings = self.data?.globalSettings { if let settings = self.data?.globalSettings {
for (_, peer, _) in settings.accountsAndPeers { for (accountContext, peer, _) in settings.accountsAndPeers {
if !accountContext.account.testingEnvironment {
if peer.isPremium { if peer.isPremium {
maximumAvailableAccounts = 4 maximumAvailableAccounts = 4
} }
count += 1
}
} }
count += settings.accountsAndPeers.count
} }
if count >= maximumAvailableAccounts { if count >= maximumAvailableAccounts {
let context = self.context let context = self.context

View File

@ -8,6 +8,7 @@ final class PeerInfoScreenMultilineInputItem: PeerInfoScreenItem {
let text: String let text: String
let placeholder: String let placeholder: String
let textUpdated: (String) -> Void let textUpdated: (String) -> Void
let action: () -> Void
let maxLength: Int? let maxLength: Int?
init( init(
@ -15,12 +16,14 @@ final class PeerInfoScreenMultilineInputItem: PeerInfoScreenItem {
text: String, text: String,
placeholder: String, placeholder: String,
textUpdated: @escaping (String) -> Void, textUpdated: @escaping (String) -> Void,
action: @escaping () -> Void,
maxLength: Int? maxLength: Int?
) { ) {
self.id = id self.id = id
self.text = text self.text = text
self.placeholder = placeholder self.placeholder = placeholder
self.textUpdated = textUpdated self.textUpdated = textUpdated
self.action = action
self.maxLength = maxLength self.maxLength = maxLength
} }
@ -61,8 +64,10 @@ final class PeerInfoScreenMultilineInputItemNode: PeerInfoScreenItemNode {
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let inputItem = ItemListMultilineInputItem(presentationData: ItemListPresentationData(presentationData), text: item.text, placeholder: item.placeholder, maxLength: item.maxLength.flatMap { ItemListMultilineInputItemTextLimit(value: $0, display: true) }, sectionId: 0, style: .blocks, textUpdated: { updatedText in let inputItem = ItemListMultilineInputItem(presentationData: ItemListPresentationData(presentationData), text: item.text, placeholder: item.placeholder, maxLength: item.maxLength.flatMap { ItemListMultilineInputItemTextLimit(value: $0, display: true) }, sectionId: 0, style: .blocks, returnKeyType: .done, textUpdated: { updatedText in
item.textUpdated(updatedText) item.textUpdated(updatedText)
}, action: {
item.action()
}, noInsets: true) }, noInsets: true)
let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0) let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0)