Various improvements

This commit is contained in:
Ilya Laktyushin 2022-05-10 19:32:23 +04:00
parent ff7cc72a71
commit 5e65806ac3
13 changed files with 877 additions and 141 deletions

View File

@ -321,13 +321,18 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
case .limitExceeded:
f(.default)
let limitScreen = PremiumLimitScreen(context: context, subject: .pins, action: {
var dismissImpl: (() -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .pins, action: {
dismissImpl?()
let premiumScreen = PremiumIntroScreen(context: context, action: {
})
chatListController?.push(premiumScreen)
})
chatListController?.push(limitScreen)
chatListController?.push(controller)
dismissImpl = { [weak controller] in
controller?.dismiss()
}
}
})
})))

View File

@ -10,6 +10,7 @@ import ItemListUI
import AccountContext
import ItemListPeerActionItem
import ChatListFilterSettingsHeaderItem
import PremiumUI
private final class ChatListFilterPresetListControllerArguments {
let context: AccountContext
@ -246,6 +247,17 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController) -> Void)?
let filtersWithCountsSignal = context.engine.peers.updatedChatListFilters()
|> distinctUntilChanged
|> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in
return .single(filters.map { filter -> (ChatListFilter, Int) in
return (filter, 0)
})
}
let filtersWithCounts = Promise<[(ChatListFilter, Int)]>()
filtersWithCounts.set(filtersWithCountsSignal)
let arguments = ChatListFilterPresetListControllerArguments(context: context,
addSuggestedPresed: { title, data in
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
@ -259,7 +271,42 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
}, openPreset: { preset in
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: { _ in }))
}, addNew: {
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: { _ in }))
let _ = combineLatest(
queue: Queue.mainQueue(),
context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
),
filtersWithCounts.get() |> take(1)
).start(next: { result, filters in
let (accountPeer, limits, premiumLimits) = result
let limit = limits.maxFoldersCount
let premiumLimit = premiumLimits.maxFoldersCount
if let accountPeer = accountPeer, accountPeer.isPremium {
if filters.count >= premiumLimit {
//printPremiumError
return
}
} else {
if filters.count >= limit {
var dismissImpl: (() -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .folders, action: {
dismissImpl?()
let controller = PremiumIntroScreen(context: context, action: {
})
pushControllerImpl?(controller)
})
pushControllerImpl?(controller)
dismissImpl = { [weak controller] in
controller?.dismiss()
}
return
}
}
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: { _ in }))
})
}, setItemWithRevealedOptions: { preset, fromPreset in
updateState { state in
var state = state
@ -296,15 +343,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
])
presentControllerImpl?(actionSheet)
})
let filtersWithCountsSignal = context.engine.peers.updatedChatListFilters()
|> distinctUntilChanged
|> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in
return .single(filters.map { filter -> (ChatListFilter, Int) in
return (filter, 0)
})
}
let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState])
|> map { preferences -> [ChatListFeaturedFilter] in
guard let state = preferences.values[PreferencesKeys.chatListFiltersFeaturedState]?.get(ChatListFiltersFeaturedState.self) else {
@ -313,10 +352,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
return state.filters
}
|> distinctUntilChanged
let filtersWithCounts = Promise<[(ChatListFilter, Int)]>()
filtersWithCounts.set(filtersWithCountsSignal)
let updatedFilterOrder = Promise<[Int32]?>(nil)
let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings])

View File

