mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
Stars reactions
This commit is contained in:
@@ -174,7 +174,7 @@ private final class BadgeComponent: Component {
|
||||
private let badgeShapeLayer = SimpleShapeLayer()
|
||||
|
||||
private let badgeForeground: SimpleLayer
|
||||
private let badgeIcon: UIImageView
|
||||
let badgeIcon: UIImageView
|
||||
private let badgeLabel: BadgeLabelView
|
||||
private let badgeLabelMaskView = UIImageView()
|
||||
|
||||
@@ -480,17 +480,20 @@ private final class PeerComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let peer: EnginePeer
|
||||
let count: Int
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
peer: EnginePeer
|
||||
peer: EnginePeer,
|
||||
count: Int
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.peer = peer
|
||||
self.count = count
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerComponent, rhs: PeerComponent) -> Bool {
|
||||
@@ -506,6 +509,9 @@ private final class PeerComponent: Component {
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -546,7 +552,7 @@ private final class PeerComponent: Component {
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PeerBadgeComponent(
|
||||
theme: component.theme,
|
||||
title: "800"
|
||||
title: "\(component.count)"
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||
@@ -590,21 +596,163 @@ private final class PeerComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private final class SliderBackgroundComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let value: CGFloat
|
||||
let topCutoff: CGFloat?
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
value: CGFloat,
|
||||
topCutoff: CGFloat?
|
||||
) {
|
||||
self.theme = theme
|
||||
self.value = value
|
||||
self.topCutoff = topCutoff
|
||||
}
|
||||
|
||||
static func ==(lhs: SliderBackgroundComponent, rhs: SliderBackgroundComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
if lhs.topCutoff != rhs.topCutoff {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let sliderBackground = UIView()
|
||||
private let sliderForeground = UIView()
|
||||
private let sliderStars = SliderStarsView()
|
||||
|
||||
private let topForegroundLine = SimpleLayer()
|
||||
private let topBackgroundLine = SimpleLayer()
|
||||
private let topForegroundText = ComponentView<Empty>()
|
||||
private let topBackgroundText = ComponentView<Empty>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.sliderBackground.clipsToBounds = true
|
||||
|
||||
self.sliderForeground.clipsToBounds = true
|
||||
self.sliderForeground.addSubview(self.sliderStars)
|
||||
|
||||
self.addSubview(self.sliderBackground)
|
||||
self.addSubview(self.sliderForeground)
|
||||
|
||||
self.sliderBackground.layer.addSublayer(self.topBackgroundLine)
|
||||
self.sliderForeground.layer.addSublayer(self.topForegroundLine)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: SliderBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.sliderBackground.backgroundColor = UIColor(rgb: 0xEEEEEF)
|
||||
self.sliderForeground.backgroundColor = UIColor(rgb: 0xFFB10D)
|
||||
self.topForegroundLine.backgroundColor = component.theme.list.plainBackgroundColor.cgColor
|
||||
self.topBackgroundLine.backgroundColor = UIColor(white: 0.0, alpha: 0.1).cgColor
|
||||
|
||||
transition.setFrame(view: self.sliderBackground, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
let sliderMinWidth = availableSize.height
|
||||
let sliderAreaWidth: CGFloat = availableSize.width - sliderMinWidth
|
||||
let sliderForegroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: sliderMinWidth + floorToScreenPixels(sliderAreaWidth * component.value), height: availableSize.height))
|
||||
transition.setFrame(view: self.sliderForeground, frame: sliderForegroundFrame)
|
||||
|
||||
self.sliderBackground.layer.cornerRadius = availableSize.height * 0.5
|
||||
self.sliderForeground.layer.cornerRadius = availableSize.height * 0.5
|
||||
|
||||
self.sliderStars.frame = CGRect(origin: .zero, size: availableSize)
|
||||
self.sliderStars.update(size: availableSize, value: component.value)
|
||||
|
||||
self.sliderForeground.isHidden = sliderForegroundFrame.width <= sliderMinWidth
|
||||
|
||||
let topCutoff = component.topCutoff ?? 0.0
|
||||
|
||||
let topLineFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(sliderAreaWidth * topCutoff), y: 0.0), size: CGSize(width: 1.0, height: availableSize.height))
|
||||
transition.setFrame(layer: self.topForegroundLine, frame: topLineFrame)
|
||||
transition.setFrame(layer: self.topBackgroundLine, frame: topLineFrame)
|
||||
|
||||
//TODO:localize
|
||||
let topTextSize = self.topForegroundText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "TOP", font: Font.medium(17.0), textColor: UIColor(white: 1.0, alpha: 0.4)))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
)
|
||||
let _ = self.topBackgroundText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "TOP", font: Font.medium(17.0), textColor: UIColor(white: 0.0, alpha: 0.1)))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
)
|
||||
let topTextFrame = CGRect(origin: CGPoint(x: topLineFrame.maxX + 6.0, y: floor((availableSize.height - topTextSize.height) * 0.5)), size: topTextSize)
|
||||
if let topForegroundTextView = self.topForegroundText.view, let topBackgroundTextView = self.topBackgroundText.view {
|
||||
if topForegroundTextView.superview == nil {
|
||||
topBackgroundTextView.layer.anchorPoint = CGPoint()
|
||||
self.sliderBackground.addSubview(topBackgroundTextView)
|
||||
|
||||
topForegroundTextView.layer.anchorPoint = CGPoint()
|
||||
self.sliderForeground.addSubview(topForegroundTextView)
|
||||
}
|
||||
|
||||
transition.setPosition(view: topForegroundTextView, position: topTextFrame.origin)
|
||||
transition.setPosition(view: topBackgroundTextView, position: topTextFrame.origin)
|
||||
|
||||
topForegroundTextView.bounds = CGRect(origin: CGPoint(), size: topTextFrame.size)
|
||||
topBackgroundTextView.bounds = CGRect(origin: CGPoint(), size: topTextFrame.size)
|
||||
|
||||
topForegroundTextView.isHidden = component.topCutoff == nil || topTextFrame.maxX >= availableSize.width - 4.0
|
||||
topBackgroundTextView.isHidden = component.topCutoff == nil || topTextFrame.maxX >= availableSize.width - 4.0
|
||||
}
|
||||
|
||||
if component.topCutoff == nil {
|
||||
self.topForegroundLine.isHidden = true
|
||||
self.topBackgroundLine.isHidden = true
|
||||
} else {
|
||||
self.topForegroundLine.isHidden = false
|
||||
self.topBackgroundLine.isHidden = false
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChatSendStarsScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer
|
||||
let balance: Int64?
|
||||
let topPeers: [EnginePeer]
|
||||
let completion: (Int64) -> Void
|
||||
let topPeers: [ChatSendStarsScreen.TopPeer]
|
||||
let completion: (Int64, ChatSendStarsScreen.TransitionOut) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peer: EnginePeer,
|
||||
balance: Int64?,
|
||||
topPeers: [EnginePeer],
|
||||
completion: @escaping (Int64) -> Void
|
||||
topPeers: [ChatSendStarsScreen.TopPeer],
|
||||
completion: @escaping (Int64, ChatSendStarsScreen.TransitionOut) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
@@ -664,10 +812,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
private let descriptionText = ComponentView<Empty>()
|
||||
|
||||
private let badgeStars = BadgeStarsView()
|
||||
private let sliderBackground = ComponentView<Empty>()
|
||||
private let slider = ComponentView<Empty>()
|
||||
private let sliderBackground = UIView()
|
||||
private let sliderForeground = UIView()
|
||||
private let sliderStars = SliderStarsView()
|
||||
private let badge = ComponentView<Empty>()
|
||||
|
||||
private var topPeersLeftSeparator: SimpleLayer?
|
||||
@@ -695,6 +841,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
private var cachedStarImage: (UIImage, PresentationTheme)?
|
||||
private var cachedCloseImage: UIImage?
|
||||
|
||||
private var isPastTopCutoff: Bool?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.bottomOverscrollLimit = 200.0
|
||||
|
||||
@@ -740,9 +888,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
self.scrollView.addSubview(self.scrollContentView)
|
||||
|
||||
self.sliderForeground.clipsToBounds = true
|
||||
self.sliderForeground.addSubview(self.sliderStars)
|
||||
|
||||
self.addSubview(self.navigationBarContainer)
|
||||
|
||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
@@ -955,34 +1100,44 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
containerSize: CGSize(width: availableSize.width - sliderInset * 2.0, height: 30.0)
|
||||
)
|
||||
let sliderFrame = CGRect(origin: CGPoint(x: sliderInset, y: contentHeight + 127.0), size: sliderSize)
|
||||
if let sliderView = self.slider.view {
|
||||
let sliderBackgroundFrame = CGRect(origin: CGPoint(x: sliderFrame.minX - 8.0, y: sliderFrame.minY + 7.0), size: CGSize(width: sliderFrame.width + 16.0, height: sliderFrame.height - 14.0))
|
||||
|
||||
let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(1000 - 1)
|
||||
|
||||
var topCutoffFraction: CGFloat?
|
||||
if let maxCount = component.topPeers.max(by: { $0.count < $1.count })?.count {
|
||||
let topCutoffFractionValue = CGFloat(maxCount) / CGFloat(1000 - 1)
|
||||
topCutoffFraction = topCutoffFractionValue
|
||||
|
||||
let isPastCutoff = progressFraction >= topCutoffFractionValue
|
||||
if let isPastTopCutoff = self.isPastTopCutoff, isPastTopCutoff != isPastCutoff {
|
||||
HapticFeedback().tap()
|
||||
}
|
||||
self.isPastTopCutoff = isPastCutoff
|
||||
} else {
|
||||
self.isPastTopCutoff = nil
|
||||
}
|
||||
|
||||
let _ = self.sliderBackground.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(SliderBackgroundComponent(
|
||||
theme: environment.theme,
|
||||
value: progressFraction,
|
||||
topCutoff: topCutoffFraction
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: sliderBackgroundFrame.size
|
||||
)
|
||||
|
||||
if let sliderView = self.slider.view, let sliderBackgroundView = self.sliderBackground.view {
|
||||
if sliderView.superview == nil {
|
||||
self.scrollContentView.addSubview(self.badgeStars)
|
||||
self.scrollContentView.addSubview(self.sliderBackground)
|
||||
self.scrollContentView.addSubview(self.sliderForeground)
|
||||
self.scrollContentView.addSubview(sliderBackgroundView)
|
||||
self.scrollContentView.addSubview(sliderView)
|
||||
}
|
||||
transition.setFrame(view: sliderView, frame: sliderFrame)
|
||||
|
||||
self.sliderBackground.backgroundColor = UIColor(rgb: 0xEEEEEF)
|
||||
self.sliderForeground.backgroundColor = UIColor(rgb: 0xFFB10D)
|
||||
|
||||
let sliderBackgroundFrame = CGRect(origin: CGPoint(x: sliderFrame.minX - 8.0, y: sliderFrame.minY + 7.0), size: CGSize(width: sliderFrame.width + 16.0, height: sliderFrame.height - 14.0))
|
||||
transition.setFrame(view: self.sliderBackground, frame: sliderBackgroundFrame)
|
||||
|
||||
let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(1000 - 1)
|
||||
let sliderMinWidth = sliderBackgroundFrame.height
|
||||
let sliderAreaWidth: CGFloat = sliderBackgroundFrame.width - sliderMinWidth
|
||||
let sliderForegroundFrame = CGRect(origin: CGPoint(x: sliderBackgroundFrame.minX, y: sliderBackgroundFrame.minY), size: CGSize(width: sliderMinWidth + floorToScreenPixels(sliderAreaWidth * progressFraction), height: sliderBackgroundFrame.height))
|
||||
transition.setFrame(view: self.sliderForeground, frame: sliderForegroundFrame)
|
||||
|
||||
self.sliderBackground.layer.cornerRadius = sliderBackgroundFrame.height * 0.5
|
||||
self.sliderForeground.layer.cornerRadius = sliderBackgroundFrame.height * 0.5
|
||||
|
||||
self.sliderStars.frame = CGRect(origin: .zero, size: sliderBackgroundFrame.size)
|
||||
self.sliderStars.update(size: sliderBackgroundFrame.size, value: progressFraction)
|
||||
|
||||
self.sliderForeground.isHidden = sliderForegroundFrame.width <= sliderMinWidth
|
||||
transition.setFrame(view: sliderBackgroundView, frame: sliderBackgroundFrame)
|
||||
|
||||
var effectiveInertiaDirection = self.inertiaDirection
|
||||
if progressFraction <= 0.03 || progressFraction >= 0.97 {
|
||||
@@ -999,6 +1154,11 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||
)
|
||||
|
||||
let sliderMinWidth = sliderBackgroundFrame.height
|
||||
let sliderAreaWidth: CGFloat = sliderBackgroundFrame.width - sliderMinWidth
|
||||
let sliderForegroundFrame = CGRect(origin: sliderBackgroundFrame.origin, size: CGSize(width: sliderMinWidth + floorToScreenPixels(sliderAreaWidth * progressFraction), height: sliderBackgroundFrame.height))
|
||||
|
||||
var badgeFrame = CGRect(origin: CGPoint(x: sliderForegroundFrame.minX + sliderForegroundFrame.width - floorToScreenPixels(sliderMinWidth * 0.5), y: sliderForegroundFrame.minY - 8.0), size: badgeSize)
|
||||
if let badgeView = self.badge.view as? BadgeComponent.View {
|
||||
if badgeView.superview == nil {
|
||||
@@ -1215,14 +1375,14 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
var validIds: [EnginePeer.Id] = []
|
||||
var items: [(itemView: ComponentView<Empty>, size: CGSize)] = []
|
||||
for topPeer in component.topPeers {
|
||||
validIds.append(topPeer.id)
|
||||
validIds.append(topPeer.peer.id)
|
||||
|
||||
let itemView: ComponentView<Empty>
|
||||
if let current = self.topPeerItems[topPeer.id] {
|
||||
if let current = self.topPeerItems[topPeer.peer.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemView = ComponentView()
|
||||
self.topPeerItems[topPeer.id] = itemView
|
||||
self.topPeerItems[topPeer.peer.id] = itemView
|
||||
}
|
||||
|
||||
let itemSize = itemView.update(
|
||||
@@ -1231,7 +1391,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: topPeer
|
||||
peer: topPeer.peer,
|
||||
count: topPeer.count
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||
@@ -1306,7 +1467,15 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.completion(self.amount)
|
||||
guard let badgeView = self.badge.view as? BadgeComponent.View else {
|
||||
return
|
||||
}
|
||||
component.completion(
|
||||
self.amount,
|
||||
ChatSendStarsScreen.TransitionOut(
|
||||
sourceView: badgeView.badgeIcon
|
||||
)
|
||||
)
|
||||
self.environment?.controller()?.dismiss()
|
||||
}
|
||||
)),
|
||||
@@ -1399,14 +1568,14 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
|
||||
public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
public final class InitialData {
|
||||
let peer: EnginePeer
|
||||
let balance: Int64?
|
||||
let topPeers: [EnginePeer]
|
||||
fileprivate let peer: EnginePeer
|
||||
fileprivate let balance: Int64?
|
||||
fileprivate let topPeers: [ChatSendStarsScreen.TopPeer]
|
||||
|
||||
fileprivate init(
|
||||
peer: EnginePeer,
|
||||
balance: Int64?,
|
||||
topPeers: [EnginePeer]
|
||||
topPeers: [ChatSendStarsScreen.TopPeer]
|
||||
) {
|
||||
self.peer = peer
|
||||
self.balance = balance
|
||||
@@ -1414,13 +1583,41 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate final class TopPeer: Equatable {
|
||||
let peer: EnginePeer
|
||||
let count: Int
|
||||
|
||||
init(peer: EnginePeer, count: Int) {
|
||||
self.peer = peer
|
||||
self.count = count
|
||||
}
|
||||
|
||||
static func ==(lhs: TopPeer, rhs: TopPeer) -> Bool {
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class TransitionOut {
|
||||
public let sourceView: UIView
|
||||
|
||||
init(sourceView: UIView) {
|
||||
self.sourceView = sourceView
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
private var presenceDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, initialData: InitialData, completion: @escaping (Int64) -> Void) {
|
||||
public init(context: AccountContext, initialData: InitialData, completion: @escaping (Int64, TransitionOut) -> Void) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: ChatSendStarsScreenComponent(
|
||||
@@ -1454,7 +1651,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
}
|
||||
|
||||
public static func initialData(context: AccountContext, peerId: EnginePeer.Id) -> Signal<InitialData?, NoError> {
|
||||
public static func initialData(context: AccountContext, peerId: EnginePeer.Id, topPeers: [ReactionsMessageAttribute.TopPeer]) -> Signal<InitialData?, NoError> {
|
||||
let balance: Signal<Int64?, NoError>
|
||||
if let starsContext = context.starsContext {
|
||||
balance = starsContext.state
|
||||
@@ -1467,19 +1664,33 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)),
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
|
||||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
|
||||
EngineDataMap(topPeers.map(\.peerId).map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
),
|
||||
balance
|
||||
)
|
||||
|> map { peer, accountPeer, balance -> InitialData? in
|
||||
guard let peer, let accountPeer else {
|
||||
|> map { peerAndTopPeerMap, balance -> InitialData? in
|
||||
let (peer, topPeerMap) = peerAndTopPeerMap
|
||||
guard let peer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return InitialData(
|
||||
peer: peer,
|
||||
balance: balance,
|
||||
topPeers: [accountPeer, peer]
|
||||
topPeers: topPeers.compactMap { topPeer -> ChatSendStarsScreen.TopPeer? in
|
||||
guard let topPeerValue = topPeerMap[topPeer.peerId] else {
|
||||
return nil
|
||||
}
|
||||
guard let topPeerValue else {
|
||||
return nil
|
||||
}
|
||||
return ChatSendStarsScreen.TopPeer(
|
||||
peer: topPeerValue,
|
||||
count: Int(topPeer.count)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user