Swiftgram/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift
2024-07-13 18:13:58 +04:00

322 lines
13 KiB
Swift

import Foundation
import UIKit
import TelegramCore
import SwiftSignalKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import TelegramUIPreferences
import ActivityIndicator
import AccountContext
import AppBundle
private enum JoinState: Equatable {
case none
case notJoined
case inProgress
case joined(justNow: Bool)
static func ==(lhs: JoinState, rhs: JoinState) -> Bool {
switch lhs {
case .none:
if case .none = rhs {
return true
} else {
return false
}
case .notJoined:
if case .notJoined = rhs {
return true
} else {
return false
}
case .inProgress:
if case .inProgress = rhs {
return true
} else {
return false
}
case let .joined(justNow):
if case .joined(justNow) = rhs {
return true
} else {
return false
}
}
}
}
public final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
private let context: AccountContext
let safeInset: CGFloat
private let transparent: Bool
private let rtl: Bool
private var strings: PresentationStrings
private var nameDisplayOrder: PresentationPersonNameOrder
private var theme: InstantPageTheme
private let openPeer: (EnginePeer) -> Void
private let highlightedBackgroundNode: ASDisplayNode
private let buttonNode: HighlightableButtonNode
private let nameNode: ASTextNode
private let joinNode: HighlightableButtonNode
private let activityIndicator: ActivityIndicator
private let checkNode: ASImageNode
var peer: EnginePeer?
private var peerDisposable: Disposable?
private let joinDisposable = MetaDisposable()
private var joinState: JoinState = .none
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, initialPeer: EnginePeer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (EnginePeer) -> Void) {
self.context = context
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder
self.theme = theme
self.peer = initialPeer
self.safeInset = safeInset
self.transparent = transparent
self.rtl = rtl
self.openPeer = openPeer
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.highlightedBackgroundNode.alpha = 0.0
self.buttonNode = HighlightableButtonNode()
self.nameNode = ASTextNode()
self.nameNode.isUserInteractionEnabled = false
self.nameNode.maximumNumberOfLines = 1
self.joinNode = HighlightableButtonNode()
self.joinNode.hitTestSlop = UIEdgeInsets(top: -17.0, left: -17.0, bottom: -17.0, right: -17.0)
self.activityIndicator = ActivityIndicator(type: .custom(theme.panelAccentColor, 22.0, 2.0, false))
self.checkNode = ASImageNode()
self.checkNode.isLayerBacked = true
self.checkNode.displayWithoutProcessing = true
self.checkNode.displaysAsynchronously = false
self.checkNode.isHidden = true
super.init()
if self.transparent {
self.backgroundColor = UIColor(white: 0.0, alpha: 0.6)
self.highlightedBackgroundNode.backgroundColor = UIColor(white: 1.0, alpha: 0.1)
} else {
self.backgroundColor = theme.panelBackgroundColor
self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor
}
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.joinNode)
self.addSubnode(self.checkNode)
self.addSubnode(self.nameNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.highlightedBackgroundNode.alpha = 1.0
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
}
}
}
self.joinNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.joinNode.layer.removeAnimation(forKey: "opacity")
strongSelf.joinNode.alpha = 0.4
} else {
strongSelf.joinNode.alpha = 1.0
strongSelf.joinNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.joinNode.addTarget(self, action: #selector(self.joinPressed), forControlEvents: .touchUpInside)
let account = self.context.account
let engine = context.engine
let signal: Signal<EnginePeer, NoError> = actualizedPeer(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, peer: initialPeer._asPeer())
|> mapToSignal({ peer -> Signal<EnginePeer, NoError> in
if let peer = peer as? TelegramChannel, let username = peer.addressName, peer.accessHash == nil {
return .single(.channel(peer)) |> then(engine.peers.resolvePeerByName(name: username)
|> mapToSignal({ result -> Signal<EnginePeer, NoError> in
guard case let .result(updatedPeer) = result else {
return .complete()
}
if let updatedPeer = updatedPeer {
return .single(updatedPeer)
} else {
return .single(.channel(peer))
}
}))
} else {
return .single(EnginePeer(peer))
}
})
self.peerDisposable = (signal |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self {
strongSelf.peer = peer
if case let .channel(peer) = peer {
var joinState = strongSelf.joinState
if case .member = peer.participationStatus {
switch joinState {
case .none:
joinState = .joined(justNow: false)
case .inProgress, .notJoined:
joinState = .joined(justNow: true)
case .joined:
break
}
} else {
joinState = .notJoined
}
strongSelf.updateJoinState(joinState)
}
strongSelf.applyThemeAndStrings(themeUpdated: false)
strongSelf.setNeedsLayout()
}
})
self.applyThemeAndStrings(themeUpdated: true)
}
deinit {
self.peerDisposable?.dispose()
self.joinDisposable.dispose()
}
public func update(strings: PresentationStrings, theme: InstantPageTheme) {
if self.strings !== strings || self.theme !== theme {
let themeUpdated = self.theme !== theme
self.strings = strings
self.theme = theme
self.applyThemeAndStrings(themeUpdated: themeUpdated)
}
}
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
private func applyThemeAndStrings(themeUpdated: Bool) {
if let peer = self.peer {
let textColor = self.transparent ? UIColor.white : self.theme.panelPrimaryColor
self.nameNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: Font.medium(17.0), textColor: textColor)
}
let accentColor = self.transparent ? UIColor.white : self.theme.panelAccentColor
self.joinNode.setAttributedTitle(NSAttributedString(string: self.strings.Channel_JoinChannel, font: Font.medium(17.0), textColor: accentColor), for: [])
if themeUpdated {
let secondaryColor = self.transparent ? UIColor.white : self.theme.panelSecondaryColor
self.checkNode.image = generateTintedImage(image: UIImage(bundleImageName: "Instant View/PanelCheck"), color: secondaryColor)
self.activityIndicator.type = .custom(self.theme.panelAccentColor, 22.0, 2.0, false)
if !self.transparent {
self.backgroundColor = self.theme.panelBackgroundColor
self.highlightedBackgroundNode.backgroundColor = self.theme.panelHighlightedBackgroundColor
}
}
self.setNeedsLayout()
}
private func updateJoinState(_ joinState: JoinState) {
if self.joinState != joinState {
self.joinState = joinState
switch joinState {
case .none:
self.joinNode.isHidden = true
self.checkNode.isHidden = true
if self.activityIndicator.supernode != nil {
self.activityIndicator.removeFromSupernode()
}
case .notJoined:
self.joinNode.isHidden = false
self.checkNode.isHidden = true
if self.activityIndicator.supernode != nil {
self.activityIndicator.removeFromSupernode()
}
case .inProgress:
self.joinNode.isHidden = true
self.checkNode.isHidden = true
if self.activityIndicator.supernode == nil {
self.addSubnode(self.activityIndicator)
}
case let .joined(justNow):
self.joinNode.isHidden = true
self.checkNode.isHidden = !justNow
if self.activityIndicator.supernode != nil {
self.activityIndicator.removeFromSupernode()
}
}
}
}
public override func layout() {
super.layout()
let size = self.bounds.size
let inset: CGFloat = 17.0 + safeInset
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
let joinSize = self.joinNode.measure(size)
let nameSize = self.nameNode.measure(CGSize(width: size.width - inset * 2.0 - joinSize.width, height: size.height))
let checkSize = self.checkNode.measure(size)
let indicatorSize = self.activityIndicator.measure(size)
if self.rtl {
self.nameNode.frame = CGRect(origin: CGPoint(x: size.width - inset - nameSize.width, y: floor((size.height - nameSize.height) / 2.0)), size: nameSize)
self.joinNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - joinSize.height) / 2.0)), size: joinSize)
self.checkNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize)
self.activityIndicator.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize)
} else {
self.nameNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - nameSize.height) / 2.0)), size: nameSize)
self.joinNode.frame = CGRect(origin: CGPoint(x: size.width - inset - joinSize.width, y: floor((size.height - joinSize.height) / 2.0)), size: joinSize)
self.checkNode.frame = CGRect(origin: CGPoint(x: size.width - inset - checkSize.width, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize)
self.activityIndicator.frame = CGRect(origin: CGPoint(x: size.width - inset - indicatorSize.width, y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize)
}
}
public func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
return nil
}
public func updateHiddenMedia(media: InstantPageMedia?) {
}
public func updateIsVisible(_ isVisible: Bool) {
}
@objc func buttonPressed() {
if let peer = self.peer {
self.openPeer(peer)
}
}
@objc func joinPressed() {
if let peer = self.peer, case .notJoined = self.joinState {
self.updateJoinState(.inProgress)
self.joinDisposable.set((self.context.engine.peers.joinChannel(peerId: peer.id, hash: nil) |> deliverOnMainQueue).start(error: { [weak self] _ in
if let strongSelf = self {
if case .inProgress = strongSelf.joinState {
strongSelf.updateJoinState(.notJoined)
}
}
}))
}
}
}