@ -14,6 +14,14 @@ import SolidRoundedButtonComponent
import Markdown
import SceneKit
private func deg2rad(_ number: Float) -> Float {
return number * .pi / 180
}
private func rad2deg(_ number: Float) -> Float {
return number * 180.0 / .pi
}
private class StarComponent: Component {
static func ==(lhs: StarComponent, rhs: StarComponent) -> Bool {
return true
@ -41,18 +49,97 @@ private class StarComponent: Component {
self.sceneView = SCNView(frame: frame)
self.sceneView.backgroundColor = .clear
self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
self.sceneView.isUserInteractionEnabled = false
super.init(frame: frame)
self.addSubview(self.sceneView)
self.setup()
let panGestureRecoginzer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
self.addGestureRecognizer(panGestureRecoginzer)
let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
self.addGestureRecognizer(tapGestureRecoginzer)
self.disablesInteractiveModalDismiss = true
self.disablesInteractiveTransitionGestureRecognizer = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
return
}
var left = true
if let view = gesture.view {
let point = gesture.location(in: view)
if point.x > view.frame.width / 2.0 {
left = false
}
}
let initial = node.rotation
let target = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: left ? -0.6 : 0.6)
let animation = CABasicAnimation(keyPath: "rotation")
animation.fromValue = NSValue(scnVector4: initial)
animation.toValue = NSValue(scnVector4: target)
animation.duration = 0.25
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
animation.fillMode = .forwards
node.addAnimation(animation, forKey: "rotate")
node.rotation = target
Queue.mainQueue().after(0.25) {
node.rotation = initial
let springAnimation = CASpringAnimation(keyPath: "rotation")
springAnimation.fromValue = NSValue(scnVector4: target)
springAnimation.toValue = NSValue(scnVector4: SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0))
springAnimation.mass = 1.0
springAnimation.stiffness = 21.0
springAnimation.damping = 5.8
springAnimation.duration = springAnimation.settlingDuration * 0.8
node.addAnimation(springAnimation, forKey: "rotate")
}
}
private var previousAngle: Float = 0.0
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
return
}
switch gesture.state {
case .began:
self.previousAngle = 0.0
case .changed:
let translation = gesture.translation(in: gesture.view)
let anglePan = deg2rad(Float(translation.x))
self.previousAngle = anglePan
node.rotation = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: self.previousAngle)
case .ended:
let velocity = gesture.velocity(in: gesture.view)
var small = false
if (self.previousAngle < .pi / 2 && self.previousAngle > -.pi / 2) && abs(velocity.x) < 200 {
small = true
}
self.playAppearanceAnimation(velocity: velocity.x, small: small)
node.rotation = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0)
default:
break
}
}
private func setup() {
guard let scene = SCNScene(named: "star.scn") else {
return
@ -77,11 +164,11 @@ private class StarComponent: Component {
self.setupGradientAnimation()
self.setupShineAnimation()
self.playAppearanceAnimation()
self.playAppearanceAnimation(boom: true)
}
private func setupGradientAnimation() {
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: true) else {
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
return
}
guard let initial = node.geometry?.materials.first?.diffuse.contentsTransform else {
@ -124,21 +211,37 @@ private class StarComponent: Component {
node.geometry?.materials.first?.emission.addAnimation(group, forKey: "shimmer")
}
private func playAppearanceAnimation() {
guard let scene = self.sceneView.scene, let starNode = scene.rootNode.childNode(withName: "star", recursively: false) else {
private func playAppearanceAnimation(velocity: CGFloat? = nil, small: Bool = false, boom: Bool = false) {
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
return
}
if boom, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) {
node.physicsField?.isActive = true
Queue.mainQueue().after(1.0) {
node.physicsField?.isActive = false
particles.particleSystems?.first?.birthRate = 0.8
}
}
let from = node.rotation
var toValue: Float = small ? 0.0 : .pi * 2.0
if let velocity = velocity, !small && abs(velocity) > 200 && velocity < 0.0 {
toValue *= -1
}
let to = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: toValue)
let distance = rad2deg(to.w - from.w)
let springAnimation = CASpringAnimation(keyPath: "rotation")
springAnimation.fromValue = NSValue(scnVector4: SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 0.0))
springAnimation.toValue = NSValue(scnVector4: SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: .pi * 2.0))
springAnimation.fromValue = NSValue(scnVector4: from)
springAnimation.toValue = NSValue(scnVector4: to)
springAnimation.mass = 1.0
springAnimation.stiffness = 21.0
springAnimation.damping = 5.8
springAnimation.duration = 1.5
springAnimation.initialVelocity = 1.7
springAnimation.duration = springAnimation.settlingDuration * 0.75
springAnimation.initialVelocity = velocity.flatMap { abs($0 / CGFloat(distance)) } ?? 1.7
starNode.addAnimation(springAnimation, forKey: "rotate")
node.addAnimation(springAnimation, forKey: "rotate")
}
func update(component: StarComponent, availableSize: CGSize, transition: Transition) -> CGSize {
@ -631,7 +734,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
.position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0))
)
size.height += 183.0
size.height += 183.0 + 10.0
let textColor = theme.list.itemPrimaryTextColor
let titleColor = theme.list.itemPrimaryTextColor
@ -1053,7 +1156,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let topPanelAlpha: CGFloat
let titleOffset: CGFloat
let titleScale: CGFloat
let titleOffsetDelta = 150.0 - environment.navigationHeight / 2.0
let titleOffsetDelta = 160.0 - environment.navigationHeight / 2.0
if let topContentOffset = state.topContentOffset {
topPanelAlpha = min(30.0, max(0.0, topContentOffset - 64.0)) / 30.0
@ -1066,7 +1169,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
}
context.add(star
.position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 18.0 - titleOffset * titleScale))
.position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 10.0 - titleOffset * titleScale))
.scale(titleScale)
)
@ -1080,7 +1183,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: max(150.0 - titleOffset, environment.navigationHeight / 2.0)))
.position(CGPoint(x: context.availableSize.width / 2.0, y: max(160.0 - titleOffset, environment.navigationHeight / 2.0)))
.scale(titleScale)
)

