mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 19:30:29 +00:00
Various improvements
This commit is contained in:
parent
efe304afe6
commit
a80a706379
@ -2239,7 +2239,8 @@ final class VideoChatScreenComponent: Component {
|
|||||||
content: LottieComponent.AppBundleContent(
|
content: LottieComponent.AppBundleContent(
|
||||||
name: "anim_profilemore"
|
name: "anim_profilemore"
|
||||||
),
|
),
|
||||||
color: .white
|
color: .white,
|
||||||
|
size: CGSize(width: 34.0, height: 34.0)
|
||||||
)),
|
)),
|
||||||
background: AnyComponent(
|
background: AnyComponent(
|
||||||
GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), isDark: false, tintColor: .init(kind: .panel, color: panelColor))
|
GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), isDark: false, tintColor: .init(kind: .panel, color: panelColor))
|
||||||
@ -2261,7 +2262,8 @@ final class VideoChatScreenComponent: Component {
|
|||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(PlainButtonComponent(
|
component: AnyComponent(PlainButtonComponent(
|
||||||
content: AnyComponent(Image(
|
content: AnyComponent(Image(
|
||||||
image: closeButtonImage(dark: false)
|
image: closeButtonImage(dark: false),
|
||||||
|
contentMode: .center
|
||||||
)),
|
)),
|
||||||
background: AnyComponent(
|
background: AnyComponent(
|
||||||
GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), isDark: false, tintColor: .init(kind: .panel, color: panelColor))
|
GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), isDark: false, tintColor: .init(kind: .panel, color: panelColor))
|
||||||
|
|||||||
@ -55,11 +55,12 @@ func closeButtonImage(dark: Bool) -> UIImage? {
|
|||||||
|
|
||||||
context.setLineWidth(2.0)
|
context.setLineWidth(2.0)
|
||||||
context.setLineCap(.round)
|
context.setLineCap(.round)
|
||||||
|
context.setLineJoin(.round)
|
||||||
context.setStrokeColor(UIColor.white.cgColor)
|
context.setStrokeColor(UIColor.white.cgColor)
|
||||||
|
|
||||||
context.move(to: CGPoint(x: 7.0 + UIScreenPixel, y: 16.0 + UIScreenPixel))
|
context.move(to: CGPoint(x: 6.0 - UIScreenPixel, y: 17.0))
|
||||||
context.addLine(to: CGPoint(x: 14.0, y: 10.0))
|
context.addLine(to: CGPoint(x: 14.0, y: 8.0 + UIScreenPixel))
|
||||||
context.addLine(to: CGPoint(x: 21.0 - UIScreenPixel, y: 16.0 + UIScreenPixel))
|
context.addLine(to: CGPoint(x: 22.0 + UIScreenPixel, y: 17.0))
|
||||||
context.strokePath()
|
context.strokePath()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -440,6 +440,7 @@ public final class ButtonComponent: Component {
|
|||||||
private var component: ButtonComponent?
|
private var component: ButtonComponent?
|
||||||
private weak var componentState: EmptyComponentState?
|
private weak var componentState: EmptyComponentState?
|
||||||
|
|
||||||
|
private var containerView: UIView
|
||||||
private var shimmeringView: ButtonShimmeringView?
|
private var shimmeringView: ButtonShimmeringView?
|
||||||
private var chromeView: UIImageView?
|
private var chromeView: UIImageView?
|
||||||
private var contentItem: ContentItem?
|
private var contentItem: ContentItem?
|
||||||
@ -447,18 +448,38 @@ public final class ButtonComponent: Component {
|
|||||||
private var activityIndicator: ActivityIndicator?
|
private var activityIndicator: ActivityIndicator?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
|
self.containerView = UIView()
|
||||||
|
self.containerView.clipsToBounds = true
|
||||||
|
self.containerView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.containerView)
|
||||||
|
|
||||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
|
|
||||||
self.highligthedChanged = { [weak self] highlighted in
|
self.highligthedChanged = { [weak self] highlighted in
|
||||||
if let self, let component = self.component, component.isEnabled {
|
if let self, let component = self.component, component.isEnabled {
|
||||||
if highlighted {
|
switch component.background.style {
|
||||||
self.layer.removeAnimation(forKey: "opacity")
|
case .glass:
|
||||||
self.alpha = 0.7
|
let transition = ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))
|
||||||
} else {
|
if highlighted {
|
||||||
self.alpha = 1.0
|
let highlightedColor = component.background.color.withMultiplied(hue: 1.0, saturation: 0.77, brightness: 1.01)
|
||||||
self.layer.animateAlpha(from: 7, to: 1.0, duration: 0.2)
|
transition.setBackgroundColor(view: self.containerView, color: highlightedColor)
|
||||||
|
transition.setScale(view: self.containerView, scale: 1.05)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
transition.setBackgroundColor(view: self.containerView, color: component.background.color)
|
||||||
|
transition.setScale(view: self.containerView, scale: 1.0)
|
||||||
|
}
|
||||||
|
case .legacy:
|
||||||
|
if highlighted {
|
||||||
|
self.containerView.layer.removeAnimation(forKey: "opacity")
|
||||||
|
self.containerView.alpha = 0.7
|
||||||
|
} else {
|
||||||
|
self.containerView.alpha = 1.0
|
||||||
|
self.containerView.layer.animateAlpha(from: 0.7, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -485,13 +506,13 @@ public final class ButtonComponent: Component {
|
|||||||
|
|
||||||
self.isEnabled = (component.isEnabled || component.allowActionWhenDisabled) && !component.displaysProgress
|
self.isEnabled = (component.isEnabled || component.allowActionWhenDisabled) && !component.displaysProgress
|
||||||
|
|
||||||
transition.setBackgroundColor(view: self, color: component.background.color)
|
transition.setBackgroundColor(view: self.containerView, color: component.background.color)
|
||||||
|
|
||||||
var cornerRadius: CGFloat = component.background.cornerRadius
|
var cornerRadius: CGFloat = component.background.cornerRadius
|
||||||
if case .glass = component.background.style, component.background.cornerRadius == 10.0 {
|
if case .glass = component.background.style, component.background.cornerRadius == 10.0 {
|
||||||
cornerRadius = availableSize.height * 0.5
|
cornerRadius = availableSize.height * 0.5
|
||||||
}
|
}
|
||||||
transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius)
|
transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: cornerRadius)
|
||||||
|
|
||||||
var contentAlpha: CGFloat = 1.0
|
var contentAlpha: CGFloat = 1.0
|
||||||
if component.displaysProgress {
|
if component.displaysProgress {
|
||||||
@ -525,7 +546,7 @@ public final class ButtonComponent: Component {
|
|||||||
contentTransition = .immediate
|
contentTransition = .immediate
|
||||||
animateIn = true
|
animateIn = true
|
||||||
contentView.isUserInteractionEnabled = false
|
contentView.isUserInteractionEnabled = false
|
||||||
self.addSubview(contentView)
|
self.containerView.addSubview(contentView)
|
||||||
|
|
||||||
contentItem.view.parentState = state
|
contentItem.view.parentState = state
|
||||||
}
|
}
|
||||||
@ -563,7 +584,7 @@ public final class ButtonComponent: Component {
|
|||||||
activityIndicator = ActivityIndicator(type: .custom(component.background.foreground, 22.0, 2.0, true))
|
activityIndicator = ActivityIndicator(type: .custom(component.background.foreground, 22.0, 2.0, true))
|
||||||
activityIndicator.view.alpha = 0.0
|
activityIndicator.view.alpha = 0.0
|
||||||
self.activityIndicator = activityIndicator
|
self.activityIndicator = activityIndicator
|
||||||
self.addSubview(activityIndicator.view)
|
self.containerView.addSubview(activityIndicator.view)
|
||||||
}
|
}
|
||||||
let indicatorSize = CGSize(width: 22.0, height: 22.0)
|
let indicatorSize = CGSize(width: 22.0, height: 22.0)
|
||||||
transition.setAlpha(view: activityIndicator.view, alpha: 1.0)
|
transition.setAlpha(view: activityIndicator.view, alpha: 1.0)
|
||||||
@ -586,7 +607,7 @@ public final class ButtonComponent: Component {
|
|||||||
shimmeringTransition = .immediate
|
shimmeringTransition = .immediate
|
||||||
shimmeringView = ButtonShimmeringView(frame: .zero)
|
shimmeringView = ButtonShimmeringView(frame: .zero)
|
||||||
self.shimmeringView = shimmeringView
|
self.shimmeringView = shimmeringView
|
||||||
self.insertSubview(shimmeringView, at: 0)
|
self.containerView.insertSubview(shimmeringView, at: 0)
|
||||||
}
|
}
|
||||||
shimmeringView.update(size: availableSize, background: component.background, cornerRadius: component.background.cornerRadius, transition: shimmeringTransition)
|
shimmeringView.update(size: availableSize, background: component.background, cornerRadius: component.background.cornerRadius, transition: shimmeringTransition)
|
||||||
shimmeringTransition.setFrame(view: shimmeringView, frame: CGRect(origin: .zero, size: availableSize))
|
shimmeringTransition.setFrame(view: shimmeringView, frame: CGRect(origin: .zero, size: availableSize))
|
||||||
@ -607,9 +628,9 @@ public final class ButtonComponent: Component {
|
|||||||
chromeView = UIImageView()
|
chromeView = UIImageView()
|
||||||
self.chromeView = chromeView
|
self.chromeView = chromeView
|
||||||
if let shimmeringView = self.shimmeringView {
|
if let shimmeringView = self.shimmeringView {
|
||||||
self.insertSubview(chromeView, aboveSubview: shimmeringView)
|
self.containerView.insertSubview(chromeView, aboveSubview: shimmeringView)
|
||||||
} else {
|
} else {
|
||||||
self.insertSubview(chromeView, at: 0)
|
self.containerView.insertSubview(chromeView, at: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
chromeView.layer.compositingFilter = "overlayBlendMode"
|
chromeView.layer.compositingFilter = "overlayBlendMode"
|
||||||
@ -624,6 +645,9 @@ public final class ButtonComponent: Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transition.setPosition(view: self.containerView, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||||
|
transition.setBoundsSize(view: self.containerView, size: availableSize)
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -561,6 +561,7 @@ public final class GiftItemComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tonButtonColor: UIColor = .clear
|
||||||
if case .generic = component.mode {
|
if case .generic = component.mode {
|
||||||
if let title = component.title {
|
if let title = component.title {
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
@ -630,6 +631,7 @@ public final class GiftItemComponent: Component {
|
|||||||
price = priceValue ?? component.strings.Gift_Options_Gift_Transfer
|
price = priceValue ?? component.strings.Gift_Options_Gift_Transfer
|
||||||
tinted = true
|
tinted = true
|
||||||
}
|
}
|
||||||
|
tonButtonColor = buttonColor
|
||||||
|
|
||||||
let buttonSize = self.button.update(
|
let buttonSize = self.button.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
@ -657,29 +659,7 @@ public final class GiftItemComponent: Component {
|
|||||||
transition.setFrame(view: buttonView, frame: buttonFrame)
|
transition.setFrame(view: buttonView, frame: buttonFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .uniqueGift(gift, _) = component.subject, gift.resellForTonOnly {
|
|
||||||
let tonSize = self.ton.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(
|
|
||||||
ZStack([
|
|
||||||
AnyComponentWithIdentity(id: "background", component: AnyComponent(RoundedRectangle(color: buttonColor, cornerRadius: 12.0))),
|
|
||||||
AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Premium/TonGift", tintColor: .white)))
|
|
||||||
])
|
|
||||||
),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: 24.0, height: 24.0)
|
|
||||||
)
|
|
||||||
let tonFrame = CGRect(origin: CGPoint(x: 4.0, y: 4.0), size: tonSize)
|
|
||||||
if let tonView = self.ton.view {
|
|
||||||
if tonView.superview == nil {
|
|
||||||
self.addSubview(tonView)
|
|
||||||
}
|
|
||||||
transition.setFrame(view: tonView, frame: tonFrame)
|
|
||||||
}
|
|
||||||
} else if let tonView = self.ton.view, tonView.superview != nil {
|
|
||||||
tonView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let label = component.label {
|
if let label = component.label {
|
||||||
let labelColor = component.theme.overallDarkAppearance ? UIColor(rgb: 0xffc337) : UIColor(rgb: 0xd3720a)
|
let labelColor = component.theme.overallDarkAppearance ? UIColor(rgb: 0xffc337) : UIColor(rgb: 0xd3720a)
|
||||||
let attributes = MarkdownAttributes(
|
let attributes = MarkdownAttributes(
|
||||||
@ -720,6 +700,29 @@ public final class GiftItemComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case .generic = component.mode, case let .uniqueGift(gift, _) = component.subject, gift.resellForTonOnly {
|
||||||
|
let tonSize = self.ton.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
ZStack([
|
||||||
|
AnyComponentWithIdentity(id: "background", component: AnyComponent(RoundedRectangle(color: tonButtonColor, cornerRadius: 12.0))),
|
||||||
|
AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Premium/TonGift", tintColor: .white)))
|
||||||
|
])
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 24.0, height: 24.0)
|
||||||
|
)
|
||||||
|
let tonFrame = CGRect(origin: CGPoint(x: 4.0, y: 4.0), size: tonSize)
|
||||||
|
if let tonView = self.ton.view {
|
||||||
|
if tonView.superview == nil {
|
||||||
|
self.addSubview(tonView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: tonView, frame: tonFrame)
|
||||||
|
}
|
||||||
|
} else if let tonView = self.ton.view, tonView.superview != nil {
|
||||||
|
tonView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
if let ribbon = component.ribbon {
|
if let ribbon = component.ribbon {
|
||||||
let ribbonFontSize: CGFloat
|
let ribbonFontSize: CGFloat
|
||||||
if case .profile = component.mode {
|
if case .profile = component.mode {
|
||||||
@ -995,7 +998,7 @@ public final class GiftItemComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
resellBackgroundTransition = .immediate
|
resellBackgroundTransition = .immediate
|
||||||
|
|
||||||
resellBackground = BlurredBackgroundView(color: UIColor(rgb: 0x000000, alpha: 0.3), enableBlur: true) //UIVisualEffectView(effect: blurEffect)
|
resellBackground = BlurredBackgroundView(color: UIColor(rgb: 0x000000, alpha: 0.3), enableBlur: true)
|
||||||
resellBackground.clipsToBounds = true
|
resellBackground.clipsToBounds = true
|
||||||
self.resellBackground = resellBackground
|
self.resellBackground = resellBackground
|
||||||
|
|
||||||
@ -1019,7 +1022,8 @@ public final class GiftItemComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .grid = component.mode {
|
switch component.mode {
|
||||||
|
case .generic, .grid:
|
||||||
let lineWidth: CGFloat = 2.0
|
let lineWidth: CGFloat = 2.0
|
||||||
let selectionFrame = backgroundFrame.insetBy(dx: 3.0, dy: 3.0)
|
let selectionFrame = backgroundFrame.insetBy(dx: 3.0, dy: 3.0)
|
||||||
|
|
||||||
@ -1030,7 +1034,9 @@ public final class GiftItemComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
selectionLayer = SimpleShapeLayer()
|
selectionLayer = SimpleShapeLayer()
|
||||||
self.selectionLayer = selectionLayer
|
self.selectionLayer = selectionLayer
|
||||||
if self.ribbon.layer.superlayer != nil {
|
if self.ton.view?.superview != nil {
|
||||||
|
self.layer.insertSublayer(selectionLayer, below: self.ton.view?.layer)
|
||||||
|
} else if self.ribbon.layer.superlayer != nil {
|
||||||
self.layer.insertSublayer(selectionLayer, below: self.ribbon.layer)
|
self.layer.insertSublayer(selectionLayer, below: self.ribbon.layer)
|
||||||
} else {
|
} else {
|
||||||
self.layer.addSublayer(selectionLayer)
|
self.layer.addSublayer(selectionLayer)
|
||||||
@ -1058,6 +1064,8 @@ public final class GiftItemComponent: Component {
|
|||||||
selectionLayer.removeFromSuperlayer()
|
selectionLayer.removeFromSuperlayer()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .select = component.mode {
|
if case .select = component.mode {
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "GiftLoadingShimmerView",
|
||||||
|
module_name = "GiftLoadingShimmerView",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/ViewControllerComponent",
|
||||||
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/AppBundle",
|
||||||
|
"//submodules/TelegramStringFormatting",
|
||||||
|
"//submodules/PresentationDataUtils",
|
||||||
|
"//submodules/TextFormat",
|
||||||
|
"//submodules/Markdown",
|
||||||
|
"//submodules/AvatarNode",
|
||||||
|
"//submodules/CheckNode",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||||
|
"//submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent",
|
||||||
|
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,200 @@
|
|||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
private final class ShimmerEffectView: UIView {
|
||||||
|
private var currentBackgroundColor: UIColor?
|
||||||
|
private var currentForegroundColor: UIColor?
|
||||||
|
private let imageContainerView: UIView
|
||||||
|
private let imageView: UIImageView
|
||||||
|
|
||||||
|
private var absoluteLocation: (CGRect, CGSize)?
|
||||||
|
private var shouldBeAnimating = false
|
||||||
|
|
||||||
|
override init(frame: CGRect = .zero) {
|
||||||
|
self.imageContainerView = UIView()
|
||||||
|
self.imageContainerView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.imageView = UIImageView()
|
||||||
|
self.imageView.isUserInteractionEnabled = false
|
||||||
|
self.imageView.contentMode = .scaleToFill
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.isUserInteractionEnabled = false
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
|
self.imageContainerView.addSubview(self.imageView)
|
||||||
|
self.addSubview(self.imageContainerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didMoveToWindow() {
|
||||||
|
super.didMoveToWindow()
|
||||||
|
updateAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
||||||
|
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.argb == backgroundColor.argb,
|
||||||
|
let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.argb == foregroundColor.argb {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.currentBackgroundColor = backgroundColor
|
||||||
|
self.currentForegroundColor = foregroundColor
|
||||||
|
|
||||||
|
self.imageView.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
|
||||||
|
context.setFillColor(backgroundColor.cgColor)
|
||||||
|
context.fill(CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
|
context.clip(to: CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
|
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||||
|
let peakColor = foregroundColor.cgColor
|
||||||
|
|
||||||
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||||
|
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: [])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
if let absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
||||||
|
let frameUpdated = self.absoluteLocation?.0 != rect
|
||||||
|
self.absoluteLocation = (rect, containerSize)
|
||||||
|
|
||||||
|
if sizeUpdated, shouldBeAnimating {
|
||||||
|
self.imageView.layer.removeAnimation(forKey: "shimmer")
|
||||||
|
self.addImageAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
if frameUpdated {
|
||||||
|
self.imageContainerView.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateAnimation() {
|
||||||
|
let inHierarchy = (self.window != nil)
|
||||||
|
let shouldAnimate = inHierarchy && (self.absoluteLocation != nil)
|
||||||
|
if shouldAnimate != self.shouldBeAnimating {
|
||||||
|
self.shouldBeAnimating = shouldAnimate
|
||||||
|
if shouldAnimate {
|
||||||
|
self.addImageAnimation()
|
||||||
|
} else {
|
||||||
|
self.imageView.layer.removeAnimation(forKey: "shimmer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addImageAnimation() {
|
||||||
|
guard let containerSize = self.absoluteLocation?.1 else { return }
|
||||||
|
let gradientHeight: CGFloat = 250.0
|
||||||
|
self.imageView.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight),
|
||||||
|
size: CGSize(width: containerSize.width, height: gradientHeight))
|
||||||
|
|
||||||
|
let anim = CABasicAnimation(keyPath: "position.y")
|
||||||
|
anim.fromValue = 0.0
|
||||||
|
anim.toValue = (containerSize.height + gradientHeight)
|
||||||
|
anim.duration = 1.3
|
||||||
|
anim.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
||||||
|
anim.fillMode = .removed
|
||||||
|
anim.isRemovedOnCompletion = true
|
||||||
|
anim.repeatCount = .infinity
|
||||||
|
anim.beginTime = CACurrentMediaTime() + 1.0
|
||||||
|
anim.isAdditive = true
|
||||||
|
|
||||||
|
self.imageView.layer.add(anim, forKey: "shimmer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class GiftLoadingShimmerView: UIView {
|
||||||
|
private let backgroundView = UIView()
|
||||||
|
private let effectView = ShimmerEffectView()
|
||||||
|
private let maskImageView = UIImageView()
|
||||||
|
|
||||||
|
private var currentParams: (size: CGSize, theme: PresentationTheme, showFilters: Bool)?
|
||||||
|
|
||||||
|
public override init(frame: CGRect = .zero) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.isUserInteractionEnabled = false
|
||||||
|
self.backgroundColor = .clear
|
||||||
|
|
||||||
|
self.addSubview(self.backgroundView)
|
||||||
|
self.addSubview(self.effectView)
|
||||||
|
self.addSubview(self.maskImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError() }
|
||||||
|
|
||||||
|
public func update(size: CGSize, theme: PresentationTheme, showFilters: Bool = false, isPlain: Bool = false, transition: ContainedViewLayoutTransition) {
|
||||||
|
let backgroundColor = isPlain ? theme.list.itemBlocksBackgroundColor : theme.list.blocksBackgroundColor
|
||||||
|
let color = theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85)
|
||||||
|
|
||||||
|
if self.currentParams?.size != size || self.currentParams?.theme !== theme || self.currentParams?.showFilters != showFilters {
|
||||||
|
self.currentParams = (size, theme, showFilters)
|
||||||
|
|
||||||
|
self.backgroundView.backgroundColor = color
|
||||||
|
|
||||||
|
self.maskImageView.image = generateImage(size, rotatedContext: { size, context in
|
||||||
|
context.setFillColor(backgroundColor.cgColor)
|
||||||
|
context.fill(CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
|
||||||
|
if showFilters {
|
||||||
|
let filterSpacing: CGFloat = 6.0
|
||||||
|
let filterWidth = (size.width - sideInset * 2.0 - filterSpacing * 3.0) / 4.0
|
||||||
|
for i in 0 ..< 4 {
|
||||||
|
let rect = CGRect(origin: CGPoint(x: sideInset + (filterWidth + filterSpacing) * CGFloat(i), y: 0.0),
|
||||||
|
size: CGSize(width: filterWidth, height: 28.0))
|
||||||
|
context.addPath(CGPath(roundedRect: rect, cornerWidth: 14.0, cornerHeight: 14.0, transform: nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentY: CGFloat = 39.0 + 7.0
|
||||||
|
var rowIndex: Int = 0
|
||||||
|
|
||||||
|
let optionSpacing: CGFloat = 10.0
|
||||||
|
let optionWidth = (size.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
||||||
|
let itemSize = CGSize(width: optionWidth, height: 154.0)
|
||||||
|
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.setFillColor(UIColor.clear.cgColor)
|
||||||
|
|
||||||
|
while currentY < size.height {
|
||||||
|
for i in 0 ..< 3 {
|
||||||
|
let itemOrigin = CGPoint(
|
||||||
|
x: sideInset + CGFloat(i) * (itemSize.width + optionSpacing),
|
||||||
|
y: 39.0 + 9.0 + CGFloat(rowIndex) * (itemSize.height + optionSpacing)
|
||||||
|
)
|
||||||
|
context.addPath(CGPath(roundedRect: CGRect(origin: itemOrigin, size: itemSize),
|
||||||
|
cornerWidth: 10.0, cornerHeight: 10.0, transform: nil))
|
||||||
|
}
|
||||||
|
currentY += itemSize.height
|
||||||
|
rowIndex += 1
|
||||||
|
}
|
||||||
|
context.fillPath()
|
||||||
|
})
|
||||||
|
|
||||||
|
self.effectView.update(backgroundColor: color, foregroundColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4))
|
||||||
|
self.effectView.updateAbsoluteRect(CGRect(origin: .zero, size: size), within: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size))
|
||||||
|
transition.updateFrame(view: self.maskImageView, frame: CGRect(origin: .zero, size: size))
|
||||||
|
transition.updateFrame(view: self.effectView, frame: CGRect(origin: .zero, size: size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -45,6 +45,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
|
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
|
||||||
"//submodules/TelegramUI/Components/LottieComponent",
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
"//submodules/TelegramUI/Components/TextFieldComponent",
|
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||||
|
"//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import GiftViewScreen
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import LottieComponent
|
import LottieComponent
|
||||||
|
import GiftLoadingShimmerView
|
||||||
|
|
||||||
private let minimumCountToDisplayFilters = 18
|
private let minimumCountToDisplayFilters = 18
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
final class View: UIView, UIScrollViewDelegate {
|
final class View: UIView, UIScrollViewDelegate {
|
||||||
private let topOverscrollLayer = SimpleLayer()
|
private let topOverscrollLayer = SimpleLayer()
|
||||||
private let scrollView: ScrollView
|
private let scrollView: ScrollView
|
||||||
private let loadingNode: LoadingShimmerNode
|
private let loadingView: GiftLoadingShimmerView
|
||||||
private let emptyResultsAnimation = ComponentView<Empty>()
|
private let emptyResultsAnimation = ComponentView<Empty>()
|
||||||
private let emptyResultsTitle = ComponentView<Empty>()
|
private let emptyResultsTitle = ComponentView<Empty>()
|
||||||
private let clearFilters = ComponentView<Empty>()
|
private let clearFilters = ComponentView<Empty>()
|
||||||
@ -88,7 +89,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
private let subtitle = ComponentView<Empty>()
|
private let subtitle = ComponentView<Empty>()
|
||||||
|
|
||||||
private var starsItems: [AnyHashable: ComponentView<Empty>] = [:]
|
private var giftItems: [AnyHashable: ComponentView<Empty>] = [:]
|
||||||
private let filterSelector = ComponentView<Empty>()
|
private let filterSelector = ComponentView<Empty>()
|
||||||
|
|
||||||
private var isUpdating: Bool = false
|
private var isUpdating: Bool = false
|
||||||
@ -117,13 +118,13 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
self.scrollView.alwaysBounceVertical = true
|
self.scrollView.alwaysBounceVertical = true
|
||||||
|
|
||||||
self.loadingNode = LoadingShimmerNode()
|
self.loadingView = GiftLoadingShimmerView()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.scrollView.delegate = self
|
self.scrollView.delegate = self
|
||||||
self.addSubview(self.scrollView)
|
self.addSubview(self.scrollView)
|
||||||
self.addSubview(self.loadingNode.view)
|
self.addSubview(self.loadingView)
|
||||||
|
|
||||||
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
|
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
|
||||||
}
|
}
|
||||||
@ -211,14 +212,14 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
|
|
||||||
var itemTransition = transition
|
var itemTransition = transition
|
||||||
let visibleItem: ComponentView<Empty>
|
let visibleItem: ComponentView<Empty>
|
||||||
if let current = self.starsItems[itemId] {
|
if let current = self.giftItems[itemId] {
|
||||||
visibleItem = current
|
visibleItem = current
|
||||||
} else {
|
} else {
|
||||||
visibleItem = ComponentView()
|
visibleItem = ComponentView()
|
||||||
if !transition.animation.isImmediate {
|
if !transition.animation.isImmediate {
|
||||||
itemTransition = .immediate
|
itemTransition = .immediate
|
||||||
}
|
}
|
||||||
self.starsItems[itemId] = visibleItem
|
self.giftItems[itemId] = visibleItem
|
||||||
}
|
}
|
||||||
|
|
||||||
var ribbon: GiftItemComponent.Ribbon?
|
var ribbon: GiftItemComponent.Ribbon?
|
||||||
@ -235,7 +236,10 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
color: ribbonColor
|
color: ribbonColor
|
||||||
)
|
)
|
||||||
|
|
||||||
let subject: GiftItemComponent.Subject = .uniqueGift(gift: uniqueGift, price: "# \(presentationStringsFormattedNumber(Int32(uniqueGift.resellAmounts?.first(where: { $0.currency == .stars })?.amount.value ?? 0), environment.dateTimeFormat.groupingSeparator))")
|
let subject: GiftItemComponent.Subject = .uniqueGift(
|
||||||
|
gift: uniqueGift,
|
||||||
|
price: "# \(presentationStringsFormattedNumber(Int32(uniqueGift.resellAmounts?.first(where: { $0.currency == .stars })?.amount.value ?? 0), environment.dateTimeFormat.groupingSeparator))"
|
||||||
|
)
|
||||||
let _ = visibleItem.update(
|
let _ = visibleItem.update(
|
||||||
transition: itemTransition,
|
transition: itemTransition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
@ -306,7 +310,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var removeIds: [AnyHashable] = []
|
var removeIds: [AnyHashable] = []
|
||||||
for (id, item) in self.starsItems {
|
for (id, item) in self.giftItems {
|
||||||
if !validIds.contains(id) {
|
if !validIds.contains(id) {
|
||||||
removeIds.append(id)
|
removeIds.append(id)
|
||||||
if let itemView = item.view {
|
if let itemView = item.view {
|
||||||
@ -322,7 +326,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for id in removeIds {
|
for id in removeIds {
|
||||||
self.starsItems.removeValue(forKey: id)
|
self.giftItems.removeValue(forKey: id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,7 +413,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
view.alpha = 0.0
|
view.alpha = 0.0
|
||||||
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||||
self.insertSubview(view, belowSubview: self.loadingNode.view)
|
self.insertSubview(view, belowSubview: self.loadingView)
|
||||||
view.playOnce()
|
view.playOnce()
|
||||||
}
|
}
|
||||||
view.bounds = CGRect(origin: .zero, size: emptyResultsAnimationFrame.size)
|
view.bounds = CGRect(origin: .zero, size: emptyResultsAnimationFrame.size)
|
||||||
@ -419,7 +423,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
view.alpha = 0.0
|
view.alpha = 0.0
|
||||||
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||||
self.insertSubview(view, belowSubview: self.loadingNode.view)
|
self.insertSubview(view, belowSubview: self.loadingView)
|
||||||
}
|
}
|
||||||
view.bounds = CGRect(origin: .zero, size: emptyResultsTitleFrame.size)
|
view.bounds = CGRect(origin: .zero, size: emptyResultsTitleFrame.size)
|
||||||
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsTitleFrame.center)
|
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsTitleFrame.center)
|
||||||
@ -1206,12 +1210,12 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
self.updateScrolling(transition: transition)
|
self.updateScrolling(transition: transition)
|
||||||
|
|
||||||
if isLoading && self.showLoading {
|
if isLoading && self.showLoading {
|
||||||
self.loadingNode.update(size: availableSize, theme: environment.theme, showFilters: !showingFilters, transition: .immediate)
|
self.loadingView.update(size: availableSize, theme: environment.theme, showFilters: !showingFilters, transition: .immediate)
|
||||||
loadingTransition.setAlpha(view: self.loadingNode.view, alpha: 1.0)
|
loadingTransition.setAlpha(view: self.loadingView, alpha: 1.0)
|
||||||
} else {
|
} else {
|
||||||
loadingTransition.setAlpha(view: self.loadingNode.view, alpha: 0.0)
|
loadingTransition.setAlpha(view: self.loadingView, alpha: 0.0)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: self.loadingNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight), size: availableSize))
|
transition.setFrame(view: self.loadingView, frame: CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight), size: availableSize))
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,195 +0,0 @@
|
|||||||
import UIKit
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import Display
|
|
||||||
import ComponentFlow
|
|
||||||
import TelegramPresentationData
|
|
||||||
|
|
||||||
private final class SearchShimmerEffectNode: ASDisplayNode {
|
|
||||||
private var currentBackgroundColor: UIColor?
|
|
||||||
private var currentForegroundColor: UIColor?
|
|
||||||
private let imageNodeContainer: ASDisplayNode
|
|
||||||
private let imageNode: ASImageNode
|
|
||||||
|
|
||||||
private var absoluteLocation: (CGRect, CGSize)?
|
|
||||||
private var isCurrentlyInHierarchy = false
|
|
||||||
private var shouldBeAnimating = false
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
self.imageNodeContainer = ASDisplayNode()
|
|
||||||
self.imageNodeContainer.isLayerBacked = true
|
|
||||||
|
|
||||||
self.imageNode = ASImageNode()
|
|
||||||
self.imageNode.isLayerBacked = true
|
|
||||||
self.imageNode.displaysAsynchronously = false
|
|
||||||
self.imageNode.displayWithoutProcessing = true
|
|
||||||
self.imageNode.contentMode = .scaleToFill
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.isLayerBacked = true
|
|
||||||
self.clipsToBounds = true
|
|
||||||
|
|
||||||
self.imageNodeContainer.addSubnode(self.imageNode)
|
|
||||||
self.addSubnode(self.imageNodeContainer)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didEnterHierarchy() {
|
|
||||||
super.didEnterHierarchy()
|
|
||||||
|
|
||||||
self.isCurrentlyInHierarchy = true
|
|
||||||
self.updateAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didExitHierarchy() {
|
|
||||||
super.didExitHierarchy()
|
|
||||||
|
|
||||||
self.isCurrentlyInHierarchy = false
|
|
||||||
self.updateAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
|
||||||
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.argb == backgroundColor.argb, let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.argb == foregroundColor.argb {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.currentBackgroundColor = backgroundColor
|
|
||||||
self.currentForegroundColor = foregroundColor
|
|
||||||
|
|
||||||
self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
|
|
||||||
context.setFillColor(backgroundColor.cgColor)
|
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
|
||||||
let peakColor = foregroundColor.cgColor
|
|
||||||
|
|
||||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
|
||||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
|
||||||
|
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
||||||
|
|
||||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
|
||||||
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
|
||||||
let frameUpdated = self.absoluteLocation?.0 != rect
|
|
||||||
self.absoluteLocation = (rect, containerSize)
|
|
||||||
|
|
||||||
if sizeUpdated {
|
|
||||||
if self.shouldBeAnimating {
|
|
||||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
|
||||||
self.addImageAnimation()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if frameUpdated {
|
|
||||||
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.updateAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateAnimation() {
|
|
||||||
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
|
||||||
if shouldBeAnimating != self.shouldBeAnimating {
|
|
||||||
self.shouldBeAnimating = shouldBeAnimating
|
|
||||||
if shouldBeAnimating {
|
|
||||||
self.addImageAnimation()
|
|
||||||
} else {
|
|
||||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func addImageAnimation() {
|
|
||||||
guard let containerSize = self.absoluteLocation?.1 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let gradientHeight: CGFloat = 250.0
|
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
|
|
||||||
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
|
||||||
animation.repeatCount = Float.infinity
|
|
||||||
animation.beginTime = 1.0
|
|
||||||
self.imageNode.layer.add(animation, forKey: "shimmer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final class LoadingShimmerNode: ASDisplayNode {
|
|
||||||
private let backgroundColorNode: ASDisplayNode
|
|
||||||
private let effectNode: SearchShimmerEffectNode
|
|
||||||
private let maskNode: ASImageNode
|
|
||||||
private var currentParams: (size: CGSize, theme: PresentationTheme, showFilters: Bool)?
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
self.backgroundColorNode = ASDisplayNode()
|
|
||||||
self.effectNode = SearchShimmerEffectNode()
|
|
||||||
self.maskNode = ASImageNode()
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.allowsGroupOpacity = true
|
|
||||||
self.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
self.addSubnode(self.backgroundColorNode)
|
|
||||||
self.addSubnode(self.effectNode)
|
|
||||||
self.addSubnode(self.maskNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(size: CGSize, theme: PresentationTheme, showFilters: Bool, transition: ContainedViewLayoutTransition) {
|
|
||||||
let color = theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85)
|
|
||||||
|
|
||||||
if self.currentParams?.size != size || self.currentParams?.theme !== theme || self.currentParams?.showFilters != showFilters {
|
|
||||||
self.currentParams = (size, theme, showFilters)
|
|
||||||
|
|
||||||
self.backgroundColorNode.backgroundColor = color
|
|
||||||
|
|
||||||
self.maskNode.image = generateImage(size, rotatedContext: { size, context in
|
|
||||||
context.setFillColor(theme.list.blocksBackgroundColor.cgColor)
|
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
|
||||||
|
|
||||||
if showFilters {
|
|
||||||
let filterSpacing: CGFloat = 6.0
|
|
||||||
let filterWidth = (size.width - sideInset * 2.0 - filterSpacing * 3.0) / 4.0
|
|
||||||
for i in 0 ..< 4 {
|
|
||||||
context.addPath(CGPath(roundedRect: CGRect(origin: CGPoint(x: sideInset + (filterWidth + filterSpacing) * CGFloat(i), y: 0.0), size: CGSize(width: filterWidth, height: 28.0)), cornerWidth: 14.0, cornerHeight: 14.0, transform: nil))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentY: CGFloat = 39.0 + 7.0
|
|
||||||
var rowIndex: Int = 0
|
|
||||||
|
|
||||||
let optionSpacing: CGFloat = 10.0
|
|
||||||
let optionWidth = (size.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
|
||||||
let itemSize = CGSize(width: optionWidth, height: 154.0)
|
|
||||||
|
|
||||||
context.setBlendMode(.copy)
|
|
||||||
context.setFillColor(UIColor.clear.cgColor)
|
|
||||||
|
|
||||||
while currentY < size.height {
|
|
||||||
for i in 0 ..< 3 {
|
|
||||||
let itemOrigin = CGPoint(x: sideInset + CGFloat(i) * (itemSize.width + optionSpacing), y: 39.0 + 9.0 + CGFloat(rowIndex) * (itemSize.height + optionSpacing))
|
|
||||||
context.addPath(CGPath(roundedRect: CGRect(origin: itemOrigin, size: itemSize), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil))
|
|
||||||
}
|
|
||||||
currentY += itemSize.height
|
|
||||||
rowIndex += 1
|
|
||||||
}
|
|
||||||
context.fillPath()
|
|
||||||
})
|
|
||||||
|
|
||||||
self.effectNode.update(backgroundColor: color, foregroundColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4))
|
|
||||||
self.effectNode.updateAbsoluteRect(CGRect(origin: CGPoint(), size: size), within: size)
|
|
||||||
}
|
|
||||||
transition.updateFrame(node: self.backgroundColorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
|
||||||
transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
|
||||||
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -527,7 +527,7 @@ public final class ListSectionComponent: Component {
|
|||||||
containerSize: CGSize(width: availableSize.width - headerSideInset * 2.0, height: availableSize.height)
|
containerSize: CGSize(width: availableSize.width - headerSideInset * 2.0, height: availableSize.height)
|
||||||
)
|
)
|
||||||
if contentHeight != 0.0 {
|
if contentHeight != 0.0 {
|
||||||
contentHeight += 7.0
|
contentHeight += 8.0 - UIScreenPixel
|
||||||
}
|
}
|
||||||
if let footerView = footer.view {
|
if let footerView = footer.view {
|
||||||
if footerView.superview == nil {
|
if footerView.superview == nil {
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "CollectionTabItemComponent",
|
||||||
|
module_name = "CollectionTabItemComponent",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/AccountContext",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/TextFormat",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/BundleIconComponent",
|
||||||
|
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,168 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import TelegramCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import MultilineTextComponent
|
||||||
|
import BundleIconComponent
|
||||||
|
import TabSelectorComponent
|
||||||
|
import EmojiTextAttachmentView
|
||||||
|
import TextFormat
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
|
public final class CollectionTabItemComponent: Component {
|
||||||
|
public typealias EnvironmentType = TabSelectorComponent.ItemEnvironment
|
||||||
|
|
||||||
|
public enum Icon: Equatable {
|
||||||
|
case collection(TelegramMediaFile)
|
||||||
|
case add
|
||||||
|
}
|
||||||
|
|
||||||
|
public let context: AccountContext
|
||||||
|
public let icon: Icon?
|
||||||
|
public let title: String
|
||||||
|
public let theme: PresentationTheme
|
||||||
|
|
||||||
|
public init(
|
||||||
|
context: AccountContext,
|
||||||
|
icon: Icon?,
|
||||||
|
title: String,
|
||||||
|
theme: PresentationTheme
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.icon = icon
|
||||||
|
self.title = title
|
||||||
|
self.theme = theme
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: CollectionTabItemComponent, rhs: CollectionTabItemComponent) -> Bool {
|
||||||
|
if lhs.icon != rhs.icon {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.title != rhs.title {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView {
|
||||||
|
private let title = ComponentView<Empty>()
|
||||||
|
private let icon = ComponentView<Empty>()
|
||||||
|
private var iconLayer: InlineStickerItemLayer?
|
||||||
|
|
||||||
|
private var component: CollectionTabItemComponent?
|
||||||
|
|
||||||
|
func update(component: CollectionTabItemComponent, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
let environment = environment[EnvironmentType.self].value
|
||||||
|
|
||||||
|
let iconSpacing: CGFloat = 3.0
|
||||||
|
|
||||||
|
let normalColor = component.theme.list.itemSecondaryTextColor
|
||||||
|
let selectedColor = component.theme.list.freeTextColor
|
||||||
|
let effectiveColor = normalColor.mixedWith(selectedColor, alpha: environment.selectionFraction)
|
||||||
|
|
||||||
|
let titleSize = self.title.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: effectiveColor))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
var iconOffset: CGFloat = 0.0
|
||||||
|
var iconSize = CGSize()
|
||||||
|
if let icon = component.icon {
|
||||||
|
switch icon {
|
||||||
|
case let .collection(file):
|
||||||
|
iconSize = CGSize(width: 16.0, height: 16.0)
|
||||||
|
|
||||||
|
let iconLayer: InlineStickerItemLayer
|
||||||
|
if let current = self.iconLayer {
|
||||||
|
iconLayer = current
|
||||||
|
} else {
|
||||||
|
iconLayer = InlineStickerItemLayer(
|
||||||
|
context: component.context,
|
||||||
|
userLocation: .other,
|
||||||
|
attemptSynchronousLoad: true,
|
||||||
|
emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file),
|
||||||
|
file: file,
|
||||||
|
cache: component.context.animationCache,
|
||||||
|
renderer: component.context.animationRenderer,
|
||||||
|
placeholderColor: component.theme.list.mediaPlaceholderColor,
|
||||||
|
pointSize: iconSize,
|
||||||
|
loopCount: 1
|
||||||
|
)
|
||||||
|
self.layer.addSublayer(iconLayer)
|
||||||
|
self.iconLayer = iconLayer
|
||||||
|
}
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: iconOffset, y: floorToScreenPixels((titleSize.height - iconSize.height) * 0.5)), size: iconSize)
|
||||||
|
iconLayer.frame = iconFrame
|
||||||
|
case .add:
|
||||||
|
iconSize = self.icon.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(BundleIconComponent(
|
||||||
|
name: "Chat/Input/Media/PanelBadgeAdd",
|
||||||
|
tintColor: component.theme.list.itemSecondaryTextColor
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
|
)
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: iconOffset, y: floorToScreenPixels((titleSize.height - iconSize.height) * 0.5)), size: iconSize)
|
||||||
|
if let iconView = self.icon.view {
|
||||||
|
if iconView.superview == nil {
|
||||||
|
iconView.isUserInteractionEnabled = false
|
||||||
|
self.addSubview(iconView)
|
||||||
|
}
|
||||||
|
iconView.frame = iconFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iconOffset += iconSize.width + iconSpacing
|
||||||
|
} else {
|
||||||
|
if let iconLayer = self.iconLayer {
|
||||||
|
self.iconLayer = nil
|
||||||
|
iconLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||||
|
iconLayer.removeFromSuperlayer()
|
||||||
|
})
|
||||||
|
iconLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
if let iconView = self.icon.view {
|
||||||
|
iconView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: iconOffset, y: 0.0), size: titleSize)
|
||||||
|
if let titleView = self.title.view {
|
||||||
|
if titleView.superview == nil {
|
||||||
|
titleView.isUserInteractionEnabled = false
|
||||||
|
self.addSubview(titleView)
|
||||||
|
}
|
||||||
|
titleView.frame = titleFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
let size: CGSize
|
||||||
|
if let _ = component.icon {
|
||||||
|
size = CGSize(width: iconSize.width + iconSpacing + titleSize.width, height: titleSize.height)
|
||||||
|
} else {
|
||||||
|
size = titleSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -58,6 +58,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/BottomButtonPanelComponent",
|
"//submodules/TelegramUI/Components/BottomButtonPanelComponent",
|
||||||
"//submodules/PromptUI",
|
"//submodules/PromptUI",
|
||||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||||
|
"//submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent"
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import BundleIconComponent
|
|||||||
import EmojiTextAttachmentView
|
import EmojiTextAttachmentView
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import PromptUI
|
import PromptUI
|
||||||
|
import CollectionTabItemComponent
|
||||||
|
|
||||||
public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate {
|
public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate {
|
||||||
public enum GiftCollection: Equatable {
|
public enum GiftCollection: Equatable {
|
||||||
@ -94,12 +95,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
private var panelSeparator: ASDisplayNode?
|
private var panelSeparator: ASDisplayNode?
|
||||||
private var panelButton: ComponentView<Empty>?
|
private var panelButton: ComponentView<Empty>?
|
||||||
private var panelCheck: ComponentView<Empty>?
|
private var panelCheck: ComponentView<Empty>?
|
||||||
|
|
||||||
private let emptyResultsClippingView = UIView()
|
|
||||||
private let emptyResultsAnimation = ComponentView<Empty>()
|
|
||||||
private let emptyResultsTitle = ComponentView<Empty>()
|
|
||||||
private let emptyResultsAction = ComponentView<Empty>()
|
|
||||||
|
|
||||||
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)?
|
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)?
|
||||||
|
|
||||||
private var theme: PresentationTheme?
|
private var theme: PresentationTheme?
|
||||||
@ -1479,162 +1475,6 @@ private func cancelContextGestures(view: UIView) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class CollectionTabItemComponent: Component {
|
|
||||||
typealias EnvironmentType = TabSelectorComponent.ItemEnvironment
|
|
||||||
|
|
||||||
enum Icon: Equatable {
|
|
||||||
case collection(TelegramMediaFile)
|
|
||||||
case add
|
|
||||||
}
|
|
||||||
|
|
||||||
let context: AccountContext
|
|
||||||
let icon: Icon?
|
|
||||||
let title: String
|
|
||||||
let theme: PresentationTheme
|
|
||||||
|
|
||||||
init(
|
|
||||||
context: AccountContext,
|
|
||||||
icon: Icon?,
|
|
||||||
title: String,
|
|
||||||
theme: PresentationTheme
|
|
||||||
) {
|
|
||||||
self.context = context
|
|
||||||
self.icon = icon
|
|
||||||
self.title = title
|
|
||||||
self.theme = theme
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: CollectionTabItemComponent, rhs: CollectionTabItemComponent) -> Bool {
|
|
||||||
if lhs.icon != rhs.icon {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.title != rhs.title {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.theme !== rhs.theme {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
final class View: UIView {
|
|
||||||
private let title = ComponentView<Empty>()
|
|
||||||
private let icon = ComponentView<Empty>()
|
|
||||||
private var iconLayer: InlineStickerItemLayer?
|
|
||||||
|
|
||||||
private var component: CollectionTabItemComponent?
|
|
||||||
|
|
||||||
func update(component: CollectionTabItemComponent, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
|
||||||
self.component = component
|
|
||||||
|
|
||||||
let environment = environment[EnvironmentType.self].value
|
|
||||||
|
|
||||||
let iconSpacing: CGFloat = 3.0
|
|
||||||
|
|
||||||
let normalColor = component.theme.list.itemSecondaryTextColor
|
|
||||||
let selectedColor = component.theme.list.freeTextColor
|
|
||||||
let effectiveColor = normalColor.mixedWith(selectedColor, alpha: environment.selectionFraction)
|
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: effectiveColor))
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
var iconOffset: CGFloat = 0.0
|
|
||||||
var iconSize = CGSize()
|
|
||||||
if let icon = component.icon {
|
|
||||||
switch icon {
|
|
||||||
case let .collection(file):
|
|
||||||
iconSize = CGSize(width: 16.0, height: 16.0)
|
|
||||||
|
|
||||||
let iconLayer: InlineStickerItemLayer
|
|
||||||
if let current = self.iconLayer {
|
|
||||||
iconLayer = current
|
|
||||||
} else {
|
|
||||||
iconLayer = InlineStickerItemLayer(
|
|
||||||
context: component.context,
|
|
||||||
userLocation: .other,
|
|
||||||
attemptSynchronousLoad: true,
|
|
||||||
emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file),
|
|
||||||
file: file,
|
|
||||||
cache: component.context.animationCache,
|
|
||||||
renderer: component.context.animationRenderer,
|
|
||||||
placeholderColor: component.theme.list.mediaPlaceholderColor,
|
|
||||||
pointSize: iconSize,
|
|
||||||
loopCount: 1
|
|
||||||
)
|
|
||||||
self.layer.addSublayer(iconLayer)
|
|
||||||
self.iconLayer = iconLayer
|
|
||||||
}
|
|
||||||
let iconFrame = CGRect(origin: CGPoint(x: iconOffset, y: floorToScreenPixels((titleSize.height - iconSize.height) * 0.5)), size: iconSize)
|
|
||||||
iconLayer.frame = iconFrame
|
|
||||||
case .add:
|
|
||||||
iconSize = self.icon.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(BundleIconComponent(
|
|
||||||
name: "Chat/Input/Media/PanelBadgeAdd",
|
|
||||||
tintColor: component.theme.list.itemSecondaryTextColor
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
|
||||||
)
|
|
||||||
let iconFrame = CGRect(origin: CGPoint(x: iconOffset, y: floorToScreenPixels((titleSize.height - iconSize.height) * 0.5)), size: iconSize)
|
|
||||||
if let iconView = self.icon.view {
|
|
||||||
if iconView.superview == nil {
|
|
||||||
iconView.isUserInteractionEnabled = false
|
|
||||||
self.addSubview(iconView)
|
|
||||||
}
|
|
||||||
iconView.frame = iconFrame
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iconOffset += iconSize.width + iconSpacing
|
|
||||||
} else {
|
|
||||||
if let iconLayer = self.iconLayer {
|
|
||||||
self.iconLayer = nil
|
|
||||||
iconLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
|
||||||
iconLayer.removeFromSuperlayer()
|
|
||||||
})
|
|
||||||
iconLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
|
||||||
}
|
|
||||||
if let iconView = self.icon.view {
|
|
||||||
iconView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: iconOffset, y: 0.0), size: titleSize)
|
|
||||||
if let titleView = self.title.view {
|
|
||||||
if titleView.superview == nil {
|
|
||||||
titleView.isUserInteractionEnabled = false
|
|
||||||
self.addSubview(titleView)
|
|
||||||
}
|
|
||||||
titleView.frame = titleFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
let size: CGSize
|
|
||||||
if let _ = component.icon {
|
|
||||||
size = CGSize(width: iconSize.width + iconSpacing + titleSize.width, height: titleSize.height)
|
|
||||||
} else {
|
|
||||||
size = titleSize
|
|
||||||
}
|
|
||||||
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeView() -> View {
|
|
||||||
return View(frame: CGRect())
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
|
||||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||||
let controller: ViewController
|
let controller: ViewController
|
||||||
weak var sourceView: UIView?
|
weak var sourceView: UIView?
|
||||||
|
|||||||
@ -39,6 +39,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||||
"//submodules/TelegramUI/Components/Settings/ThemeCarouselItem",
|
"//submodules/TelegramUI/Components/Settings/ThemeCarouselItem",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/BalancedTextComponent",
|
||||||
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
|
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
|
||||||
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
|
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
|
||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
@ -55,6 +56,10 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
"//submodules/TextFormat",
|
"//submodules/TextFormat",
|
||||||
|
"//submodules/TelegramUI/Components/PeerInfo/CollectionTabItemComponent",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
|
"//submodules/TelegramUI/Components/Gifts/GiftItemComponent",
|
||||||
|
"//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView"
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -1065,6 +1065,7 @@ final class ChannelAppearanceScreenComponent: Component {
|
|||||||
componentTheme: environment.theme,
|
componentTheme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
topInset: environment.statusBarHeight,
|
topInset: environment.statusBarHeight,
|
||||||
|
bottomInset: 0.0,
|
||||||
sectionId: 0,
|
sectionId: 0,
|
||||||
peer: peer,
|
peer: peer,
|
||||||
subtitleString: contentsData.subscriberCount.flatMap {
|
subtitleString: contentsData.subscriberCount.flatMap {
|
||||||
|
|||||||
@ -2,16 +2,24 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import SwiftSignalKit
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import GiftItemComponent
|
import GiftItemComponent
|
||||||
import PlainButtonComponent
|
import PlainButtonComponent
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import TabSelectorComponent
|
||||||
|
import CollectionTabItemComponent
|
||||||
|
import LottieComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
import BalancedTextComponent
|
||||||
|
import GiftLoadingShimmerView
|
||||||
|
|
||||||
final class GiftListItemComponent: Component {
|
final class GiftListItemComponent: Component {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let gifts: [StarGift.UniqueGift]
|
let gifts: [StarGift.UniqueGift]
|
||||||
|
let starGifts: [StarGift]
|
||||||
let selectedId: Int64?
|
let selectedId: Int64?
|
||||||
let selectionUpdated: (StarGift.UniqueGift) -> Void
|
let selectionUpdated: (StarGift.UniqueGift) -> Void
|
||||||
let tag: AnyObject?
|
let tag: AnyObject?
|
||||||
@ -20,6 +28,7 @@ final class GiftListItemComponent: Component {
|
|||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
gifts: [StarGift.UniqueGift],
|
gifts: [StarGift.UniqueGift],
|
||||||
|
starGifts: [StarGift],
|
||||||
selectedId: Int64?,
|
selectedId: Int64?,
|
||||||
selectionUpdated: @escaping (StarGift.UniqueGift) -> Void,
|
selectionUpdated: @escaping (StarGift.UniqueGift) -> Void,
|
||||||
tag: AnyObject?
|
tag: AnyObject?
|
||||||
@ -27,6 +36,7 @@ final class GiftListItemComponent: Component {
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.gifts = gifts
|
self.gifts = gifts
|
||||||
|
self.starGifts = starGifts
|
||||||
self.selectedId = selectedId
|
self.selectedId = selectedId
|
||||||
self.selectionUpdated = selectionUpdated
|
self.selectionUpdated = selectionUpdated
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
@ -39,6 +49,9 @@ final class GiftListItemComponent: Component {
|
|||||||
if lhs.gifts != rhs.gifts {
|
if lhs.gifts != rhs.gifts {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.starGifts != rhs.starGifts {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.selectedId != rhs.selectedId {
|
if lhs.selectedId != rhs.selectedId {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -59,11 +72,30 @@ final class GiftListItemComponent: Component {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let tabSelector = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var selectedGiftId: Int64 = 0
|
||||||
|
private var resaleGiftsContexts: [Int64: ResaleGiftsContext] = [:]
|
||||||
|
private var resaleGiftsState: ResaleGiftsContext.State?
|
||||||
|
private var resaleGiftsDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private let emptyResultsAnimation = ComponentView<Empty>()
|
||||||
|
private let emptyResultsText = ComponentView<Empty>()
|
||||||
|
private let emptyResultsAction = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private let loadingView = GiftLoadingShimmerView()
|
||||||
|
|
||||||
private var giftItems: [AnyHashable: ComponentView<Empty>] = [:]
|
private var giftItems: [AnyHashable: ComponentView<Empty>] = [:]
|
||||||
|
|
||||||
|
private(set) var visibleBounds: CGRect?
|
||||||
|
|
||||||
|
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||||
|
|
||||||
private var component: GiftListItemComponent?
|
private var component: GiftListItemComponent?
|
||||||
private var state: EmptyComponentState?
|
private var state: EmptyComponentState?
|
||||||
|
|
||||||
|
private var isUpdating: Bool = false
|
||||||
|
|
||||||
override public init(frame: CGRect) {
|
override public init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
}
|
}
|
||||||
@ -72,30 +104,288 @@ final class GiftListItemComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
private var visibleBounds: CGRect?
|
deinit {
|
||||||
|
self.resaleGiftsDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
func updateVisibleBounds(_ bounds: CGRect) {
|
func updateVisibleBounds(_ bounds: CGRect) {
|
||||||
self.visibleBounds = bounds
|
self.visibleBounds = bounds
|
||||||
self.state?.updated()
|
if !self.isUpdating {
|
||||||
|
self.state?.updated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMore() -> Bool {
|
||||||
|
guard self.selectedGiftId != 0 else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let resaleGiftsContext = self.resaleGiftsContexts[self.selectedGiftId] {
|
||||||
|
resaleGiftsContext.loadMore()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSelectedGift(id: Int64) {
|
||||||
|
guard let component = self.component, self.selectedGiftId != id else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.selectedGiftId = id
|
||||||
|
|
||||||
|
if id == 0 {
|
||||||
|
self.resaleGiftsState = nil
|
||||||
|
self.resaleGiftsDisposable.set(nil)
|
||||||
|
} else {
|
||||||
|
let resaleGiftsContext: ResaleGiftsContext
|
||||||
|
if let current = self.resaleGiftsContexts[id] {
|
||||||
|
resaleGiftsContext = current
|
||||||
|
} else {
|
||||||
|
resaleGiftsContext = ResaleGiftsContext(account: component.context.account, giftId: id)
|
||||||
|
self.resaleGiftsContexts[id] = resaleGiftsContext
|
||||||
|
}
|
||||||
|
|
||||||
|
var isFirstTime = true
|
||||||
|
self.resaleGiftsDisposable.set((resaleGiftsContext.state
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.resaleGiftsState = state
|
||||||
|
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: isFirstTime ? .easeInOut(duration: 0.25) : .immediate)
|
||||||
|
}
|
||||||
|
isFirstTime = false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .easeInOut(duration: 0.25))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(component: GiftListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: GiftListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.isUpdating = true
|
||||||
|
defer {
|
||||||
|
self.isUpdating = false
|
||||||
|
}
|
||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
|
||||||
let topInset: CGFloat = 13.0
|
|
||||||
let spacing: CGFloat = 10.0
|
|
||||||
let itemsInRow = 3
|
|
||||||
let rowsCount = Int(ceil(CGFloat(component.gifts.count) / CGFloat(itemsInRow)))
|
|
||||||
|
|
||||||
let itemWidth = floorToScreenPixels((availableSize.width - sideInset * 2.0 - spacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow))
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let sideInset: CGFloat = self.selectedGiftId != 0 ? 18.0 : 16.0
|
||||||
|
let edgeInset: CGFloat = 16.0
|
||||||
|
var topInset: CGFloat = edgeInset
|
||||||
|
let columnSpacing: CGFloat = self.selectedGiftId != 0 ? 14.0 : 10.0
|
||||||
|
let rowSpacing: CGFloat = 10.0
|
||||||
|
let itemsInRow = 3
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
var tabSelectorItems: [TabSelectorComponent.Item] = []
|
||||||
|
tabSelectorItems.append(TabSelectorComponent.Item(
|
||||||
|
id: AnyHashable(Int64(0)),
|
||||||
|
title: "My Gifts"
|
||||||
|
))
|
||||||
|
|
||||||
|
for gift in component.starGifts {
|
||||||
|
guard case let .generic(gift) = gift, let title = gift.title else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tabSelectorItems.append(TabSelectorComponent.Item(
|
||||||
|
id: AnyHashable(gift.id),
|
||||||
|
content: .component(AnyComponent(
|
||||||
|
CollectionTabItemComponent(
|
||||||
|
context: component.context,
|
||||||
|
icon: .collection(gift.file),
|
||||||
|
title: title,
|
||||||
|
theme: component.theme
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
isReorderable: false,
|
||||||
|
contextAction: nil
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
let tabSelectorSize = self.tabSelector.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(TabSelectorComponent(
|
||||||
|
context: component.context,
|
||||||
|
colors: TabSelectorComponent.Colors(
|
||||||
|
foreground: component.theme.list.itemSecondaryTextColor,
|
||||||
|
selection: component.theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15),
|
||||||
|
simple: true
|
||||||
|
),
|
||||||
|
theme: component.theme,
|
||||||
|
customLayout: TabSelectorComponent.CustomLayout(
|
||||||
|
font: Font.medium(14.0),
|
||||||
|
spacing: 2.0
|
||||||
|
),
|
||||||
|
items: tabSelectorItems,
|
||||||
|
selectedId: AnyHashable(self.selectedGiftId),
|
||||||
|
reorderItem: nil,
|
||||||
|
setSelectedId: { [weak self] id in
|
||||||
|
guard let self, let idValue = id.base as? Int64 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.setSelectedGift(id: idValue)
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - 14.0 * 2.0, height: 50.0)
|
||||||
|
)
|
||||||
|
if let tabSelectorView = self.tabSelector.view {
|
||||||
|
if tabSelectorView.superview == nil {
|
||||||
|
tabSelectorView.alpha = 1.0
|
||||||
|
self.insertSubview(tabSelectorView, at: 0)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabSelectorSize.width) / 2.0), y: topInset), size: tabSelectorSize))
|
||||||
|
|
||||||
|
topInset += tabSelectorSize.height + 16.0
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectiveGifts: [StarGift.UniqueGift] = []
|
||||||
|
var isLoading = false
|
||||||
|
if self.selectedGiftId == 0 {
|
||||||
|
effectiveGifts = component.gifts
|
||||||
|
} else if let resaleGiftsState = self.resaleGiftsState {
|
||||||
|
var uniqueGifts: [StarGift.UniqueGift] = []
|
||||||
|
for gift in resaleGiftsState.gifts {
|
||||||
|
if case let .unique(uniqueGift) = gift {
|
||||||
|
uniqueGifts.append(uniqueGift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
effectiveGifts = uniqueGifts
|
||||||
|
|
||||||
|
if effectiveGifts.isEmpty, case .loading = resaleGiftsState.dataState {
|
||||||
|
isLoading = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rowsCount = Int(ceil(CGFloat(effectiveGifts.count) / CGFloat(itemsInRow)))
|
||||||
|
let itemWidth = floorToScreenPixels((availableSize.width - sideInset * 2.0 - columnSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow))
|
||||||
|
let itemHeight: CGFloat = self.selectedGiftId == 0 ? itemWidth : 154.0
|
||||||
var validIds: [AnyHashable] = []
|
var validIds: [AnyHashable] = []
|
||||||
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: itemWidth, height: itemWidth))
|
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: itemWidth, height: itemHeight))
|
||||||
|
|
||||||
|
var contentHeight = topInset + edgeInset + itemHeight * CGFloat(rowsCount) + rowSpacing * CGFloat(rowsCount - 1)
|
||||||
|
|
||||||
let contentHeight = topInset * 2.0 + itemWidth * CGFloat(rowsCount) + spacing * CGFloat(rowsCount - 1)
|
let fadeTransition: ComponentTransition = .easeInOut(duration: 0.25)
|
||||||
|
if self.selectedGiftId == 0 && effectiveGifts.isEmpty {
|
||||||
|
let emptyTextSpacing: CGFloat = 16.0
|
||||||
|
let emptyAnimationHeight: CGFloat = 100.0
|
||||||
|
|
||||||
|
let emptyResultsAnimationSize = self.emptyResultsAnimation.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(LottieComponent(
|
||||||
|
content: LottieComponent.AppBundleContent(name: "Style")
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: emptyAnimationHeight, height: emptyAnimationHeight)
|
||||||
|
)
|
||||||
|
//TODO:localize
|
||||||
|
let emptyResultsTextSize = self.emptyResultsText.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
BalancedTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "You don't have any gifts you can use as styles for your profile.", font: Font.regular(13.0), textColor: component.theme.list.itemSecondaryTextColor)),
|
||||||
|
horizontalAlignment: .center,
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== component.theme {
|
||||||
|
self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: component.theme.list.itemAccentColor)!, component.theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttonAttributedString = NSMutableAttributedString(string: "Browse gifts available for purchase >", font: Font.regular(15.0), textColor: component.theme.list.itemAccentColor, paragraphAlignment: .center)
|
||||||
|
if let range = buttonAttributedString.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
|
||||||
|
buttonAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
|
}
|
||||||
|
|
||||||
|
let emptyResultsActionSize = self.emptyResultsAction.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
PlainButtonComponent(
|
||||||
|
content: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(buttonAttributedString),
|
||||||
|
horizontalAlignment: .center,
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
)),
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if case let .generic(gift) = component.starGifts.first {
|
||||||
|
self.setSelectedGift(id: gift.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animateScale: false
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 3.0, height: 50.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
let emptyTotalHeight = emptyResultsAnimationSize.height + emptyTextSpacing + emptyResultsTextSize.height + emptyTextSpacing + emptyResultsActionSize.height
|
||||||
|
let emptyAnimationY = topInset
|
||||||
|
|
||||||
|
let emptyResultsAnimationFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - emptyResultsAnimationSize.width) / 2.0), y: emptyAnimationY), size: emptyResultsAnimationSize)
|
||||||
|
let emptyResultsTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - emptyResultsTextSize.width) / 2.0), y: emptyResultsAnimationFrame.maxY + emptyTextSpacing), size: emptyResultsTextSize)
|
||||||
|
let emptyResultsActionFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - emptyResultsActionSize.width) / 2.0), y: emptyResultsTextFrame.maxY + emptyTextSpacing), size: emptyResultsActionSize)
|
||||||
|
|
||||||
|
if let view = self.emptyResultsAnimation.view as? LottieComponent.View {
|
||||||
|
if view.superview == nil {
|
||||||
|
view.alpha = 0.0
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||||
|
self.addSubview(view)
|
||||||
|
view.playOnce()
|
||||||
|
}
|
||||||
|
view.frame = emptyResultsAnimationFrame
|
||||||
|
}
|
||||||
|
if let view = self.emptyResultsText.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
view.alpha = 0.0
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||||
|
self.addSubview(view)
|
||||||
|
}
|
||||||
|
view.frame = emptyResultsTextFrame
|
||||||
|
}
|
||||||
|
if let view = self.emptyResultsAction.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
view.alpha = 0.0
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||||
|
self.addSubview(view)
|
||||||
|
}
|
||||||
|
view.frame = emptyResultsActionFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
contentHeight = topInset + emptyTotalHeight + 21.0
|
||||||
|
} else {
|
||||||
|
if let view = self.emptyResultsAnimation.view {
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
||||||
|
view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if let view = self.emptyResultsText.view {
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
||||||
|
view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if let view = self.emptyResultsAction.view {
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
||||||
|
view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var index: Int32 = 0
|
var index: Int32 = 0
|
||||||
for gift in component.gifts {
|
for gift in effectiveGifts {
|
||||||
var isVisible = false
|
var isVisible = false
|
||||||
if let visibleBounds = self.visibleBounds, visibleBounds.intersects(itemFrame) {
|
if let visibleBounds = self.visibleBounds, visibleBounds.intersects(itemFrame) {
|
||||||
isVisible = true
|
isVisible = true
|
||||||
@ -115,6 +405,27 @@ final class GiftListItemComponent: Component {
|
|||||||
itemTransition = .immediate
|
itemTransition = .immediate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let subject: GiftItemComponent.Subject = .uniqueGift(
|
||||||
|
gift: gift,
|
||||||
|
price: self.selectedGiftId != 0 ? "# \(presentationStringsFormattedNumber(Int32(gift.resellAmounts?.first(where: { $0.currency == .stars })?.amount.value ?? 0), presentationData.dateTimeFormat.groupingSeparator))" : nil
|
||||||
|
)
|
||||||
|
|
||||||
|
var ribbon: GiftItemComponent.Ribbon?
|
||||||
|
if self.selectedGiftId != 0 {
|
||||||
|
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
|
||||||
|
for attribute in gift.attributes {
|
||||||
|
if case let .backdrop(_, _, innerColor, outerColor, _, _, _) = attribute {
|
||||||
|
ribbonColor = .custom(outerColor, innerColor)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ribbon = GiftItemComponent.Ribbon(
|
||||||
|
text: "#\(gift.number)",
|
||||||
|
font: .monospaced,
|
||||||
|
color: ribbonColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let _ = visibleItem.update(
|
let _ = visibleItem.update(
|
||||||
transition: itemTransition,
|
transition: itemTransition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
@ -123,13 +434,13 @@ final class GiftListItemComponent: Component {
|
|||||||
GiftItemComponent(
|
GiftItemComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
strings: component.context.sharedContext.currentPresentationData.with { $0 }.strings,
|
strings: presentationData.strings,
|
||||||
peer: nil,
|
peer: nil,
|
||||||
subject: .uniqueGift(gift: gift, price: nil),
|
subject: subject,
|
||||||
ribbon: nil,
|
ribbon: ribbon,
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
isSelected: gift.id == component.selectedId,
|
isSelected: gift.id == component.selectedId,
|
||||||
mode: .grid
|
mode: self.selectedGiftId != 0 ? .generic : .grid
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
effectAlignment: .center,
|
effectAlignment: .center,
|
||||||
@ -147,7 +458,11 @@ final class GiftListItemComponent: Component {
|
|||||||
)
|
)
|
||||||
if let itemView = visibleItem.view {
|
if let itemView = visibleItem.view {
|
||||||
if itemView.superview == nil {
|
if itemView.superview == nil {
|
||||||
self.addSubview(itemView)
|
if self.loadingView.superview != nil {
|
||||||
|
self.insertSubview(itemView, at: self.subviews.count - 2)
|
||||||
|
} else {
|
||||||
|
self.insertSubview(itemView, at: self.subviews.count - 1)
|
||||||
|
}
|
||||||
|
|
||||||
if !transition.animation.isImmediate {
|
if !transition.animation.isImmediate {
|
||||||
itemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
|
itemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
|
||||||
@ -157,10 +472,10 @@ final class GiftListItemComponent: Component {
|
|||||||
itemTransition.setFrame(view: itemView, frame: itemFrame.insetBy(dx: -2.0, dy: -2.0))
|
itemTransition.setFrame(view: itemView, frame: itemFrame.insetBy(dx: -2.0, dy: -2.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
itemFrame.origin.x += itemFrame.width + spacing
|
itemFrame.origin.x += itemFrame.width + columnSpacing
|
||||||
if itemFrame.maxX > availableSize.width {
|
if itemFrame.maxX > availableSize.width {
|
||||||
itemFrame.origin.x = sideInset
|
itemFrame.origin.x = sideInset
|
||||||
itemFrame.origin.y += itemFrame.height + spacing
|
itemFrame.origin.y += itemFrame.height + rowSpacing
|
||||||
}
|
}
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
@ -185,6 +500,23 @@ final class GiftListItemComponent: Component {
|
|||||||
self.giftItems.removeValue(forKey: id)
|
self.giftItems.removeValue(forKey: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let loadingTransition: ComponentTransition = .easeInOut(duration: 0.25)
|
||||||
|
if isLoading {
|
||||||
|
if let tabSelectorView = self.tabSelector.view {
|
||||||
|
if self.subviews.last !== tabSelectorView || self.loadingView.superview == nil {
|
||||||
|
self.addSubview(self.loadingView)
|
||||||
|
self.addSubview(tabSelectorView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentHeight = 568.0
|
||||||
|
let loadingSize = CGSize(width: availableSize.width, height: contentHeight)
|
||||||
|
self.loadingView.update(size: loadingSize, theme: component.theme, isPlain: true, transition: .immediate)
|
||||||
|
transition.setFrame(view: self.loadingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset - 50.0), size: loadingSize))
|
||||||
|
loadingTransition.setAlpha(view: self.loadingView, alpha: 1.0)
|
||||||
|
} else {
|
||||||
|
loadingTransition.setAlpha(view: self.loadingView, alpha: 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
return CGSize(width: availableSize.width, height: contentHeight)
|
return CGSize(width: availableSize.width, height: contentHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem, ListIte
|
|||||||
let componentTheme: PresentationTheme
|
let componentTheme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let topInset: CGFloat
|
let topInset: CGFloat
|
||||||
|
let bottomInset: CGFloat
|
||||||
let sectionId: ItemListSectionId
|
let sectionId: ItemListSectionId
|
||||||
let peer: EnginePeer?
|
let peer: EnginePeer?
|
||||||
let subtitleString: String?
|
let subtitleString: String?
|
||||||
@ -31,12 +32,13 @@ final class PeerNameColorProfilePreviewItem: ListViewItem, ItemListItem, ListIte
|
|||||||
let nameDisplayOrder: PresentationPersonNameOrder
|
let nameDisplayOrder: PresentationPersonNameOrder
|
||||||
let showBackground: Bool
|
let showBackground: Bool
|
||||||
|
|
||||||
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, topInset: CGFloat, sectionId: ItemListSectionId, peer: EnginePeer?, subtitleString: String? = nil, files: [Int64: TelegramMediaFile], nameDisplayOrder: PresentationPersonNameOrder, showBackground: Bool) {
|
init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, topInset: CGFloat, bottomInset: CGFloat, sectionId: ItemListSectionId, peer: EnginePeer?, subtitleString: String? = nil, files: [Int64: TelegramMediaFile], nameDisplayOrder: PresentationPersonNameOrder, showBackground: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.componentTheme = componentTheme
|
self.componentTheme = componentTheme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.topInset = topInset
|
self.topInset = topInset
|
||||||
|
self.bottomInset = bottomInset
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.subtitleString = subtitleString
|
self.subtitleString = subtitleString
|
||||||
@ -150,7 +152,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
|||||||
return { [weak self] item, params, neighbors in
|
return { [weak self] item, params, neighbors in
|
||||||
let separatorHeight = UIScreenPixel
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
let contentSize = CGSize(width: params.width, height: 210.0 + item.topInset)
|
let contentSize = CGSize(width: params.width, height: 210.0 + item.topInset + item.bottomInset)
|
||||||
var insets = itemListNeighborsGroupedInsets(neighbors, params)
|
var insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||||
if params.width <= 320.0 {
|
if params.width <= 320.0 {
|
||||||
insets.top = 0.0
|
insets.top = 0.0
|
||||||
@ -169,7 +171,7 @@ final class PeerNameColorProfilePreviewItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
self.backgroundNode.backgroundColor = item.theme.rootController.navigationBar.opaqueBackgroundColor
|
self.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||||
self.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
self.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||||
self.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
self.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||||
|
|
||||||
|
|||||||
@ -35,12 +35,21 @@ import TabSelectorComponent
|
|||||||
import WallpaperResources
|
import WallpaperResources
|
||||||
import EdgeEffect
|
import EdgeEffect
|
||||||
import TextFormat
|
import TextFormat
|
||||||
|
import TelegramStringFormatting
|
||||||
|
|
||||||
private let giftListTag = GenericComponentViewTag()
|
private let giftListTag = GenericComponentViewTag()
|
||||||
|
|
||||||
final class UserAppearanceScreenComponent: Component {
|
final class UserAppearanceScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
|
public final class TransitionHint {
|
||||||
|
public let animateTabChange: Bool
|
||||||
|
|
||||||
|
public init(animateTabChange: Bool) {
|
||||||
|
self.animateTabChange = animateTabChange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -59,13 +68,16 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
private final class ContentsData {
|
private final class ContentsData {
|
||||||
let peer: EnginePeer?
|
let peer: EnginePeer?
|
||||||
let gifts: [StarGift.UniqueGift]
|
let gifts: [StarGift.UniqueGift]
|
||||||
|
let starGifts: [StarGift]
|
||||||
|
|
||||||
init(
|
init(
|
||||||
peer: EnginePeer?,
|
peer: EnginePeer?,
|
||||||
gifts: [StarGift.UniqueGift]
|
gifts: [StarGift.UniqueGift],
|
||||||
|
starGifts: [StarGift]
|
||||||
) {
|
) {
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.gifts = gifts
|
self.gifts = gifts
|
||||||
|
self.starGifts = starGifts
|
||||||
}
|
}
|
||||||
|
|
||||||
static func get(context: AccountContext) -> Signal<ContentsData, NoError> {
|
static func get(context: AccountContext) -> Signal<ContentsData, NoError> {
|
||||||
@ -73,9 +85,10 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
context.engine.data.subscribe(
|
context.engine.data.subscribe(
|
||||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)
|
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)
|
||||||
),
|
),
|
||||||
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudUniqueStarGifts], namespaces: [Namespaces.ItemCollection.CloudDice], aroundIndex: nil, count: 10000000)
|
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudUniqueStarGifts], namespaces: [Namespaces.ItemCollection.CloudDice], aroundIndex: nil, count: 10000000),
|
||||||
|
context.engine.payments.cachedStarGifts()
|
||||||
)
|
)
|
||||||
|> map { peer, view -> ContentsData in
|
|> map { peer, view, starGifts -> ContentsData in
|
||||||
var gifts: [StarGift.UniqueGift] = []
|
var gifts: [StarGift.UniqueGift] = []
|
||||||
for orderedView in view.orderedItemListsViews {
|
for orderedView in view.orderedItemListsViews {
|
||||||
if orderedView.collectionId == Namespaces.OrderedItemList.CloudUniqueStarGifts {
|
if orderedView.collectionId == Namespaces.OrderedItemList.CloudUniqueStarGifts {
|
||||||
@ -89,7 +102,8 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
return ContentsData(
|
return ContentsData(
|
||||||
peer: peer,
|
peer: peer,
|
||||||
gifts: gifts
|
gifts: gifts,
|
||||||
|
starGifts: starGifts ?? []
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,6 +156,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class View: UIView, UIScrollViewDelegate {
|
final class View: UIView, UIScrollViewDelegate {
|
||||||
|
private let containerView = UIView()
|
||||||
private let topOverscrollLayer = SimpleLayer()
|
private let topOverscrollLayer = SimpleLayer()
|
||||||
private let scrollView: ScrollView
|
private let scrollView: ScrollView
|
||||||
private let actionButton = ComponentView<Empty>()
|
private let actionButton = ComponentView<Empty>()
|
||||||
@ -181,9 +196,11 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
|
|
||||||
private var cachedIconFiles: [Int64: TelegramMediaFile] = [:]
|
private var cachedIconFiles: [Int64: TelegramMediaFile] = [:]
|
||||||
|
|
||||||
|
private var selectedNameGift: StarGift.UniqueGift?
|
||||||
private var updatedPeerNameColor: PeerColor?
|
private var updatedPeerNameColor: PeerColor?
|
||||||
private var updatedPeerNameEmoji: Int64??
|
private var updatedPeerNameEmoji: Int64??
|
||||||
|
|
||||||
|
private var selectedProfileGift: StarGift.UniqueGift?
|
||||||
private var updatedPeerProfileColor: PeerNameColor??
|
private var updatedPeerProfileColor: PeerNameColor??
|
||||||
private var updatedPeerProfileEmoji: Int64??
|
private var updatedPeerProfileEmoji: Int64??
|
||||||
private var updatedPeerStatus: PeerEmojiStatus??
|
private var updatedPeerStatus: PeerEmojiStatus??
|
||||||
@ -198,6 +215,9 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
private weak var emojiStatusSelectionController: ViewController?
|
private weak var emojiStatusSelectionController: ViewController?
|
||||||
|
|
||||||
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||||
|
private var cachedStarImage: (UIImage, PresentationTheme)?
|
||||||
|
private var cachedSubtitleStarImage: (UIImage, PresentationTheme)?
|
||||||
|
private var cachedTonImage: (UIImage, PresentationTheme)?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.scrollView = ScrollView()
|
self.scrollView = ScrollView()
|
||||||
@ -216,8 +236,10 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.containerView)
|
||||||
|
|
||||||
self.scrollView.delegate = self
|
self.scrollView.delegate = self
|
||||||
self.addSubview(self.scrollView)
|
self.containerView.addSubview(self.scrollView)
|
||||||
|
|
||||||
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
|
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
|
||||||
|
|
||||||
@ -308,18 +330,32 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
if let giftListView = self.profileGiftsSection.findTaggedView(tag: giftListTag) as? GiftListItemComponent.View {
|
if let giftListView = self.profileGiftsSection.findTaggedView(tag: giftListTag) as? GiftListItemComponent.View {
|
||||||
let rect = self.scrollView.convert(self.scrollView.bounds, to: giftListView)
|
let rect = self.scrollView.convert(self.scrollView.bounds, to: giftListView)
|
||||||
let visibleRect = giftListView.bounds.intersection(rect)
|
let visibleRect = giftListView.bounds.intersection(rect)
|
||||||
giftListView.updateVisibleBounds(visibleRect)
|
if !self.isUpdating {
|
||||||
|
giftListView.updateVisibleBounds(visibleRect)
|
||||||
|
} else if giftListView.visibleBounds == nil {
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
giftListView.updateVisibleBounds(visibleRect)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .name:
|
case .name:
|
||||||
if let giftListView = self.nameGiftsSection.findTaggedView(tag: giftListTag) as? GiftListItemComponent.View {
|
if let giftListView = self.nameGiftsSection.findTaggedView(tag: giftListTag) as? GiftListItemComponent.View {
|
||||||
let rect = self.scrollView.convert(self.scrollView.bounds, to: giftListView)
|
let rect = self.scrollView.convert(self.scrollView.bounds, to: giftListView)
|
||||||
let visibleRect = giftListView.bounds.intersection(rect)
|
let visibleRect = giftListView.bounds.intersection(rect)
|
||||||
giftListView.updateVisibleBounds(visibleRect)
|
if !self.isUpdating {
|
||||||
}
|
giftListView.updateVisibleBounds(visibleRect)
|
||||||
|
} else if giftListView.visibleBounds == nil {
|
||||||
let bottomContentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height)
|
Queue.mainQueue().justDispatch {
|
||||||
if bottomContentOffset < 320.0 {
|
giftListView.updateVisibleBounds(visibleRect)
|
||||||
self.starGiftsContext?.loadMore()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bottomContentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height)
|
||||||
|
if bottomContentOffset < 320.0 {
|
||||||
|
if !giftListView.loadMore() {
|
||||||
|
self.starGiftsContext?.loadMore()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -599,6 +635,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
if case .collectible = resolvedState.nameColor {
|
if case .collectible = resolvedState.nameColor {
|
||||||
self.updatedPeerNameColor = .preset(.blue)
|
self.updatedPeerNameColor = .preset(.blue)
|
||||||
}
|
}
|
||||||
|
self.selectedNameGift = nil
|
||||||
if let result {
|
if let result {
|
||||||
self.updatedPeerNameEmoji = result.fileId.id
|
self.updatedPeerNameEmoji = result.fileId.id
|
||||||
} else {
|
} else {
|
||||||
@ -671,13 +708,21 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
let environment = environment[EnvironmentType.self].value
|
let environment = environment[EnvironmentType.self].value
|
||||||
let themeUpdated = self.environment?.theme !== environment.theme
|
let themeUpdated = self.environment?.theme !== environment.theme
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
let theme = environment.theme
|
||||||
|
|
||||||
|
var animateTabChange = false
|
||||||
|
if let hint = transition.userData(TransitionHint.self) {
|
||||||
|
animateTabChange = hint.animateTabChange
|
||||||
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
self.backgroundColor = environment.theme.list.blocksBackgroundColor
|
self.backgroundColor = environment.theme.list.blocksBackgroundColor
|
||||||
|
self.scrollView.backgroundColor = environment.theme.list.blocksBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme {
|
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme {
|
||||||
@ -722,7 +767,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
guard let contentsData = self.contentsData, var peer = contentsData.peer, let resolvedState = self.resolveState() else {
|
guard let contentsData = self.contentsData, var peer = contentsData.peer, let resolvedState = self.resolveState() else {
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentTheme = self.currentTheme, (self.resolvedCurrentTheme?.reference != currentTheme || self.resolvedCurrentTheme?.isDark != environment.theme.overallDarkAppearance), (self.resolvingCurrentTheme?.reference != currentTheme || self.resolvingCurrentTheme?.isDark != environment.theme.overallDarkAppearance) {
|
if let currentTheme = self.currentTheme, (self.resolvedCurrentTheme?.reference != currentTheme || self.resolvedCurrentTheme?.isDark != environment.theme.overallDarkAppearance), (self.resolvingCurrentTheme?.reference != currentTheme || self.resolvingCurrentTheme?.isDark != environment.theme.overallDarkAppearance) {
|
||||||
self.resolvingCurrentTheme?.disposable.dispose()
|
self.resolvingCurrentTheme?.disposable.dispose()
|
||||||
|
|
||||||
@ -785,20 +830,39 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
var previewTransition = transition
|
||||||
|
let transitionScale = (availableSize.height - 3.0) / availableSize.height
|
||||||
|
if animateTabChange, let snapshotView = self.containerView.snapshotView(afterScreenUpdates: false) {
|
||||||
|
self.insertSubview(snapshotView, belowSubview: self.containerView)
|
||||||
|
snapshotView.layer.animateScale(from: 1.0, to: transitionScale, duration: 0.12, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { completed in
|
||||||
|
snapshotView.removeFromSuperview()
|
||||||
|
})
|
||||||
|
|
||||||
|
self.scrollView.contentOffset = CGPoint(x: 0.0, y: 0.0)
|
||||||
|
|
||||||
|
self.containerView.layer.animateScale(from: transitionScale, to: 1.0, duration: 0.15, delay: 0.1, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
self.containerView.layer.allowsGroupOpacity = true
|
||||||
|
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { completed in
|
||||||
|
self.containerView.layer.allowsGroupOpacity = false
|
||||||
|
})
|
||||||
|
previewTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
let tabSelectorSize = self.tabSelector.update(
|
let tabSelectorSize = self.tabSelector.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
TabSelectorComponent(
|
TabSelectorComponent(
|
||||||
colors: TabSelectorComponent.Colors(
|
colors: TabSelectorComponent.Colors(
|
||||||
foreground: environment.theme.list.itemSecondaryTextColor,
|
foreground: environment.theme.list.itemAccentColor,
|
||||||
selection: environment.theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15),
|
selection: environment.theme.list.itemAccentColor.withMultipliedAlpha(0.1),
|
||||||
|
normal: environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.78),
|
||||||
simple: true
|
simple: true
|
||||||
),
|
),
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
|
customLayout: TabSelectorComponent.CustomLayout(font: Font.semibold(16.0)),
|
||||||
items: [
|
items: [
|
||||||
TabSelectorComponent.Item(id: Section.profile.rawValue, title: "Profile"),
|
TabSelectorComponent.Item(id: Section.profile.rawValue, title: environment.strings.ProfileColorSetup_TitleProfile),
|
||||||
TabSelectorComponent.Item(id: Section.name.rawValue, title: "Name")
|
TabSelectorComponent.Item(id: Section.name.rawValue, title: environment.strings.ProfileColorSetup_TitleName)
|
||||||
],
|
],
|
||||||
selectedId: self.currentSection.rawValue,
|
selectedId: self.currentSection.rawValue,
|
||||||
setSelectedId: { [weak self] value in
|
setSelectedId: { [weak self] value in
|
||||||
@ -806,8 +870,11 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let intValue = value.base as? Int32 {
|
if let intValue = value.base as? Int32 {
|
||||||
self.currentSection = Section(rawValue: intValue) ?? .profile
|
let updatedSection = Section(rawValue: intValue) ?? .profile
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.3))
|
if self.currentSection != updatedSection {
|
||||||
|
self.currentSection = updatedSection
|
||||||
|
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -850,30 +917,23 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
if let nameGiftsSectionView = self.nameGiftsSection.view, nameGiftsSectionView.superview != nil {
|
if let nameGiftsSectionView = self.nameGiftsSection.view, nameGiftsSectionView.superview != nil {
|
||||||
nameGiftsSectionView.removeFromSuperview()
|
nameGiftsSectionView.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasHeaderColor = false
|
|
||||||
if resolvedState.profileColor != nil {
|
|
||||||
hasHeaderColor = true
|
|
||||||
}
|
|
||||||
if case .starGift = resolvedState.emojiStatus?.content {
|
|
||||||
hasHeaderColor = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let profilePreviewSize = self.profilePreview.update(
|
let profilePreviewSize = self.profilePreview.update(
|
||||||
transition: transition,
|
transition: previewTransition,
|
||||||
component: AnyComponent(TopBottomCornersComponent(topCornerRadius: itemCornerRadius, bottomCornerRadius: !self.scrolledUp ? itemCornerRadius : 0.0, component: AnyComponent(ListItemComponentAdaptor(
|
component: AnyComponent(TopBottomCornersComponent(topCornerRadius: itemCornerRadius, bottomCornerRadius: !self.scrolledUp ? itemCornerRadius : 0.0, component: AnyComponent(ListItemComponentAdaptor(
|
||||||
itemGenerator: PeerNameColorProfilePreviewItem(
|
itemGenerator: PeerNameColorProfilePreviewItem(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
componentTheme: environment.theme,
|
componentTheme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
topInset: 0.0,
|
topInset: 28.0,
|
||||||
|
bottomInset: 15.0 + UIScreenPixel,
|
||||||
sectionId: 0,
|
sectionId: 0,
|
||||||
peer: peer,
|
peer: peer,
|
||||||
subtitleString: environment.strings.Presence_online,
|
subtitleString: environment.strings.Presence_online,
|
||||||
files: self.cachedIconFiles,
|
files: self.cachedIconFiles,
|
||||||
nameDisplayOrder: presentationData.nameDisplayOrder,
|
nameDisplayOrder: presentationData.nameDisplayOrder,
|
||||||
showBackground: false
|
showBackground: true
|
||||||
),
|
),
|
||||||
params: ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
|
params: ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
|
||||||
)))),
|
)))),
|
||||||
@ -883,7 +943,8 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
let profilePreviewFrame = CGRect(origin: CGPoint(x: sideInset, y: environment.navigationHeight + 12.0), size: profilePreviewSize)
|
let profilePreviewFrame = CGRect(origin: CGPoint(x: sideInset, y: environment.navigationHeight + 12.0), size: profilePreviewSize)
|
||||||
if let profilePreviewView = self.profilePreview.view {
|
if let profilePreviewView = self.profilePreview.view {
|
||||||
if profilePreviewView.superview == nil {
|
if profilePreviewView.superview == nil {
|
||||||
self.addSubview(profilePreviewView)
|
profilePreviewView.isUserInteractionEnabled = false
|
||||||
|
self.containerView.addSubview(profilePreviewView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: profilePreviewView, frame: profilePreviewFrame)
|
transition.setFrame(view: profilePreviewView, frame: profilePreviewFrame)
|
||||||
}
|
}
|
||||||
@ -918,7 +979,6 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
component: AnyComponent(ListSectionComponent(
|
component: AnyComponent(ListSectionComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
style: .glass,
|
style: .glass,
|
||||||
background: .range(from: 0, corners: DynamicCornerRadiusView.Corners(minXMinY: !hasHeaderColor ? itemCornerRadius : 0.0, maxXMinY: !hasHeaderColor ? itemCornerRadius : 0.0, minXMaxY: itemCornerRadius, maxXMaxY: itemCornerRadius)),
|
|
||||||
header: nil,
|
header: nil,
|
||||||
footer: AnyComponent(MultilineTextComponent(
|
footer: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(previewFooterText),
|
text: .plain(previewFooterText),
|
||||||
@ -937,7 +997,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.currentSection = .name
|
self.currentSection = .name
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.3))
|
self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true)))
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
items: [
|
items: [
|
||||||
@ -951,6 +1011,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
guard let self, let value, let resolvedState = self.resolveState() else {
|
guard let self, let value, let resolvedState = self.resolveState() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.selectedProfileGift = nil
|
||||||
self.updatedPeerProfileColor = value
|
self.updatedPeerProfileColor = value
|
||||||
if case .starGift = resolvedState.emojiStatus?.content {
|
if case .starGift = resolvedState.emojiStatus?.content {
|
||||||
self.updatedPeerStatus = .some(nil)
|
self.updatedPeerStatus = .some(nil)
|
||||||
@ -1025,7 +1086,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
guard let self, let resolvedState = self.resolveState() else {
|
guard let self, let resolvedState = self.resolveState() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.selectedProfileGift = nil
|
||||||
self.updatedPeerProfileColor = .some(nil)
|
self.updatedPeerProfileColor = .some(nil)
|
||||||
self.updatedPeerProfileEmoji = .some(nil)
|
self.updatedPeerProfileEmoji = .some(nil)
|
||||||
if case .starGift = resolvedState.emojiStatus?.content {
|
if case .starGift = resolvedState.emojiStatus?.content {
|
||||||
@ -1062,92 +1123,87 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
contentHeight += sectionSpacing
|
contentHeight += sectionSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
if !contentsData.gifts.isEmpty {
|
var selectedGiftId: Int64?
|
||||||
var selectedGiftId: Int64?
|
if let status = resolvedState.emojiStatus, case let .starGift(id, _, _, _, _, _, _, _, _) = status.content {
|
||||||
if let status = resolvedState.emojiStatus, case let .starGift(id, _, _, _, _, _, _, _, _) = status.content {
|
selectedGiftId = id
|
||||||
selectedGiftId = id
|
|
||||||
}
|
|
||||||
let giftsSectionSize = self.profileGiftsSection.update(
|
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(ListSectionComponent(
|
|
||||||
theme: environment.theme,
|
|
||||||
style: .glass,
|
|
||||||
header: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(
|
|
||||||
string: environment.strings.NameColor_GiftTitle,
|
|
||||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
|
||||||
textColor: environment.theme.list.freeTextColor
|
|
||||||
)),
|
|
||||||
maximumNumberOfLines: 0
|
|
||||||
)),
|
|
||||||
footer: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(
|
|
||||||
string: environment.strings.NameColor_GiftInfo,
|
|
||||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
|
||||||
textColor: environment.theme.list.freeTextColor
|
|
||||||
)),
|
|
||||||
maximumNumberOfLines: 0
|
|
||||||
)),
|
|
||||||
items: [
|
|
||||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
|
||||||
GiftListItemComponent(
|
|
||||||
context: component.context,
|
|
||||||
theme: environment.theme,
|
|
||||||
gifts: contentsData.gifts,
|
|
||||||
selectedId: selectedGiftId,
|
|
||||||
selectionUpdated: { [weak self] gift in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var fileId: Int64?
|
|
||||||
var patternFileId: Int64?
|
|
||||||
var innerColor: Int32?
|
|
||||||
var outerColor: Int32?
|
|
||||||
var patternColor: Int32?
|
|
||||||
var textColor: Int32?
|
|
||||||
for attribute in gift.attributes {
|
|
||||||
switch attribute {
|
|
||||||
case let .model(_, file, _):
|
|
||||||
fileId = file.fileId.id
|
|
||||||
self.cachedIconFiles[file.fileId.id] = file
|
|
||||||
case let .pattern(_, file, _):
|
|
||||||
patternFileId = file.fileId.id
|
|
||||||
self.cachedIconFiles[file.fileId.id] = file
|
|
||||||
case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
|
|
||||||
innerColor = innerColorValue
|
|
||||||
outerColor = outerColorValue
|
|
||||||
patternColor = patternColorValue
|
|
||||||
textColor = textColorValue
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let fileId, let patternFileId, let innerColor, let outerColor, let patternColor, let textColor {
|
|
||||||
self.updatedPeerProfileColor = .some(nil)
|
|
||||||
self.updatedPeerProfileEmoji = .some(nil)
|
|
||||||
self.updatedPeerStatus = .some(PeerEmojiStatus(content: .starGift(id: gift.id, fileId: fileId, title: gift.title, slug: gift.slug, patternFileId: patternFileId, innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor), expirationDate: nil))
|
|
||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tag: giftListTag
|
|
||||||
)
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
displaySeparators: false
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
|
||||||
)
|
|
||||||
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
|
||||||
if let giftsSectionView = self.profileGiftsSection.view {
|
|
||||||
if giftsSectionView.superview == nil {
|
|
||||||
self.scrollView.addSubview(giftsSectionView)
|
|
||||||
}
|
|
||||||
transition.setFrame(view: giftsSectionView, frame: giftsSectionFrame)
|
|
||||||
}
|
|
||||||
contentHeight += giftsSectionSize.height
|
|
||||||
contentHeight += sectionSpacing
|
|
||||||
}
|
}
|
||||||
|
//TODO:localize
|
||||||
|
self.profileGiftsSection.parentState = self.state
|
||||||
|
let giftsSectionSize = self.profileGiftsSection.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(ListSectionComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
style: .glass,
|
||||||
|
header: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: "Your Gifts".uppercased(),
|
||||||
|
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||||
|
textColor: environment.theme.list.freeTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
)),
|
||||||
|
footer: nil,
|
||||||
|
items: [
|
||||||
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
||||||
|
GiftListItemComponent(
|
||||||
|
context: component.context,
|
||||||
|
theme: environment.theme,
|
||||||
|
gifts: contentsData.gifts,
|
||||||
|
starGifts: contentsData.starGifts,
|
||||||
|
selectedId: selectedGiftId,
|
||||||
|
selectionUpdated: { [weak self] gift in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var fileId: Int64?
|
||||||
|
var patternFileId: Int64?
|
||||||
|
var innerColor: Int32?
|
||||||
|
var outerColor: Int32?
|
||||||
|
var patternColor: Int32?
|
||||||
|
var textColor: Int32?
|
||||||
|
for attribute in gift.attributes {
|
||||||
|
switch attribute {
|
||||||
|
case let .model(_, file, _):
|
||||||
|
fileId = file.fileId.id
|
||||||
|
self.cachedIconFiles[file.fileId.id] = file
|
||||||
|
case let .pattern(_, file, _):
|
||||||
|
patternFileId = file.fileId.id
|
||||||
|
self.cachedIconFiles[file.fileId.id] = file
|
||||||
|
case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
|
||||||
|
innerColor = innerColorValue
|
||||||
|
outerColor = outerColorValue
|
||||||
|
patternColor = patternColorValue
|
||||||
|
textColor = textColorValue
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let fileId, let patternFileId, let innerColor, let outerColor, let patternColor, let textColor {
|
||||||
|
self.selectedProfileGift = gift
|
||||||
|
self.updatedPeerProfileColor = .some(nil)
|
||||||
|
self.updatedPeerProfileEmoji = .some(nil)
|
||||||
|
self.updatedPeerStatus = .some(PeerEmojiStatus(content: .starGift(id: gift.id, fileId: fileId, title: gift.title, slug: gift.slug, patternFileId: patternFileId, innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor), expirationDate: nil))
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tag: giftListTag
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
displaySeparators: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||||
|
)
|
||||||
|
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
||||||
|
if let giftsSectionView = self.profileGiftsSection.view {
|
||||||
|
if giftsSectionView.superview == nil {
|
||||||
|
self.scrollView.addSubview(giftsSectionView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: giftsSectionView, frame: giftsSectionFrame)
|
||||||
|
}
|
||||||
|
contentHeight += giftsSectionSize.height
|
||||||
|
contentHeight += sectionSpacing
|
||||||
case .name:
|
case .name:
|
||||||
var transition = transition
|
var transition = transition
|
||||||
if self.namePreview.view == nil {
|
if self.namePreview.view == nil {
|
||||||
@ -1208,7 +1264,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let namePreviewSize = self.namePreview.update(
|
let namePreviewSize = self.namePreview.update(
|
||||||
transition: transition,
|
transition: previewTransition,
|
||||||
component: AnyComponent(TopBottomCornersComponent(topCornerRadius: itemCornerRadius, bottomCornerRadius: !self.scrolledUp ? itemCornerRadius : 0.0, component: AnyComponent(ListItemComponentAdaptor(
|
component: AnyComponent(TopBottomCornersComponent(topCornerRadius: itemCornerRadius, bottomCornerRadius: !self.scrolledUp ? itemCornerRadius : 0.0, component: AnyComponent(ListItemComponentAdaptor(
|
||||||
itemGenerator: PeerNameColorChatPreviewItem(
|
itemGenerator: PeerNameColorChatPreviewItem(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
@ -1231,7 +1287,8 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
let namePreviewFrame = CGRect(origin: CGPoint(x: sideInset, y: environment.navigationHeight + 12.0), size: namePreviewSize)
|
let namePreviewFrame = CGRect(origin: CGPoint(x: sideInset, y: environment.navigationHeight + 12.0), size: namePreviewSize)
|
||||||
if let namePreviewView = self.namePreview.view {
|
if let namePreviewView = self.namePreview.view {
|
||||||
if namePreviewView.superview == nil {
|
if namePreviewView.superview == nil {
|
||||||
self.addSubview(namePreviewView)
|
namePreviewView.isUserInteractionEnabled = false
|
||||||
|
self.containerView.addSubview(namePreviewView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: namePreviewView, frame: namePreviewFrame)
|
transition.setFrame(view: namePreviewView, frame: namePreviewFrame)
|
||||||
}
|
}
|
||||||
@ -1267,6 +1324,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
self.updatedPeerNameEmoji = .some(nil)
|
self.updatedPeerNameEmoji = .some(nil)
|
||||||
}
|
}
|
||||||
self.updatedPeerNameColor = .preset(value)
|
self.updatedPeerNameColor = .preset(value)
|
||||||
|
self.selectedNameGift = nil
|
||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
},
|
},
|
||||||
sectionId: 0
|
sectionId: 0
|
||||||
@ -1308,83 +1366,141 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
contentHeight += nameColorSectionSize.height
|
contentHeight += nameColorSectionSize.height
|
||||||
contentHeight += sectionSpacing
|
contentHeight += sectionSpacing
|
||||||
|
|
||||||
if !self.starGifts.isEmpty {
|
var selectedGiftId: Int64?
|
||||||
var selectedGiftId: Int64?
|
if case let .collectible(collectibleColor) = resolvedState.nameColor {
|
||||||
if case let .collectible(collectibleColor) = resolvedState.nameColor {
|
selectedGiftId = collectibleColor.collectibleId
|
||||||
selectedGiftId = collectibleColor.collectibleId
|
|
||||||
}
|
|
||||||
let giftsSectionSize = self.nameGiftsSection.update(
|
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(ListSectionComponent(
|
|
||||||
theme: environment.theme,
|
|
||||||
style: .glass,
|
|
||||||
header: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(
|
|
||||||
string: environment.strings.NameColor_GiftTitle,
|
|
||||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
|
||||||
textColor: environment.theme.list.freeTextColor
|
|
||||||
)),
|
|
||||||
maximumNumberOfLines: 0
|
|
||||||
)),
|
|
||||||
footer: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(
|
|
||||||
string: environment.strings.NameColor_GiftInfo,
|
|
||||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
|
||||||
textColor: environment.theme.list.freeTextColor
|
|
||||||
)),
|
|
||||||
maximumNumberOfLines: 0
|
|
||||||
)),
|
|
||||||
items: [
|
|
||||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
|
||||||
GiftListItemComponent(
|
|
||||||
context: component.context,
|
|
||||||
theme: environment.theme,
|
|
||||||
gifts: self.starGifts,
|
|
||||||
selectedId: selectedGiftId,
|
|
||||||
selectionUpdated: { [weak self] gift in
|
|
||||||
guard let self, let peerColor = gift.peerColor else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.updatedPeerNameColor = .collectible(peerColor)
|
|
||||||
self.updatedPeerNameEmoji = peerColor.backgroundEmojiId
|
|
||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
|
||||||
},
|
|
||||||
tag: giftListTag
|
|
||||||
)
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
displaySeparators: false
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
|
||||||
)
|
|
||||||
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
|
||||||
if let giftsSectionView = self.nameGiftsSection.view {
|
|
||||||
if giftsSectionView.superview == nil {
|
|
||||||
self.scrollView.addSubview(giftsSectionView)
|
|
||||||
}
|
|
||||||
transition.setFrame(view: giftsSectionView, frame: giftsSectionFrame)
|
|
||||||
}
|
|
||||||
contentHeight += giftsSectionSize.height
|
|
||||||
contentHeight += sectionSpacing
|
|
||||||
}
|
}
|
||||||
|
//TODO:localize
|
||||||
|
|
||||||
|
var peerColorStarGifts: [StarGift] = []
|
||||||
|
for gift in contentsData.starGifts {
|
||||||
|
if case let .generic(genericGift) = gift, genericGift.flags.contains(.peerColorAvailable), let resale = genericGift.availability?.resale, resale > 0 {
|
||||||
|
peerColorStarGifts.append(gift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.nameGiftsSection.parentState = self.state
|
||||||
|
let giftsSectionSize = self.nameGiftsSection.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(ListSectionComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
style: .glass,
|
||||||
|
header: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(
|
||||||
|
string: "Your Gifts".uppercased(),
|
||||||
|
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||||
|
textColor: environment.theme.list.freeTextColor
|
||||||
|
)),
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
)),
|
||||||
|
footer: nil,
|
||||||
|
items: [
|
||||||
|
AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
||||||
|
GiftListItemComponent(
|
||||||
|
context: component.context,
|
||||||
|
theme: environment.theme,
|
||||||
|
gifts: self.starGifts,
|
||||||
|
starGifts: peerColorStarGifts,
|
||||||
|
selectedId: selectedGiftId,
|
||||||
|
selectionUpdated: { [weak self] gift in
|
||||||
|
guard let self, let peerColor = gift.peerColor else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.selectedNameGift = gift
|
||||||
|
self.updatedPeerNameColor = .collectible(peerColor)
|
||||||
|
self.updatedPeerNameEmoji = peerColor.backgroundEmojiId
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
},
|
||||||
|
tag: giftListTag
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
displaySeparators: false
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||||
|
)
|
||||||
|
let giftsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: giftsSectionSize)
|
||||||
|
if let giftsSectionView = self.nameGiftsSection.view {
|
||||||
|
if giftsSectionView.superview == nil {
|
||||||
|
self.scrollView.addSubview(giftsSectionView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: giftsSectionView, frame: giftsSectionFrame)
|
||||||
|
}
|
||||||
|
contentHeight += giftsSectionSize.height
|
||||||
|
contentHeight += sectionSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
contentHeight += bottomContentInset
|
contentHeight += bottomContentInset
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let buttonSideInset: CGFloat = 36.0
|
let buttonSideInset: CGFloat = environment.safeInsets.left + 36.0
|
||||||
let buttonTitle = "Apply Style" // environment.strings.Channel_Appearance_ApplyButton
|
var buttonTitle = "Apply Style" // environment.strings.Channel_Appearance_ApplyButton
|
||||||
// if let emojiStatus = resolvedState.emojiStatus, case .starGift = emojiStatus.content, resolvedState.changes.contains(.emojiStatus) {
|
var buttonAttributedSubtitleString: NSMutableAttributedString?
|
||||||
// buttonTitle = environment.strings.NameColor_WearCollectible
|
|
||||||
// }
|
if let gift = self.selectedProfileGift, let resellAmounts = gift.resellAmounts, let starsAmount = resellAmounts.first(where: { $0.currency == .stars }) {
|
||||||
|
let resellAmount: CurrencyAmount
|
||||||
|
if gift.resellForTonOnly {
|
||||||
|
resellAmount = resellAmounts.first(where: { $0.currency == .ton }) ?? starsAmount
|
||||||
|
} else {
|
||||||
|
resellAmount = starsAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.cachedStarImage == nil || self.cachedStarImage?.1 !== theme {
|
||||||
|
self.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme)
|
||||||
|
}
|
||||||
|
if self.cachedTonImage == nil || self.cachedTonImage?.1 !== theme {
|
||||||
|
self.cachedTonImage = (generateTintedImage(image: UIImage(bundleImageName: "Ads/TonAbout"), color: theme.list.itemCheckColors.foregroundColor)!, theme)
|
||||||
|
}
|
||||||
|
if self.cachedSubtitleStarImage == nil || self.cachedSubtitleStarImage?.1 !== environment.theme {
|
||||||
|
self.cachedSubtitleStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/StarsCount"), color: .white)!, theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buyString = environment.strings.Gift_View_BuyFor
|
||||||
|
let currencySymbol: String
|
||||||
|
let currencyAmount: String
|
||||||
|
switch resellAmount.currency {
|
||||||
|
case .stars:
|
||||||
|
currencySymbol = "#"
|
||||||
|
currencyAmount = formatStarsAmountText(resellAmount.amount, dateTimeFormat: environment.dateTimeFormat)
|
||||||
|
case .ton:
|
||||||
|
currencySymbol = "$"
|
||||||
|
currencyAmount = formatTonAmountText(resellAmount.amount.value, dateTimeFormat: environment.dateTimeFormat, maxDecimalPositions: nil)
|
||||||
|
|
||||||
|
buttonAttributedSubtitleString = NSMutableAttributedString(string: environment.strings.Gift_View_EqualsTo(" # \(formatStarsAmountText(starsAmount.amount, dateTimeFormat: environment.dateTimeFormat))").string, font: Font.medium(11.0), textColor: theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center)
|
||||||
|
|
||||||
|
}
|
||||||
|
buyString += " \(currencySymbol) \(currencyAmount)"
|
||||||
|
buttonTitle = buyString
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttonAttributedString = NSMutableAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||||
|
if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 {
|
||||||
|
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
|
buttonAttributedString.addAttribute(.foregroundColor, value: theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
|
buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
|
buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
|
}
|
||||||
|
if let range = buttonAttributedString.string.range(of: "$"), let tonImage = self.cachedTonImage?.0 {
|
||||||
|
buttonAttributedString.addAttribute(.attachment, value: tonImage, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
|
buttonAttributedString.addAttribute(.foregroundColor, value: theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
|
buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
|
buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string))
|
||||||
|
}
|
||||||
|
if let buttonAttributedSubtitleString, let range = buttonAttributedSubtitleString.string.range(of: "#"), let starImage = self.cachedSubtitleStarImage?.0 {
|
||||||
|
buttonAttributedSubtitleString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedSubtitleString.string))
|
||||||
|
buttonAttributedSubtitleString.addAttribute(.foregroundColor, value: theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), range: NSRange(range, in: buttonAttributedSubtitleString.string))
|
||||||
|
buttonAttributedSubtitleString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedSubtitleString.string))
|
||||||
|
buttonAttributedSubtitleString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedSubtitleString.string))
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonContents: [AnyComponentWithIdentity<Empty>] = [
|
||||||
|
AnyComponentWithIdentity(id: AnyHashable(buttonTitle), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))))
|
||||||
|
]
|
||||||
|
if let buttonAttributedSubtitleString {
|
||||||
|
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedSubtitleString)))))
|
||||||
|
}
|
||||||
|
|
||||||
var buttonContents: [AnyComponentWithIdentity<Empty>] = []
|
|
||||||
buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(buttonTitle), component: AnyComponent(
|
|
||||||
Text(text: buttonTitle, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor)
|
|
||||||
)))
|
|
||||||
|
|
||||||
let buttonSize = self.actionButton.update(
|
let buttonSize = self.actionButton.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(ButtonComponent(
|
component: AnyComponent(ButtonComponent(
|
||||||
@ -1426,7 +1542,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
transition.setAlpha(view: buttonView, alpha: 1.0)
|
transition.setAlpha(view: buttonView, alpha: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let edgeEffectHeight: CGFloat = availableSize.height - buttonY + 8.0
|
let edgeEffectHeight: CGFloat = availableSize.height - buttonY + 24.0
|
||||||
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - edgeEffectHeight), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
|
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - edgeEffectHeight), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
|
||||||
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||||
self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, isInverted: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: availableSize, transition: transition)
|
self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, isInverted: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: availableSize, transition: transition)
|
||||||
@ -1440,10 +1556,8 @@ final class UserAppearanceScreenComponent: Component {
|
|||||||
if self.scrollView.contentSize != contentSize {
|
if self.scrollView.contentSize != contentSize {
|
||||||
self.scrollView.contentSize = contentSize
|
self.scrollView.contentSize = contentSize
|
||||||
}
|
}
|
||||||
// let scrollInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: availableSize.height - bottomPanelFrame.minY, right: 0.0)
|
|
||||||
// if self.scrollView.verticalScrollIndicatorInsets != scrollInsets {
|
transition.setFrame(view: self.containerView, frame: CGRect(origin: .zero, size: availableSize))
|
||||||
// self.scrollView.verticalScrollIndicatorInsets = scrollInsets
|
|
||||||
// }
|
|
||||||
|
|
||||||
if !previousBounds.isEmpty, !transition.animation.isImmediate {
|
if !previousBounds.isEmpty, !transition.animation.isImmediate {
|
||||||
let bounds = self.scrollView.bounds
|
let bounds = self.scrollView.bounds
|
||||||
|
|||||||
@ -36,15 +36,18 @@ public final class TabSelectorComponent: Component {
|
|||||||
public struct Colors: Equatable {
|
public struct Colors: Equatable {
|
||||||
public var foreground: UIColor
|
public var foreground: UIColor
|
||||||
public var selection: UIColor
|
public var selection: UIColor
|
||||||
|
public var normal: UIColor?
|
||||||
public var simple: Bool
|
public var simple: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
foreground: UIColor,
|
foreground: UIColor,
|
||||||
selection: UIColor,
|
selection: UIColor,
|
||||||
|
normal: UIColor? = nil,
|
||||||
simple: Bool = false
|
simple: Bool = false
|
||||||
) {
|
) {
|
||||||
self.foreground = foreground
|
self.foreground = foreground
|
||||||
self.selection = selection
|
self.selection = selection
|
||||||
|
self.normal = normal
|
||||||
self.simple = simple
|
self.simple = simple
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,7 +61,7 @@ public final class TabSelectorComponent: Component {
|
|||||||
public var verticalInset: CGFloat
|
public var verticalInset: CGFloat
|
||||||
public var allowScroll: Bool
|
public var allowScroll: Bool
|
||||||
|
|
||||||
public init(font: UIFont, spacing: CGFloat, innerSpacing: CGFloat? = nil, fillWidth: Bool = false, lineSelection: Bool = false, verticalInset: CGFloat = 0.0, allowScroll: Bool = true) {
|
public init(font: UIFont, spacing: CGFloat = 2.0, innerSpacing: CGFloat? = nil, fillWidth: Bool = false, lineSelection: Bool = false, verticalInset: CGFloat = 0.0, allowScroll: Bool = true) {
|
||||||
self.font = font
|
self.font = font
|
||||||
self.spacing = spacing
|
self.spacing = spacing
|
||||||
self.innerSpacing = innerSpacing
|
self.innerSpacing = innerSpacing
|
||||||
@ -611,6 +614,9 @@ public final class TabSelectorComponent: Component {
|
|||||||
if case .component = item.content {
|
if case .component = item.content {
|
||||||
useSelectionFraction = true
|
useSelectionFraction = true
|
||||||
}
|
}
|
||||||
|
if let _ = component.colors.normal {
|
||||||
|
useSelectionFraction = true
|
||||||
|
}
|
||||||
|
|
||||||
let itemSize = itemView.title.update(
|
let itemSize = itemView.title.update(
|
||||||
transition: itemTransition,
|
transition: itemTransition,
|
||||||
@ -619,6 +625,7 @@ public final class TabSelectorComponent: Component {
|
|||||||
content: item.content,
|
content: item.content,
|
||||||
font: itemFont,
|
font: itemFont,
|
||||||
color: component.colors.foreground,
|
color: component.colors.foreground,
|
||||||
|
normalColor: component.colors.normal,
|
||||||
selectedColor: component.colors.selection,
|
selectedColor: component.colors.selection,
|
||||||
selectionFraction: useSelectionFraction ? selectionFraction : 0.0
|
selectionFraction: useSelectionFraction ? selectionFraction : 0.0
|
||||||
)),
|
)),
|
||||||
@ -805,6 +812,7 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
let content: TabSelectorComponent.Item.Content
|
let content: TabSelectorComponent.Item.Content
|
||||||
let font: UIFont
|
let font: UIFont
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
|
let normalColor: UIColor?
|
||||||
let selectedColor: UIColor
|
let selectedColor: UIColor
|
||||||
let selectionFraction: CGFloat
|
let selectionFraction: CGFloat
|
||||||
|
|
||||||
@ -813,6 +821,7 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
content: TabSelectorComponent.Item.Content,
|
content: TabSelectorComponent.Item.Content,
|
||||||
font: UIFont,
|
font: UIFont,
|
||||||
color: UIColor,
|
color: UIColor,
|
||||||
|
normalColor: UIColor?,
|
||||||
selectedColor: UIColor,
|
selectedColor: UIColor,
|
||||||
selectionFraction: CGFloat
|
selectionFraction: CGFloat
|
||||||
) {
|
) {
|
||||||
@ -820,6 +829,7 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
self.content = content
|
self.content = content
|
||||||
self.font = font
|
self.font = font
|
||||||
self.color = color
|
self.color = color
|
||||||
|
self.normalColor = normalColor
|
||||||
self.selectedColor = selectedColor
|
self.selectedColor = selectedColor
|
||||||
self.selectionFraction = selectionFraction
|
self.selectionFraction = selectionFraction
|
||||||
}
|
}
|
||||||
@ -837,6 +847,9 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
if lhs.color != rhs.color {
|
if lhs.color != rhs.color {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.normalColor != rhs.normalColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.selectedColor != rhs.selectedColor {
|
if lhs.selectedColor != rhs.selectedColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -856,7 +869,7 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
|
|
||||||
switch component.content {
|
switch component.content {
|
||||||
case let .text(text):
|
case let .text(text):
|
||||||
let attributedTitle = NSMutableAttributedString(string: text, font: component.font, textColor: component.color)
|
let attributedTitle = NSMutableAttributedString(string: text, font: component.font, textColor: component.normalColor ?? component.color)
|
||||||
var range = (attributedTitle.string as NSString).range(of: "⭐️")
|
var range = (attributedTitle.string as NSString).range(of: "⭐️")
|
||||||
if range.location != NSNotFound {
|
if range.location != NSNotFound {
|
||||||
attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
|
attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
|
||||||
@ -879,7 +892,12 @@ private final class ItemComponent: CombinedComponent {
|
|||||||
.opacity(1.0 - component.selectionFraction)
|
.opacity(1.0 - component.selectionFraction)
|
||||||
)
|
)
|
||||||
|
|
||||||
let selectedAttributedTitle = NSMutableAttributedString(string: text, font: component.font, textColor: component.selectedColor)
|
var selectedColor = component.selectedColor
|
||||||
|
if let _ = component.normalColor {
|
||||||
|
selectedColor = component.color
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedAttributedTitle = NSMutableAttributedString(string: text, font: component.font, textColor: selectedColor)
|
||||||
range = (selectedAttributedTitle.string as NSString).range(of: "⭐️")
|
range = (selectedAttributedTitle.string as NSString).range(of: "⭐️")
|
||||||
if range.location != NSNotFound {
|
if range.location != NSNotFound {
|
||||||
selectedAttributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
|
selectedAttributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
|
||||||
|
|||||||
@ -5,7 +5,6 @@ PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
|||||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="7.16309" height="12.7222">
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="7.16309" height="12.7222">
|
||||||
<g>
|
<g>
|
||||||
<rect height="12.7222" opacity="0" width="7.16309" x="0" y="0"/>
|
<path d="M7.16309 6.35742C7.16309 6.17432 7.08984 6.00586 6.95068 5.87402L1.1499 0.19043C1.01807 0.065918 0.856934 0 0.666504 0C0.292969 0 0 0.285645 0 0.666504C0 0.849609 0.0732422 1.01807 0.19043 1.14258L5.52246 6.35742L0.19043 11.5723C0.0732422 11.6968 0 11.8579 0 12.0483C0 12.4292 0.292969 12.7148 0.666504 12.7148C0.856934 12.7148 1.01807 12.6489 1.1499 12.5171L6.95068 6.84082C7.08984 6.70166 7.16309 6.54053 7.16309 6.35742Z" fill="#ffffff" fill-opacity="1.0"/>
|
||||||
<path d="M7.16309 6.35742C7.16309 6.17432 7.08984 6.00586 6.95068 5.87402L1.1499 0.19043C1.01807 0.065918 0.856934 0 0.666504 0C0.292969 0 0 0.285645 0 0.666504C0 0.849609 0.0732422 1.01807 0.19043 1.14258L5.52246 6.35742L0.19043 11.5723C0.0732422 11.6968 0 11.8579 0 12.0483C0 12.4292 0.292969 12.7148 0.666504 12.7148C0.856934 12.7148 1.01807 12.6489 1.1499 12.5171L6.95068 6.84082C7.08984 6.70166 7.16309 6.54053 7.16309 6.35742Z" fill="#ffffff" fill-opacity="0.85"/>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 879 B After Width: | Height: | Size: 811 B |
BIN
submodules/TelegramUI/Resources/Animations/Style.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/Style.tgs
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user