Swiftgram/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift
Ilya Laktyushin 980a2c47bc Various fixes
2023-02-20 04:30:51 +04:00

333 lines
14 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import TelegramPresentationData
import AvatarNode
import AccountContext
import SelectablePeerNode
import ShareController
import SolidRoundedButtonNode
import ActivityIndicator
private let avatarFont = avatarPlaceholderFont(size: 42.0)
private final class MoreNode: ASDisplayNode {
private let avatarNode = AvatarNode(font: Font.regular(24.0))
init(count: Int) {
super.init()
self.addSubnode(self.avatarNode)
self.avatarNode.setCustomLetters(["+\(count)"])
}
func updateLayout(size: CGSize) {
self.avatarNode.frame = CGRect(origin: CGPoint(x: floor((size.width - 60.0) / 2.0), y: 4.0), size: CGSize(width: 60.0, height: 60.0))
}
}
final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainerNode {
enum Content {
case invite(isGroup: Bool, image: TelegramMediaImageRepresentation?, title: String, memberCount: Int32, members: [EnginePeer])
case request(isGroup: Bool, image: TelegramMediaImageRepresentation?, title: String, about: String?, memberCount: Int32)
var isGroup: Bool {
switch self {
case let .invite(isGroup, _, _, _, _), let .request(isGroup, _, _, _, _):
return isGroup
}
}
var image: TelegramMediaImageRepresentation? {
switch self {
case let .invite(_, image, _, _, _), let .request(_, image, _, _, _):
return image
}
}
var title: String {
switch self {
case let .invite(_, _, title, _, _), let .request(_, _, title, _, _):
return title
}
}
var memberCount: Int32 {
switch self {
case let .invite(_, _, _, memberCount, _), let .request(_, _, _, _, memberCount):
return memberCount
}
}
}
private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
private let avatarNode: AvatarNode
private let titleNode: ASTextNode
private let countNode: ASTextNode
private let aboutNode: ASTextNode
private let descriptionNode: ASTextNode
private let peersScrollNode: ASScrollNode
private let peerNodes: [SelectablePeerNode]
private let moreNode: MoreNode?
private let actionButtonNode: SolidRoundedButtonNode
var join: (() -> Void)?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: JoinLinkPreviewPeerContentNode.Content) {
self.avatarNode = AvatarNode(font: avatarFont)
self.titleNode = ASTextNode()
self.titleNode.textAlignment = .center
self.countNode = ASTextNode()
self.aboutNode = ASTextNode()
self.aboutNode.maximumNumberOfLines = 8
self.aboutNode.textAlignment = .center
self.descriptionNode = ASTextNode()
self.descriptionNode.maximumNumberOfLines = 3
self.descriptionNode.textAlignment = .center
self.peersScrollNode = ASScrollNode()
self.peersScrollNode.view.showsHorizontalScrollIndicator = false
self.actionButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 11.0, gloss: false)
let itemTheme = SelectablePeerNodeTheme(textColor: theme.actionSheet.primaryTextColor, secretTextColor: .green, selectedTextColor: theme.actionSheet.controlAccentColor, checkBackgroundColor: theme.actionSheet.opaqueItemBackgroundColor, checkFillColor: theme.actionSheet.controlAccentColor, checkColor: theme.actionSheet.opaqueItemBackgroundColor, avatarPlaceholderColor: theme.list.mediaPlaceholderColor)
if case let .invite(isGroup, _, _, memberCount, members) = content {
self.peerNodes = members.compactMap { peer in
guard peer.id != context.account.peerId else {
return nil
}
let node = SelectablePeerNode()
node.setup(context: context, theme: theme, strings: strings, peer: EngineRenderedPeer(peer: peer), synchronousLoad: false)
node.theme = itemTheme
return node
}
if members.count < Int(memberCount) {
self.moreNode = MoreNode(count: Int(memberCount) - members.count)
} else {
self.moreNode = nil
}
self.actionButtonNode.title = isGroup ? strings.Invitation_JoinGroup : strings.Channel_JoinChannel
} else {
self.peerNodes = []
self.moreNode = nil
self.actionButtonNode.title = content.isGroup ? strings.MemberRequests_RequestToJoinGroup : strings.MemberRequests_RequestToJoinChannel
}
super.init()
let peer = TelegramGroup(id: EnginePeer.Id(0), title: content.title, photo: content.image.flatMap { [$0] } ?? [], participantCount: Int(content.memberCount), role: .member, membership: .Left, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
self.addSubnode(self.avatarNode)
self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer), emptyColor: theme.list.mediaPlaceholderColor)
self.addSubnode(self.titleNode)
self.titleNode.attributedText = NSAttributedString(string: content.title, font: Font.semibold(24.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
self.addSubnode(self.countNode)
let membersString: String
if content.isGroup {
if case let .invite(_, _, _, memberCount, members) = content, !members.isEmpty {
membersString = strings.Invitation_Members(memberCount)
} else {
membersString = strings.Conversation_StatusMembers(content.memberCount)
}
} else {
membersString = strings.Conversation_StatusSubscribers(content.memberCount)
}
self.countNode.attributedText = NSAttributedString(string: membersString, font: Font.regular(15.0), textColor: theme.actionSheet.secondaryTextColor, paragraphAlignment: .center)
if !self.peerNodes.isEmpty {
for peerNode in peerNodes {
self.peersScrollNode.addSubnode(peerNode)
}
self.addSubnode(self.peersScrollNode)
}
self.moreNode.flatMap(self.peersScrollNode.addSubnode)
if case let .request(isGroup, _, _, about, _) = content {
if let about = about, !about.isEmpty {
self.aboutNode.attributedText = NSAttributedString(string: about, font: Font.regular(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
self.addSubnode(self.aboutNode)
}
self.descriptionNode.attributedText = NSAttributedString(string: isGroup ? strings.MemberRequests_RequestToJoinDescriptionGroup : strings.MemberRequests_RequestToJoinDescriptionChannel, font: Font.regular(15.0), textColor: theme.actionSheet.secondaryTextColor, paragraphAlignment: .center)
self.addSubnode(self.descriptionNode)
}
self.actionButtonNode.pressed = { [weak self] in
self?.join?()
self?.actionButtonNode.transitionToProgress()
}
self.addSubnode(self.actionButtonNode)
}
func activate() {
}
func deactivate() {
}
func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) {
}
func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) {
self.contentOffsetUpdated = f
}
func updateTheme(_ theme: PresentationTheme) {
}
func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let showPeers = !self.peerNodes.isEmpty && !isLandscape
var nodeHeight: CGFloat = (!showPeers ? 236.0 : 320.0)
let paddedSize = CGSize(width: size.width - 60.0, height: size.height)
self.peersScrollNode.isHidden = !showPeers
var aboutSize: CGSize?
var descriptionSize: CGSize?
if self.aboutNode.supernode != nil {
if isLandscape {
self.aboutNode.maximumNumberOfLines = 3
} else {
self.aboutNode.maximumNumberOfLines = 8
}
let measuredSize = self.aboutNode.measure(paddedSize)
nodeHeight += measuredSize.height + 20.0
aboutSize = measuredSize
}
if isLandscape {
self.descriptionNode.removeFromSupernode()
} else if self.descriptionNode.supernode == nil {
self.addSubnode(self.descriptionNode)
}
if self.descriptionNode.supernode != nil {
let measuredSize = self.descriptionNode.measure(paddedSize)
nodeHeight += measuredSize.height + 20.0 + 10.0
descriptionSize = measuredSize
}
let constrainSize = CGSize(width: size.width - 32.0, height: size.height)
let titleSize = self.titleNode.measure(constrainSize)
nodeHeight += titleSize.height
let verticalOrigin = size.height - nodeHeight
let avatarSize: CGFloat = 100.0
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: floor((size.width - avatarSize) / 2.0), y: verticalOrigin + 32.0), size: CGSize(width: avatarSize, height: avatarSize)))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: verticalOrigin + 27.0 + avatarSize + 15.0), size: titleSize))
let countSize = self.countNode.measure(constrainSize)
transition.updateFrame(node: self.countNode, frame: CGRect(origin: CGPoint(x: floor((size.width - countSize.width) / 2.0), y: verticalOrigin + 27.0 + avatarSize + 15.0 + titleSize.height + 3.0), size: countSize))
var verticalOffset = verticalOrigin + 27.0 + avatarSize + 15.0 + titleSize.height + 3.0 + countSize.height + 18.0
if let aboutSize = aboutSize {
transition.updateFrame(node: self.aboutNode, frame: CGRect(origin: CGPoint(x: floor((size.width - aboutSize.width) / 2.0), y: verticalOffset), size: aboutSize))
verticalOffset += aboutSize.height + 20.0
}
let peerSize = CGSize(width: 85.0, height: 95.0)
let peerInset: CGFloat = 10.0
var peerOffset = peerInset
for node in self.peerNodes {
node.frame = CGRect(origin: CGPoint(x: peerOffset, y: 0.0), size: peerSize)
peerOffset += peerSize.width
}
if let moreNode = self.moreNode {
moreNode.updateLayout(size: peerSize)
moreNode.frame = CGRect(origin: CGPoint(x: peerOffset, y: 0.0), size: peerSize)
peerOffset += peerSize.width
}
self.peersScrollNode.view.contentSize = CGSize(width: CGFloat(self.peerNodes.count) * peerSize.width + (self.moreNode != nil ? peerSize.width : 0.0) + peerInset * 2.0, height: peerSize.height)
transition.updateFrame(node: self.peersScrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOrigin + 27.0 + avatarSize + 15.0 + titleSize.height + 3.0 + countSize.height + 12.0), size: CGSize(width: size.width, height: peerSize.height)))
if showPeers {
verticalOffset += 100.0
}
let buttonInset: CGFloat = 16.0
let actionButtonHeight = self.actionButtonNode.updateLayout(width: size.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.actionButtonNode, frame: CGRect(x: buttonInset, y: verticalOffset, width: size.width, height: actionButtonHeight))
verticalOffset += actionButtonHeight + 20.0
if let descriptionSize = descriptionSize {
transition.updateFrame(node: self.descriptionNode, frame: CGRect(origin: CGPoint(x: floor((size.width - descriptionSize.width) / 2.0), y: verticalOffset), size: descriptionSize))
}
self.contentOffsetUpdated?(-size.height + nodeHeight, transition)
}
func updateSelectedPeers(animated: Bool) {
}
}
public enum ShareLoadingState {
case preparing
case progress(Float)
case done
}
public final class JoinLinkPreviewLoadingContainerNode: ASDisplayNode, ShareContentContainerNode {
private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
private var theme: PresentationTheme
private let activityIndicator: ActivityIndicator
public init(theme: PresentationTheme) {
self.theme = theme
self.activityIndicator = ActivityIndicator(type: .custom(theme.actionSheet.controlAccentColor, 22.0, 2.0, false))
super.init()
self.addSubnode(self.activityIndicator)
}
public func activate() {
}
public func deactivate() {
}
public func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) {
}
public func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) {
self.contentOffsetUpdated = f
}
public func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.activityIndicator.type = .custom(theme.actionSheet.controlAccentColor, 22.0, 2.0, false)
}
public func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
let nodeHeight: CGFloat = 125.0
let indicatorSize = self.activityIndicator.calculateSizeThatFits(size)
let indicatorFrame = CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: size.height - nodeHeight + floor((nodeHeight - indicatorSize.height) / 2.0)), size: indicatorSize)
transition.updateFrame(node: self.activityIndicator, frame: indicatorFrame)
self.contentOffsetUpdated?(-size.height + nodeHeight, transition)
}
public func updateSelectedPeers(animated: Bool) {
}
}