View File

@ -16,6 +16,410 @@ import BundleIconComponent
import SolidRoundedButtonComponent
import Markdown
private class PremiumLimitAnimationComponent: Component {
private let iconName: String
private let inactiveColor: UIColor
private let activeColors: [UIColor]
private let textColor: UIColor
init(
iconName: String,
inactiveColor: UIColor,
activeColors: [UIColor],
textColor: UIColor
) {
self.iconName = iconName
self.inactiveColor = inactiveColor
self.activeColors = activeColors
self.textColor = textColor
}
static func ==(lhs: PremiumLimitAnimationComponent, rhs: PremiumLimitAnimationComponent) -> Bool {
if lhs.iconName != rhs.iconName {
return false
}
if lhs.inactiveColor != rhs.inactiveColor {
return false
}
if lhs.activeColors != rhs.activeColors {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
return true
}
final class View: UIView {
private let container: SimpleLayer
private let inactiveBackground: SimpleLayer
private let activeContainer: SimpleLayer
private let activeBackground: SimpleLayer
private let badgeView: UIView
private let badgeMaskView: UIView
private let badgeMaskBackgroundView: UIView
private let badgeMaskArrowView: UIImageView
private let badgeForeground: SimpleLayer
private let badgeIcon: UIImageView
private let badgeCountLabel: RollingLabel
override init(frame: CGRect) {
self.container = SimpleLayer()
self.container.masksToBounds = true
self.container.cornerRadius = 6.0
self.inactiveBackground = SimpleLayer()
self.activeContainer = SimpleLayer()
self.activeContainer.masksToBounds = true
self.activeBackground = SimpleLayer()
self.badgeView = UIView()
self.badgeView.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0)
self.badgeMaskBackgroundView = UIView()
self.badgeMaskBackgroundView.backgroundColor = .white
self.badgeMaskBackgroundView.layer.cornerRadius = 24.0
self.badgeMaskArrowView = UIImageView()
self.badgeMaskArrowView.image = generateImage(CGSize(width: 44.0, height: 12.0), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
context.scaleBy(x: 3.76, y: 3.76)
context.translateBy(x: -9.3, y: -12.7)
try? drawSvgPath(context, path: "M6.4,0.0 C2.9,0.0 0.0,2.84 0.0,6.35 C0.0,9.86 2.9,12.7 6.4,12.7 H9.302 H11.3 C11.7,12.7 12.1,12.87 12.4,13.17 L14.4,15.13 C14.8,15.54 15.5,15.54 15.9,15.13 L17.8,13.17 C18.1,12.87 18.5,12.7 18.9,12.7 H20.9 H23.6 C27.1,12.7 29.9,9.86 29.9,6.35 C29.9,2.84 27.1,0.0 23.6,0.0 Z ")
})
self.badgeMaskView = UIView()
self.badgeMaskView.addSubview(self.badgeMaskBackgroundView)
self.badgeMaskView.addSubview(self.badgeMaskArrowView)
self.badgeView.mask = self.badgeMaskView
self.badgeForeground = SimpleLayer()
self.badgeIcon = UIImageView()
self.badgeIcon.contentMode = .center
self.badgeCountLabel = RollingLabel()
self.badgeCountLabel.font = Font.with(size: 24.0, design: .round, weight: .semibold, traits: [])
self.badgeCountLabel.textColor = .white
self.badgeCountLabel.text(num: 0)
super.init(frame: frame)
self.layer.addSublayer(self.container)
self.container.addSublayer(self.inactiveBackground)
self.container.addSublayer(self.activeContainer)
self.activeContainer.addSublayer(self.activeBackground)
self.addSubview(self.badgeView)
self.badgeView.layer.addSublayer(self.badgeForeground)
self.badgeView.addSubview(self.badgeIcon)
self.badgeView.addSubview(self.badgeCountLabel)
self.isUserInteractionEnabled = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var didPlayAppearanceAnimation = false
func playAppearanceAnimation(availableSize: CGSize) {
self.badgeView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.4, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
let now = self.badgeView.layer.convertTime(CACurrentMediaTime(), from: nil)
let positionAnimation = CABasicAnimation(keyPath: "position.x")
positionAnimation.fromValue = NSValue(cgPoint: CGPoint(x: -availableSize.width / 2.0, y: 0.0))
positionAnimation.toValue = NSValue(cgPoint: CGPoint())
positionAnimation.isAdditive = true
positionAnimation.duration = 0.5
positionAnimation.fillMode = .forwards
positionAnimation.beginTime = now
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotateAnimation.fromValue = 0.0 as NSNumber
rotateAnimation.toValue = 0.2 as NSNumber
rotateAnimation.isAdditive = true
rotateAnimation.duration = 0.2
rotateAnimation.beginTime = now + 0.5
rotateAnimation.fillMode = .forwards
rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
returnAnimation.fromValue = 0.2 as NSNumber
returnAnimation.toValue = 0.0 as NSNumber
returnAnimation.isAdditive = true
returnAnimation.duration = 0.18
returnAnimation.beginTime = now + 0.5 + 0.2
returnAnimation.fillMode = .forwards
returnAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn)
self.badgeView.layer.add(positionAnimation, forKey: "appearance1")
self.badgeView.layer.add(rotateAnimation, forKey: "appearance2")
self.badgeView.layer.add(returnAnimation, forKey: "appearance3")
self.badgeCountLabel.text(num: 4)
}
var previousAvailableSize: CGSize?
func update(component: PremiumLimitAnimationComponent, availableSize: CGSize, transition: Transition) -> CGSize {
self.inactiveBackground.backgroundColor = component.inactiveColor.cgColor
self.activeBackground.backgroundColor = component.activeColors.last?.cgColor
self.badgeIcon.image = UIImage(bundleImageName: component.iconName)?.withRenderingMode(.alwaysTemplate)
self.badgeIcon.tintColor = component.textColor
let lineHeight: CGFloat = 30.0
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - lineHeight), size: CGSize(width: availableSize.width, height: lineHeight))
self.container.frame = containerFrame
self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: containerFrame.width / 2.0 - 1.0, height: lineHeight))
self.activeContainer.frame = CGRect(origin: CGPoint(x: containerFrame.width / 2.0 + 1.0, y: 0.0), size: CGSize(width: containerFrame.width / 2.0 - 1.0, height: lineHeight))
self.activeBackground.bounds = CGRect(origin: .zero, size: CGSize(width: containerFrame.width * 3.0 / 2.0, height: lineHeight))
if self.activeBackground.animation(forKey: "movement") == nil {
self.activeBackground.position = CGPoint(x: containerFrame.width * 3.0 / 4.0, y: lineHeight / 2.0)
}
let badgeSize = CGSize(width: 82.0, height: 48.0 + 12.0)
self.badgeMaskView.frame = CGRect(origin: .zero, size: badgeSize)
self.badgeMaskBackgroundView.frame = CGRect(origin: .zero, size: CGSize(width: badgeSize.width, height: 48.0))
self.badgeMaskArrowView.frame = CGRect(origin: CGPoint(x: (badgeSize.width - 44.0) / 2.0, y: badgeSize.height - 12.0), size: CGSize(width: 44.0, height: 12.0))
self.badgeView.bounds = CGRect(origin: .zero, size: badgeSize)
self.badgeView.center = CGPoint(x: availableSize.width / 2.0, y: 82.0)
if self.badgeForeground.animation(forKey: "movement") == nil {
self.badgeForeground.position = CGPoint(x: badgeSize.width * 3.0 / 2.0, y: badgeSize.height / 2.0)
}
self.badgeForeground.bounds = CGRect(origin: CGPoint(), size: CGSize(width: badgeSize.width * 3.0, height: badgeSize.height))
self.badgeIcon.frame = CGRect(x: 15.0, y: 9.0, width: 30.0, height: 30.0)
self.badgeCountLabel.frame = CGRect(x: badgeSize.width - 36.0, y: 10.0, width: 30.0, height: 48.0)
if !self.didPlayAppearanceAnimation {
self.didPlayAppearanceAnimation = true
self.playAppearanceAnimation(availableSize: availableSize)
}
if self.previousAvailableSize != availableSize {
self.previousAvailableSize = availableSize
var locations: [CGFloat] = []
let delta = 1.0 / CGFloat(component.activeColors.count - 1)
for i in 0 ..< component.activeColors.count {
locations.append(delta * CGFloat(i))
}
let gradient = generateGradientImage(size: CGSize(width: 200.0, height: 60.0), colors: component.activeColors, locations: locations, direction: .horizontal)
self.badgeForeground.contentsGravity = .resizeAspectFill
self.badgeForeground.contents = gradient?.cgImage
self.activeBackground.contentsGravity = .resizeAspectFill
self.activeBackground.contents = gradient?.cgImage
self.setupGradientAnimations()
}
return availableSize
}
private func setupGradientAnimations() {
if let _ = self.badgeForeground.animation(forKey: "movement") {
} else {
CATransaction.begin()
let badgeOffset = (self.badgeForeground.frame.width - self.badgeView.bounds.width) / 2.0
let badgePreviousValue = self.badgeForeground.position.x
var badgeNewValue: CGFloat = badgeOffset
if badgeOffset - badgePreviousValue < self.badgeForeground.frame.width * 0.25 {
badgeNewValue -= self.badgeForeground.frame.width * 0.35
}
self.badgeForeground.position = CGPoint(x: badgeNewValue, y: self.badgeForeground.bounds.size.height / 2.0)
let lineOffset = (self.activeBackground.frame.width - self.activeContainer.bounds.width) / 2.0
let linePreviousValue = self.activeBackground.position.x
var lineNewValue: CGFloat = lineOffset
if lineOffset - linePreviousValue < self.activeBackground.frame.width * 0.25 {
lineNewValue -= self.activeBackground.frame.width * 0.35
}
self.activeBackground.position = CGPoint(x: lineNewValue, y: self.activeBackground.bounds.size.height / 2.0)
let badgeAnimation = CABasicAnimation(keyPath: "position.x")
badgeAnimation.duration = 4.5
badgeAnimation.fromValue = badgePreviousValue
badgeAnimation.toValue = badgeNewValue
badgeAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
CATransaction.setCompletionBlock { [weak self] in
self?.setupGradientAnimations()
}
self.badgeForeground.add(badgeAnimation, forKey: "movement")
let lineAnimation = CABasicAnimation(keyPath: "position.x")
lineAnimation.duration = 4.5
lineAnimation.fromValue = linePreviousValue
lineAnimation.toValue = lineNewValue
lineAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
self.activeBackground.add(lineAnimation, forKey: "movement")
CATransaction.commit()
}
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
public final class PremiumLimitDisplayComponent: CombinedComponent {
public let inactiveColor: UIColor
public let activeColors: [UIColor]
public let inactiveTitle: String
public let inactiveTitleColor: UIColor
public let activeTitle: String
public let activeValue: String
public let activeTitleColor: UIColor
public let badgeIconName: String
public let badgeValue: String
public init(
inactiveColor: UIColor,
activeColors: [UIColor],
inactiveTitle: String,
inactiveTitleColor: UIColor,
activeTitle: String,
activeValue: String,
activeTitleColor: UIColor,
badgeIconName: String,
badgeValue: String
) {
self.inactiveColor = inactiveColor
self.activeColors = activeColors
self.inactiveTitle = inactiveTitle
self.inactiveTitleColor = inactiveTitleColor
self.activeTitle = activeTitle
self.activeValue = activeValue
self.activeTitleColor = activeTitleColor
self.badgeIconName = badgeIconName
self.badgeValue = badgeValue
}
public static func ==(lhs: PremiumLimitDisplayComponent, rhs: PremiumLimitDisplayComponent) -> Bool {
if lhs.inactiveColor != rhs.inactiveColor {
return false
}
if lhs.activeColors != rhs.activeColors {
return false
}
if lhs.inactiveTitle != rhs.inactiveTitle {
return false
}
if lhs.inactiveTitleColor != rhs.inactiveTitleColor {
return false
}
if lhs.activeTitle != rhs.activeTitle {
return false
}
if lhs.activeValue != rhs.activeValue {
return false
}
if lhs.activeTitleColor != rhs.activeTitleColor {
return false
}
if lhs.badgeIconName != rhs.badgeIconName {
return false
}
if lhs.badgeValue != rhs.badgeValue {
return false
}
return true
}
public static var body: Body {
let inactiveTitle = Child(Text.self)
let activeTitle = Child(Text.self)
let activeValue = Child(Text.self)
let animation = Child(PremiumLimitAnimationComponent.self)
return { context in
let component = context.component
let height: CGFloat = 120.0
let lineHeight: CGFloat = 30.0
let inactiveTitle = inactiveTitle.update(
component: Text(
text: component.inactiveTitle,
font: Font.semibold(15.0),
color: component.inactiveTitleColor
),
availableSize: context.availableSize,
transition: context.transition
)
let activeTitle = activeTitle.update(
component: Text(
text: component.activeTitle,
font: Font.semibold(15.0),
color: component.activeTitleColor
),
availableSize: context.availableSize,
transition: context.transition
)
let activeValue = activeValue.update(
component: Text(
text: component.activeValue,
font: Font.semibold(15.0),
color: component.activeTitleColor
),
availableSize: context.availableSize,
transition: context.transition
)
let animation = animation.update(
component: PremiumLimitAnimationComponent(
iconName: component.badgeIconName,
inactiveColor: component.inactiveColor,
activeColors: component.activeColors,
textColor: component.activeTitleColor
),
availableSize: CGSize(width: context.availableSize.width, height: height),
transition: context.transition
)
context.add(animation
.position(CGPoint(x: context.availableSize.width / 2.0, y: height / 2.0))
)
context.add(inactiveTitle
.position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0))
)
context.add(activeTitle
.position(CGPoint(x: context.availableSize.width / 2.0 + 1.0 + activeTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0))
)
context.add(activeValue
.position(CGPoint(x: context.availableSize.width - activeValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0))
)
return CGSize(width: context.availableSize.width, height: height)
}
}
}
private final class LimitSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -78,13 +482,9 @@ private final class LimitSheetContent: CombinedComponent {
}
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 limit = Child(PremiumLimitDisplayComponent.self)
let button = Child(SolidRoundedButtonComponent.self)
return { context in
@ -100,68 +500,40 @@ private final class LimitSheetContent: CombinedComponent {
let textSideInset: CGFloat = 24.0 + environment.safeInsets.left
let iconName: String
let badgeString: String
let badgeValue: String
let string: String
let premiumValue: String
switch subject {
case .folders:
let limit = state.limits.maxFoldersCount
let premiumLimit = state.premiumLimits.maxFoldersCount
iconName = "Premium/Folder"
badgeString = "\(limit)"
badgeValue = "\(limit)"
string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string
premiumValue = "\(premiumLimit)"
case .chatsInFolder:
let limit = state.limits.maxFolderChatsCount
let premiumLimit = state.premiumLimits.maxFolderChatsCount
iconName = "Premium/Chat"
badgeString = "\(limit)"
badgeValue = "\(limit)"
string = strings.Premium_MaxChatsInFolderCountText("\(limit)", "\(premiumLimit)").string
premiumValue = "\(premiumLimit)"
case .pins:
let limit = state.limits.maxPinnedChatCount
let premiumLimit = state.premiumLimits.maxPinnedChatCount
let limit = 4//state.limits.maxPinnedChatCount
let premiumLimit = 6//state.premiumLimits.maxPinnedChatCount
iconName = "Premium/Pin"
badgeString = "\(limit)"
badgeValue = "\(limit)"
string = strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string
premiumValue = "\(premiumLimit)"
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))
badgeValue = 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
premiumValue = dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))
}
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(
@ -195,19 +567,45 @@ private final class LimitSheetContent: CombinedComponent {
transition: .immediate
)
let limit = limit.update(
component: PremiumLimitDisplayComponent(
inactiveColor: UIColor(rgb: 0xE9E9EA),
activeColors: [
UIColor(rgb: 0x0077ff),
UIColor(rgb: 0x6b93ff),
UIColor(rgb: 0x8878ff),
UIColor(rgb: 0xe46ace)
],
inactiveTitle: "Free",
inactiveTitleColor: .black,
activeTitle: "Premium",
activeValue: premiumValue,
activeTitleColor: .white,
badgeIconName: iconName,
badgeValue: badgeValue
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 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)],
backgroundColors: [
UIColor(rgb: 0x0077ff),
UIColor(rgb: 0x6b93ff),
UIColor(rgb: 0x8878ff),
UIColor(rgb: 0xe46ace)
],
foregroundColor: .white
),
font: .bold,
fontSize: 17.0,
height: 50.0,
cornerRadius: 10.0,
gloss: false,
gloss: true,
iconName: "Premium/X2",
iconPosition: .right,
action: { [weak component] in
@ -224,23 +622,12 @@ private final class LimitSheetContent: CombinedComponent {
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))
context.add(limit
.position(CGPoint(x: width / 2.0, y: limit.size.height / 2.0 + 44.0))
)
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))
.position(CGPoint(x: width / 2.0, y: 28.0))
)
context.add(text
.position(CGPoint(x: width / 2.0, y: 228.0))

View File

@ -0,0 +1,197 @@
import UIKit
private extension UILabel {
func textWidth() -> CGFloat {
return UILabel.textWidth(label: self)
}
class func textWidth(label: UILabel) -> CGFloat {
return textWidth(label: label, text: label.text!)
}
class func textWidth(label: UILabel, text: String) -> CGFloat {
return textWidth(font: label.font, text: text)
}
class func textWidth(font: UIFont, text: String) -> CGFloat {
let myText = text as NSString
let rect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(labelSize.width)
}
}
open class RollingLabel: UILabel {
private var fullText = ""
private var suffix: String = ""
open var showSymbol = false
private var scrollLayers: [CAScrollLayer] = []
private var scrollLabels: [UILabel] = []
private let duration = 1.12
private let durationOffset = 0.2
private let textsNotAnimated = [","]
public func text(num: Int) {
self.configure(with: num)
self.text = " "
self.animate()
}
public func setPrefix(prefix: String) {
self.suffix = prefix
}
private func configure(with number: Int) {
fullText = String(number)
clean()
setupSubviews()
}
private func animate(ascending: Bool = true) {
createAnimations(ascending: ascending)
}
private func clean() {
self.text = nil
self.subviews.forEach { $0.removeFromSuperview() }
self.layer.sublayers?.forEach { $0.removeFromSuperlayer() }
scrollLayers.removeAll()
scrollLabels.removeAll()
}
private func setupSubviews() {
let stringArray = fullText.map { String($0) }
var x: CGFloat = 0
let y: CGFloat = 0
if self.textAlignment == .center {
if showSymbol {
self.text = "\(fullText) \(suffix)"
} else {
self.text = fullText
}
let w = UILabel.textWidth(font: self.font, text: self.text ?? "")
self.text = "" //
x = -(w / 2)
} else if self.textAlignment == .right {
if showSymbol {
self.text = "\(fullText) \(suffix) "
} else {
self.text = fullText
}
let w = UILabel.textWidth(font: self.font, text: self.text ?? "")
self.text = "" //
x = -w
}
if showSymbol {
let wLabel = UILabel()
wLabel.frame.origin = CGPoint(x: x, y: y)
wLabel.textColor = textColor
wLabel.font = font
wLabel.text = "\(suffix) "
wLabel.textAlignment = .center
wLabel.sizeToFit()
self.addSubview(wLabel)
x += wLabel.bounds.width
}
stringArray.enumerated().forEach { index, text in
if textsNotAnimated.contains(text) {
let label = UILabel()
label.frame.origin = CGPoint(x: x, y: y)
label.textColor = textColor
label.font = font
label.text = text
label.textAlignment = .center
label.sizeToFit()
self.addSubview(label)
x += label.bounds.width
} else {
let label = UILabel()
label.frame.origin = CGPoint(x: x, y: y)
label.textColor = textColor
label.font = font
label.text = "0"
label.textAlignment = .center
label.sizeToFit()
createScrollLayer(to: label, text: text)
x += label.bounds.width
}
}
}
private func createScrollLayer(to label: UILabel, text: String) {
let scrollLayer = CAScrollLayer()
scrollLayer.frame = label.frame
scrollLayers.append(scrollLayer)
self.layer.addSublayer(scrollLayer)
createContentForLayer(scrollLayer: scrollLayer, text: text)
}
private func createContentForLayer(scrollLayer: CAScrollLayer, text: String) {
var textsForScroll: [String] = []
let max: Int
var found = false
if let val = Int(text) {
max = val
found = true
} else {
max = 9
}
for i in 0...max {
let str = String(i)
textsForScroll.append(str)
}
if !found {
textsForScroll.append(text)
}
var height: CGFloat = 0
for text in textsForScroll {
let label = UILabel()
label.text = text
label.textColor = textColor
label.font = font
label.textAlignment = .center
label.frame = CGRect(x: 0, y: height, width: scrollLayer.frame.width, height: scrollLayer.frame.height)
scrollLayer.addSublayer(label.layer)
scrollLabels.append(label)
height = label.frame.maxY
}
}
private func createAnimations(ascending: Bool) {
var offset: CFTimeInterval = 0.0
for scrollLayer in scrollLayers {
let maxY = scrollLayer.sublayers?.last?.frame.origin.y ?? 0.0
let animation = CABasicAnimation(keyPath: "sublayerTransform.translation.y")
animation.duration = duration + offset
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
if ascending {
animation.fromValue = maxY
animation.toValue = 0
} else {
animation.fromValue = 0
animation.toValue = maxY
}
scrollLayer.scrollMode = .vertically
scrollLayer.add(animation, forKey: nil)
scrollLayer.scroll(to: CGPoint(x: 0, y: maxY))
offset += self.durationOffset
}
}
}

View File

@ -645,9 +645,7 @@ public final class SolidRoundedButtonView: UIView {
let previousValue = buttonBackgroundAnimationView.center.x
var newValue: CGFloat = offset
if offset - previousValue < buttonBackgroundAnimationView.frame.width * 0.25 {
newValue -= CGFloat.random(in: buttonBackgroundAnimationView.frame.width * 0.3 ..< buttonBackgroundAnimationView.frame.width * 0.4)
} else {
// newValue -= CGFloat.random(in: 0.0 ..< buttonBackgroundAnimationView.frame.width * 0.1)
newValue -= buttonBackgroundAnimationView.frame.width * 0.35
}
buttonBackgroundAnimationView.center = CGPoint(x: newValue, y: buttonBackgroundAnimationView.bounds.size.height / 2.0)
@ -794,7 +792,10 @@ public final class SolidRoundedButtonView: UIView {
}
if let buttonBackgroundAnimationView = self.buttonBackgroundAnimationView {
transition.updateFrame(view: buttonBackgroundAnimationView, frame: CGRect(origin: CGPoint(), size: CGSize(width: buttonSize.width * 2.4, height: buttonSize.height)))
if buttonBackgroundAnimationView.layer.animation(forKey: "movement") == nil {
buttonBackgroundAnimationView.center = CGPoint(x: buttonSize.width * 2.4 / 2.0, y: buttonSize.height / 2.0)
}
buttonBackgroundAnimationView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: buttonSize.width * 2.4, height: buttonSize.height))
self.setupGradientAnimations()
}

View File

@ -128,6 +128,37 @@ public extension TelegramEngine {
)
}
}
public func subscribe<
T0: TelegramEngineDataItem,
T1: TelegramEngineDataItem,
T2: TelegramEngineDataItem
>(
_ t0: T0,
_ t1: T1,
_ t2: T2
) -> Signal<
(
T0.Result,
T1.Result,
T2.Result
),
NoError> {
return self._subscribe(items: [
t0 as! AnyPostboxViewDataItem,
t1 as! AnyPostboxViewDataItem,
t2 as! AnyPostboxViewDataItem
])
|> map { results -> (T0.Result, T1.Result, T2.Result) in
return (
results[0] as! T0.Result,
results[1] as! T1.Result,
results[2] as! T2.Result
)
}
}
public func get<
T0: TelegramEngineDataItem,
T1: TelegramEngineDataItem
@ -142,5 +173,23 @@ public extension TelegramEngine {
NoError> {
return self.subscribe(t0, t1) |> take(1)
}
public func get<
T0: TelegramEngineDataItem,
T1: TelegramEngineDataItem,
T2: TelegramEngineDataItem
>(
_ t0: T0,
_ t1: T1,
_ t2: T2
) -> Signal<
(
T0.Result,
T1.Result,
T2.Result
),
NoError> {
return self.subscribe(t0, t1, t2) |> take(1)
}
}
}

View File

@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Tmp.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Tmp2.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -131,11 +131,11 @@ public final class TelegramRootController: NavigationController {
self.rootTabController = tabBarController
self.pushViewController(tabBarController, animated: false)
Queue.mainQueue().after(1.0) {
// let screen = PremiumLimitScreen(context: self.context, subject: .pins, action: {})
let screen = PremiumIntroScreen(context: self.context, action: {})
self.chatListController?.push(screen)
}
// Queue.mainQueue().after(1.0) {
//// let screen = PremiumLimitScreen(context: self.context, subject: .pins, action: {})
// let screen = PremiumIntroScreen(context: self.context, action: {})
// self.chatListController?.push(screen)
// }
}
public func updateRootControllers(showCallsTab: Bool) {