Send invite link UI

This commit is contained in:
Ali 2023-02-28 22:23:48 +04:00
parent 774cca56cb
commit 40fb4a2ded
7 changed files with 1196 additions and 5 deletions

View File

@ -10,6 +10,7 @@ public final class SolidRoundedButtonComponent: Component {
public let title: String?
public let label: String?
public let badge: String?
public let icon: UIImage?
public let theme: SolidRoundedButtonTheme
public let font: SolidRoundedButtonFont
@ -28,6 +29,7 @@ public final class SolidRoundedButtonComponent: Component {
public init(
title: String? = nil,
label: String? = nil,
badge: String? = nil,
icon: UIImage? = nil,
theme: SolidRoundedButtonTheme,
font: SolidRoundedButtonFont = .bold,
@ -45,6 +47,7 @@ public final class SolidRoundedButtonComponent: Component {
) {
self.title = title
self.label = label
self.badge = badge
self.icon = icon
self.theme = theme
self.font = font
@ -68,6 +71,9 @@ public final class SolidRoundedButtonComponent: Component {
if lhs.label != rhs.label {
return false
}
if lhs.badge != rhs.badge {
return false
}
if lhs.icon !== rhs.icon {
return false
}
@ -121,6 +127,7 @@ public final class SolidRoundedButtonComponent: Component {
let button = SolidRoundedButtonView(
title: component.title,
label: component.label,
badge: component.badge,
icon: component.icon,
theme: component.theme,
font: component.font,
@ -141,6 +148,7 @@ public final class SolidRoundedButtonComponent: Component {
if let button = self.button {
button.title = component.title
button.label = component.label
button.badge = component.badge
button.iconPosition = component.iconPosition
button.iconSpacing = component.iconSpacing
button.icon = component.iconName.flatMap { UIImage(bundleImageName: $0) }

View File

@ -69,6 +69,93 @@ public enum SolidRoundedButtonProgressType {
case embedded
}
private final class BadgeNode: ASDisplayNode {
private var fillColor: UIColor
private var strokeColor: UIColor
private var textColor: UIColor
private let textNode: ImmediateTextNode
private let backgroundNode: ASImageNode
private let font: UIFont = Font.with(size: 15.0, design: .round, weight: .bold)
var text: String = "" {
didSet {
self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor)
self.invalidateCalculatedLayout()
}
}
init(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) {
self.fillColor = fillColor
self.strokeColor = strokeColor
self.textColor = textColor
self.textNode = ImmediateTextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false
self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: nil, strokeWidth: 1.0)
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.textNode)
}
func updateTheme(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) {
self.fillColor = fillColor
self.strokeColor = strokeColor
self.textColor = textColor
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0)
self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor)
}
func animateBump(incremented: Bool) {
if incremented {
let firstTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut)
firstTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 1.2)
firstTransition.updateTransformScale(layer: self.textNode.layer, scale: 1.2, completion: { finished in
if finished {
let secondTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut)
secondTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 1.0)
secondTransition.updateTransformScale(layer: self.textNode.layer, scale: 1.0)
}
})
} else {
let firstTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut)
firstTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 0.8)
firstTransition.updateTransformScale(layer: self.textNode.layer, scale: 0.8, completion: { finished in
if finished {
let secondTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut)
secondTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 1.0)
secondTransition.updateTransformScale(layer: self.textNode.layer, scale: 1.0)
}
})
}
}
func animateOut() {
let timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue
self.backgroundNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, delay: 0.0, timingFunction: timingFunction, removeOnCompletion: true, completion: nil)
self.textNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, delay: 0.0, timingFunction: timingFunction, removeOnCompletion: true, completion: nil)
}
func update(_ constrainedSize: CGSize) -> CGSize {
let badgeSize = self.textNode.updateLayout(constrainedSize)
let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 8.0), height: 18.0)
let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize)
self.backgroundNode.frame = backgroundFrame
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: floorToScreenPixels((backgroundFrame.size.height - badgeSize.height) / 2.0) - UIScreenPixel), size: badgeSize)
return backgroundSize
}
}
public final class SolidRoundedButtonNode: ASDisplayNode {
private var theme: SolidRoundedButtonTheme
private var font: SolidRoundedButtonFont
@ -89,6 +176,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
private let iconNode: ASImageNode
private var animationNode: SimpleAnimationNode?
private var progressNode: ASImageNode?
private var badgeNode: BadgeNode?
private let buttonHeight: CGFloat
private let buttonCornerRadius: CGFloat
@ -123,6 +211,15 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
}
}
public var badge: String? {
didSet {
self.updateAccessibilityLabels()
if let width = self.validLayout {
_ = self.updateLayout(width: width, transition: .immediate)
}
}
}
public var icon: UIImage? {
didSet {
self.iconNode.image = generateTintedImage(image: self.icon, color: self.theme.foregroundColor)
@ -630,6 +727,23 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
}
transition.updateFrame(node: self.titleNode, frame: titleFrame)
if let badge = self.badge {
let badgeNode: BadgeNode
if let current = self.badgeNode {
badgeNode = current
} else {
badgeNode = BadgeNode(fillColor: self.theme.foregroundColor, strokeColor: .clear, textColor: self.theme.backgroundColor)
self.badgeNode = badgeNode
self.addSubnode(badgeNode)
}
badgeNode.text = badge
let badgeSize = badgeNode.update(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: badgeNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + floor((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize))
} else if let badgeNode = self.badgeNode {
self.badgeNode = nil
badgeNode.removeFromSupernode()
}
if self.subtitle != self.subtitleNode.attributedText?.string {
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: self.theme.foregroundColor)
}
@ -766,6 +880,7 @@ public final class SolidRoundedButtonView: UIView {
private let iconNode: UIImageView
private var animationNode: SimpleAnimationNode?
private var progressNode: UIImageView?
private var badgeNode: BadgeNode?
private let buttonHeight: CGFloat
private let buttonCornerRadius: CGFloat
@ -800,6 +915,15 @@ public final class SolidRoundedButtonView: UIView {
}
}
public var badge: String? {
didSet {
self.updateAccessibilityLabels()
if let width = self.validLayout {
_ = self.updateLayout(width: width, transition: .immediate)
}
}
}
private func updateAccessibilityLabels() {
self.accessibilityLabel = (self.title ?? "") + " " + (self.subtitle ?? "")
self.accessibilityValue = self.label
@ -900,7 +1024,7 @@ public final class SolidRoundedButtonView: UIView {
public var progressType: SolidRoundedButtonProgressType = .fullSize
public init(title: String? = nil, label: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
public init(title: String? = nil, label: String? = nil, badge: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
self.theme = theme
self.font = font
self.fontSize = fontSize
@ -908,6 +1032,7 @@ public final class SolidRoundedButtonView: UIView {
self.buttonCornerRadius = cornerRadius
self.title = title
self.label = label
self.badge = badge
self.gloss = gloss
self.buttonBackgroundNode = UIImageView()
@ -1325,6 +1450,23 @@ public final class SolidRoundedButtonView: UIView {
}
transition.updateFrame(view: self.titleNode, frame: titleFrame)
if let badge = self.badge {
let badgeNode: BadgeNode
if let current = self.badgeNode {
badgeNode = current
} else {
badgeNode = BadgeNode(fillColor: self.theme.foregroundColor, strokeColor: .clear, textColor: self.theme.backgroundColor)
self.badgeNode = badgeNode
self.addSubnode(badgeNode)
}
badgeNode.text = badge
let badgeSize = badgeNode.update(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: badgeNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + floor((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize))
} else if let badgeNode = self.badgeNode {
self.badgeNode = nil
badgeNode.removeFromSupernode()
}
if self.subtitle != self.subtitleNode.attributedText?.string {
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: self.theme.foregroundColor)
}

View File

@ -357,6 +357,7 @@ swift_library(
"//submodules/MediaPasteboardUI:MediaPasteboardUI",
"//submodules/DrawingUI:DrawingUI",
"//submodules/FeaturedStickersScreen:FeaturedStickersScreen",
"//submodules/TelegramUI/Components/SendInviteLinkScreen",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -0,0 +1,37 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "SendInviteLinkScreen",
module_name = "SendInviteLinkScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/TelegramStringFormatting",
"//submodules/PresentationDataUtils",
"//submodules/Components/SolidRoundedButtonComponent",
"//submodules/AvatarNode",
"//submodules/CheckNode",
"//submodules/Markdown",
"//submodules/PeerPresenceStatusManager",
"//submodules/UndoUI",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,328 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import AccountContext
import TelegramCore
import MultilineTextComponent
import Postbox
import AvatarNode
import TelegramPresentationData
import CheckNode
import PeerPresenceStatusManager
import TelegramStringFormatting
private let avatarFont = avatarPlaceholderFont(size: 15.0)
private func cancelContextGestures(view: UIView) {
if let gestureRecognizers = view.gestureRecognizers {
for gesture in gestureRecognizers {
if let gesture = gesture as? ContextGesture {
gesture.cancel()
}
}
}
for subview in view.subviews {
cancelContextGestures(view: subview)
}
}
final class PeerListItemComponent: Component {
enum SelectionState: Equatable {
case none
case editing(isSelected: Bool)
}
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let sideInset: CGFloat
let title: String
let peer: EnginePeer?
let presence: EnginePeer.Presence?
let selectionState: SelectionState
let hasNext: Bool
let action: (EnginePeer) -> Void
init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
sideInset: CGFloat,
title: String,
peer: EnginePeer?,
presence: EnginePeer.Presence?,
selectionState: SelectionState,
hasNext: Bool,
action: @escaping (EnginePeer) -> Void
) {
self.context = context
self.theme = theme
self.strings = strings
self.sideInset = sideInset
self.title = title
self.peer = peer
self.presence = presence
self.selectionState = selectionState
self.hasNext = hasNext
self.action = action
}
static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.sideInset != rhs.sideInset {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.presence != rhs.presence {
return false
}
if lhs.selectionState != rhs.selectionState {
return false
}
if lhs.hasNext != rhs.hasNext {
return false
}
return true
}
final class View: UIView {
private let containerButton: HighlightTrackingButton
private let title = ComponentView<Empty>()
private let label = ComponentView<Empty>()
private let separatorLayer: SimpleLayer
private let avatarNode: AvatarNode
private var checkLayer: CheckLayer?
private var component: PeerListItemComponent?
private weak var state: EmptyComponentState?
private var statusManager: PeerPresenceStatusManager?
override init(frame: CGRect) {
self.separatorLayer = SimpleLayer()
self.containerButton = HighlightTrackingButton()
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = true
super.init(frame: frame)
self.layer.addSublayer(self.separatorLayer)
self.addSubview(self.containerButton)
self.containerButton.layer.addSublayer(self.avatarNode.layer)
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
guard let component = self.component, let peer = component.peer else {
return
}
component.action(peer)
}
func update(component: PeerListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
var hasSelectionUpdated = false
if let previousComponent = self.component {
switch previousComponent.selectionState {
case .none:
if case .none = component.selectionState {
} else {
hasSelectionUpdated = true
}
case .editing:
if case .editing = component.selectionState {
} else {
hasSelectionUpdated = true
}
}
}
self.component = component
self.state = state
if let presence = component.presence {
let statusManager: PeerPresenceStatusManager
if let current = self.statusManager {
statusManager = current
} else {
statusManager = PeerPresenceStatusManager(update: { [weak state] in
state?.updated(transition: .immediate)
})
self.statusManager = statusManager
}
statusManager.reset(presence: presence)
} else {
self.statusManager = nil
}
let contextInset: CGFloat = 0.0
let height: CGFloat = 60.0
let verticalInset: CGFloat = 1.0
let leftInset: CGFloat = 62.0 + component.sideInset
var rightInset: CGFloat = contextInset * 2.0 + 8.0 + component.sideInset
let avatarLeftInset: CGFloat = component.sideInset + 10.0
if case let .editing(isSelected) = component.selectionState {
rightInset += 48.0
let checkSize: CGFloat = 22.0
let checkLayer: CheckLayer
if let current = self.checkLayer {
checkLayer = current
if themeUpdated {
checkLayer.theme = CheckNodeTheme(theme: component.theme, style: .plain)
}
checkLayer.setSelected(isSelected, animated: !transition.animation.isImmediate)
} else {
checkLayer = CheckLayer(theme: CheckNodeTheme(theme: component.theme, style: .plain))
self.checkLayer = checkLayer
self.containerButton.layer.addSublayer(checkLayer)
checkLayer.frame = CGRect(origin: CGPoint(x: -checkSize, y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize))
checkLayer.setSelected(isSelected, animated: false)
checkLayer.setNeedsDisplay()
}
transition.setFrame(layer: checkLayer, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset + floor((48.0 - checkSize) * 0.5), y: floor((height - verticalInset * 2.0 - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize)))
} else {
if let checkLayer = self.checkLayer {
self.checkLayer = nil
transition.setPosition(layer: checkLayer, position: CGPoint(x: -checkLayer.bounds.width * 0.5, y: checkLayer.position.y), completion: { [weak checkLayer] _ in
checkLayer?.removeFromSuperlayer()
})
}
}
let avatarSize: CGFloat = 40.0
let avatarFrame = CGRect(origin: CGPoint(x: avatarLeftInset, y: floor((height - verticalInset * 2.0 - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
if self.avatarNode.bounds.isEmpty {
self.avatarNode.frame = avatarFrame
} else {
transition.setFrame(layer: self.avatarNode.layer, frame: avatarFrame)
}
if let peer = component.peer {
let clipStyle: AvatarNodeClipStyle
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
clipStyle = .roundedRect
} else {
clipStyle = .round
}
if peer.id == component.context.account.peerId {
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, overrideImage: .savedMessagesIcon, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
} else {
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
}
}
let labelData: (String, Bool)
if let presence = component.presence {
labelData = stringAndActivityForUserPresence(strings: component.strings, dateTimeFormat: PresentationDateTimeFormat(), presence: presence, relativeTo: Int32(Date().timeIntervalSince1970))
} else {
labelData = (component.strings.LastSeen_Offline, false)
}
let labelSize = self.label.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: labelData.0, font: Font.regular(15.0), textColor: labelData.1 ? component.theme.list.itemAccentColor : component.theme.list.itemSecondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let previousTitleFrame = self.title.view?.frame
var previousTitleContents: UIView?
if hasSelectionUpdated && !"".isEmpty {
previousTitleContents = self.title.view?.snapshotView(afterScreenUpdates: false)
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
let titleSpacing: CGFloat = 1.0
let centralContentHeight: CGFloat = titleSize.height + labelSize.height + titleSpacing
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - verticalInset * 2.0 - centralContentHeight) / 2.0)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.containerButton.addSubview(titleView)
}
titleView.frame = titleFrame
if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x {
transition.animatePosition(view: titleView, from: CGPoint(x: previousTitleFrame.origin.x - titleFrame.origin.x, y: 0.0), to: CGPoint(), additive: true)
}
if let previousTitleFrame, let previousTitleContents, previousTitleFrame.size != titleSize {
previousTitleContents.frame = CGRect(origin: previousTitleFrame.origin, size: previousTitleFrame.size)
self.addSubview(previousTitleContents)
transition.setFrame(view: previousTitleContents, frame: CGRect(origin: titleFrame.origin, size: previousTitleFrame.size))
transition.setAlpha(view: previousTitleContents, alpha: 0.0, completion: { [weak previousTitleContents] _ in
previousTitleContents?.removeFromSuperview()
})
transition.animateAlpha(view: titleView, from: 0.0, to: 1.0)
}
}
if let labelView = self.label.view {
if labelView.superview == nil {
labelView.isUserInteractionEnabled = false
self.containerButton.addSubview(labelView)
}
transition.setFrame(view: labelView, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSpacing), size: labelSize))
}
if themeUpdated {
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
}
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel)))
self.separatorLayer.isHidden = !component.hasNext
let containerFrame = CGRect(origin: CGPoint(x: contextInset, y: verticalInset), size: CGSize(width: availableSize.width - contextInset * 2.0, height: height - verticalInset * 2.0))
transition.setFrame(view: self.containerButton, frame: containerFrame)
return CGSize(width: availableSize.width, height: height)
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -0,0 +1,636 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import ViewControllerComponent
import ComponentDisplayAdapters
import TelegramPresentationData
import AccountContext
import TelegramCore
import MultilineTextComponent
import Postbox
import SolidRoundedButtonComponent
import PresentationDataUtils
import Markdown
import UndoUI
private final class SendInviteLinkScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let link: String
let peers: [EnginePeer]
let peerPresences: [EnginePeer.Id: EnginePeer.Presence]
init(
context: AccountContext,
link: String,
peers: [EnginePeer],
peerPresences: [EnginePeer.Id: EnginePeer.Presence]
) {
self.context = context
self.link = link
self.peers = peers
self.peerPresences = peerPresences
}
static func ==(lhs: SendInviteLinkScreenComponent, rhs: SendInviteLinkScreenComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.link != rhs.link {
return false
}
if lhs.peers != rhs.peers {
return false
}
if lhs.peerPresences != rhs.peerPresences {
return false
}
return true
}
private struct ItemLayout: Equatable {
var containerSize: CGSize
var containerInset: CGFloat
var bottomInset: CGFloat
var topInset: CGFloat
init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat) {
self.containerSize = containerSize
self.containerInset = containerInset
self.bottomInset = bottomInset
self.topInset = topInset
}
}
private final class ScrollView: UIScrollView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return super.hitTest(point, with: event)
}
}
final class View: UIView, UIScrollViewDelegate {
private let dimView: UIView
private let backgroundLayer: SimpleLayer
private let navigationBarContainer: SparseContainerView
private let scrollView: ScrollView
private let scrollContentClippingView: SparseContainerView
private let scrollContentView: UIView
private let iconBackgroundView: UIView
private let iconView: UIImageView
private let title = ComponentView<Empty>()
private let leftButton = ComponentView<Empty>()
private let descriptionText = ComponentView<Empty>()
private let actionButton = ComponentView<Empty>()
private let itemContainerView: UIView
private var items: [AnyHashable: ComponentView<Empty>] = [:]
private var selectedItems = Set<EnginePeer.Id>()
private let bottomOverscrollLimit: CGFloat
private var ignoreScrolling: Bool = false
private var component: SendInviteLinkScreenComponent?
private weak var state: EmptyComponentState?
private var environment: ViewControllerComponentContainer.Environment?
private var itemLayout: ItemLayout?
private var topOffsetDistance: CGFloat?
override init(frame: CGRect) {
self.bottomOverscrollLimit = 200.0
self.dimView = UIView()
self.backgroundLayer = SimpleLayer()
self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.backgroundLayer.cornerRadius = 10.0
self.navigationBarContainer = SparseContainerView()
self.scrollView = ScrollView()
self.scrollContentClippingView = SparseContainerView()
self.scrollContentClippingView.clipsToBounds = true
self.scrollContentView = UIView()
self.iconBackgroundView = UIView()
self.iconView = UIImageView(image: UIImage(bundleImageName: "Chat/Links/LargeLink")?.withRenderingMode(.alwaysTemplate))
self.itemContainerView = UIView()
self.itemContainerView.clipsToBounds = true
self.itemContainerView.layer.cornerRadius = 10.0
super.init(frame: frame)
self.addSubview(self.dimView)
self.layer.addSublayer(self.backgroundLayer)
self.addSubview(self.navigationBarContainer)
self.scrollView.delaysContentTouches = true
self.scrollView.canCancelContentTouches = true
self.scrollView.clipsToBounds = false
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
}
if #available(iOS 13.0, *) {
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
}
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceHorizontal = false
self.scrollView.alwaysBounceVertical = true
self.scrollView.scrollsToTop = false
self.scrollView.delegate = self
self.scrollView.clipsToBounds = true
self.addSubview(self.scrollContentClippingView)
self.scrollContentClippingView.addSubview(self.scrollView)
self.scrollContentView.addSubview(self.iconBackgroundView)
self.scrollContentView.addSubview(self.iconView)
self.scrollView.addSubview(self.scrollContentView)
self.scrollContentView.addSubview(self.itemContainerView)
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate)
}
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
guard let itemLayout = self.itemLayout, let topOffsetDistance = self.topOffsetDistance else {
return
}
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
topOffset = max(0.0, topOffset)
if topOffset < topOffsetDistance {
targetContentOffset.pointee.y = scrollView.contentOffset.y
scrollView.setContentOffset(CGPoint(x: 0.0, y: itemLayout.topInset), animated: true)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.bounds.contains(point) {
return nil
}
if !self.backgroundLayer.frame.contains(point) {
return self.dimView
}
if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) {
return result
}
let result = super.hitTest(point, with: event)
return result
}
@objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
guard let environment = self.environment, let controller = environment.controller() else {
return
}
controller.dismiss()
}
}
private func updateScrolling(transition: Transition) {
guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else {
return
}
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
topOffset = max(0.0, topOffset)
transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset))
let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25))
self.topOffsetDistance = topOffsetDistance
var topOffsetFraction = topOffset / topOffsetDistance
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
let transitionFactor: CGFloat = 1.0 - topOffsetFraction
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition)
}
func animateIn() {
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
let animateOffset: CGFloat = self.backgroundLayer.frame.minY
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
if let actionButtonView = self.actionButton.view {
actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
func animateOut(completion: @escaping () -> Void) {
let animateOffset: CGFloat = self.backgroundLayer.frame.minY
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
completion()
})
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
if let actionButtonView = self.actionButton.view {
actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
}
}
func update(component: SendInviteLinkScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
let environment = environment[ViewControllerComponentContainer.Environment.self].value
let themeUpdated = self.environment?.theme !== environment.theme
let resetScrolling = self.scrollView.bounds.width != availableSize.width
let sideInset: CGFloat = 16.0
if self.component == nil {
for peer in component.peers {
self.selectedItems.insert(peer.id)
}
}
self.component = component
self.state = state
self.environment = environment
if themeUpdated {
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor
self.iconBackgroundView.backgroundColor = environment.theme.list.itemCheckColors.fillColor
self.iconView.tintColor = environment.theme.list.itemCheckColors.foregroundColor
self.itemContainerView.backgroundColor = environment.theme.list.itemBlocksBackgroundColor
var locations: [NSNumber] = []
var colors: [CGColor] = []
let numStops = 6
for i in 0 ..< numStops {
let step = CGFloat(i) / CGFloat(numStops - 1)
locations.append(step as NSNumber)
colors.append(environment.theme.list.blocksBackgroundColor.withAlphaComponent(1.0 - step * step).cgColor)
}
}
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
var contentHeight: CGFloat = 0.0
//TODO:localize
let leftButtonSize = self.leftButton.update(
transition: transition,
component: AnyComponent(Button(
content: AnyComponent(Text(text: "Skip", font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)),
action: { [weak self] in
guard let self, let controller = self.environment?.controller() else {
return
}
controller.dismiss()
}
).minSize(CGSize(width: 44.0, height: 56.0))),
environment: {},
containerSize: CGSize(width: 120.0, height: 100.0)
)
let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: leftButtonSize)
if let leftButtonView = self.leftButton.view {
if leftButtonView.superview == nil {
self.navigationBarContainer.addSubview(leftButtonView)
}
transition.setFrame(view: leftButtonView, frame: leftButtonFrame)
leftButtonView.isHidden = self.selectedItems.isEmpty ? true : false
}
//TODO:localize
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Invite via Link", font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: 18.0), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.navigationBarContainer.addSubview(titleView)
}
transition.setFrame(view: titleView, frame: titleFrame)
}
contentHeight += 44.0
contentHeight += 22.0
let iconBackgroundSize = CGSize(width: 68.0, height: 48.0)
let iconBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconBackgroundSize.width) * 0.5), y: contentHeight), size: iconBackgroundSize)
transition.setFrame(view: self.iconBackgroundView, frame: iconBackgroundFrame)
transition.setCornerRadius(layer: self.iconBackgroundView.layer, cornerRadius: min(iconBackgroundFrame.width, iconBackgroundFrame.height) * 0.5)
if let icon = self.iconView.image {
let scaleFraction: CGFloat = 0.5
let iconSize = CGSize(width: floor(icon.size.width * scaleFraction), height: floor(icon.size.height * scaleFraction))
transition.setFrame(view: self.iconView, frame: CGRect(origin: CGPoint(x: floor(iconBackgroundFrame.minX + (iconBackgroundFrame.width - iconSize.width) * 0.5), y: floor(iconBackgroundFrame.minY + (iconBackgroundFrame.height - iconSize.height) * 0.5)), size: iconSize))
}
contentHeight += iconBackgroundSize.height
contentHeight += 26.0
let text: String
if component.peers.count == 1 {
text = "**\(component.peers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast))** restricts adding them to groups.\nYou can send them an invite link as message instead."
} else {
text = "**\(component.peers.count) users** restrict adding them to groups.\nYou can send them an invite link as message instead."
}
let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
let bold = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor)
let descriptionTextSize = self.descriptionText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .markdown(text: text, attributes: MarkdownAttributes(
body: body,
bold: bold,
link: body,
linkAttribute: { _ in nil }
)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
let descriptionTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - descriptionTextSize.width) * 0.5), y: contentHeight), size: descriptionTextSize)
if let descriptionTextView = self.descriptionText.view {
if descriptionTextView.superview == nil {
self.scrollContentView.addSubview(descriptionTextView)
}
transition.setFrame(view: descriptionTextView, frame: descriptionTextFrame)
}
contentHeight += descriptionTextFrame.height
contentHeight += 13.0
var singleItemHeight: CGFloat = 0.0
var itemsHeight: CGFloat = 0.0
var validIds: [AnyHashable] = []
for i in 0 ..< component.peers.count {
let peer = component.peers[i]
for _ in 0 ..< 1 {
//let id: AnyHashable = AnyHashable("\(peer.id)_\(j)")
let id = AnyHashable(peer.id)
validIds.append(id)
let item: ComponentView<Empty>
var itemTransition = transition
if let current = self.items[id] {
item = current
} else {
itemTransition = .immediate
item = ComponentView()
self.items[id] = item
}
let itemSize = item.update(
transition: itemTransition,
component: AnyComponent(PeerListItemComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
sideInset: 0.0,
title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast),
peer: peer,
presence: component.peerPresences[peer.id],
selectionState: .editing(isSelected: self.selectedItems.contains(peer.id)),
hasNext: i != component.peers.count - 1,
action: { [weak self] peer in
guard let self else {
return
}
if self.selectedItems.contains(peer.id) {
self.selectedItems.remove(peer.id)
} else {
self.selectedItems.insert(peer.id)
}
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .easeInOut)))
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize)
if let itemView = item.view {
if itemView.superview == nil {
self.itemContainerView.addSubview(itemView)
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
}
itemsHeight += itemSize.height
singleItemHeight = itemSize.height
}
}
var removeIds: [AnyHashable] = []
for (id, item) in self.items {
if !validIds.contains(id) {
removeIds.append(id)
item.view?.removeFromSuperview()
}
}
for id in removeIds {
self.items.removeValue(forKey: id)
}
transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight)))
var initialContentHeight = contentHeight
initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5))
contentHeight += itemsHeight
contentHeight += 24.0
initialContentHeight += 24.0
//TODO:localize
let actionButtonSize = self.actionButton.update(
transition: transition,
component: AnyComponent(SolidRoundedButtonComponent(
title: self.selectedItems.isEmpty ? "Skip" : "Send Invite Link",
badge: self.selectedItems.isEmpty ? nil : "\(self.selectedItems.count)",
theme: SolidRoundedButtonComponent.Theme(theme: environment.theme),
font: .bold,
fontSize: 17.0,
height: 50.0,
cornerRadius: 11.0,
gloss: false,
animationName: nil,
iconPosition: .right,
iconSpacing: 4.0,
action: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
return
}
if self.selectedItems.isEmpty {
controller.dismiss()
} else {
let _ = enqueueMessagesToMultiplePeers(account: component.context.account, peerIds: Array(self.selectedItems), threadIds: [:], messages: [.message(text: component.link, attributes: [], inlineStickers: [:], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start()
//TODO:localize
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: component.context, peers: Array(component.peers.prefix(3)), title: nil, text: "Invite link sent.", customUndoText: nil), elevatedLayout: false, action: { _ in return false }), in: .window(.root))
controller.dismiss()
}
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
)
let bottomPanelHeight = 14.0 + environment.safeInsets.bottom + actionButtonSize.height
let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize)
if let actionButtonView = self.actionButton.view {
if actionButtonView.superview == nil {
self.addSubview(actionButtonView)
}
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
}
contentHeight += bottomPanelHeight
initialContentHeight += bottomPanelHeight
let containerInset: CGFloat = environment.statusBarHeight + 10.0
let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight)
let scrollContentHeight = max(topInset + contentHeight, availableSize.height - containerInset)
self.scrollContentClippingView.layer.cornerRadius = 10.0
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset)
transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight)))
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize))
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset + 56.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: actionButtonFrame.minY - 24.0 - (containerInset + 56.0)))
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
self.ignoreScrolling = true
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height - containerInset)))
let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight)
if contentSize != self.scrollView.contentSize {
self.scrollView.contentSize = contentSize
}
if resetScrolling {
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize)
}
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public class SendInviteLinkScreen: ViewControllerComponentContainer {
private let context: AccountContext
private let link: String
private let peers: [EnginePeer]
private var isDismissed: Bool = false
private var presenceDisposable: Disposable?
public init(context: AccountContext, link: String, peers: [EnginePeer]) {
self.context = context
self.link = link
self.peers = peers
super.init(context: context, component: SendInviteLinkScreenComponent(context: context, link: link, peers: peers, peerPresences: [:]), navigationBarAppearance: .none)
self.statusBar.statusBarStyle = .Ignore
self.navigationPresentation = .flatModal
self.blocksBackgroundWhenInOverlay = true
self.presenceDisposable = (context.engine.data.subscribe(EngineDataMap(
peers.map(\.id).map(TelegramEngine.EngineData.Item.Peer.Presence.init(id:))
))
|> deliverOnMainQueue).start(next: { [weak self] presences in
guard let self else {
return
}
var parsedPresences: [EnginePeer.Id: EnginePeer.Presence] = [:]
for (id, presence) in presences {
if let presence {
parsedPresences[id] = presence
}
}
self.updateComponent(component: AnyComponent(SendInviteLinkScreenComponent(context: context, link: link, peers: peers, peerPresences: parsedPresences)), transition: .immediate)
})
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presenceDisposable?.dispose()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.view.disablesInteractiveModalDismiss = true
if let componentView = self.node.hostView.componentView as? SendInviteLinkScreenComponent.View {
componentView.animateIn()
}
}
override public func dismiss(completion: (() -> Void)? = nil) {
if !self.isDismissed {
self.isDismissed = true
if let componentView = self.node.hostView.componentView as? SendInviteLinkScreenComponent.View {
componentView.animateOut(completion: { [weak self] in
completion?()
self?.dismiss(animated: false)
})
} else {
self.dismiss(animated: false)
}
}
}
}

View File

@ -85,6 +85,7 @@ import ChatListHeaderComponent
import ChatControllerInteraction
import StorageUsageScreen
import AvatarEditorScreen
import SendInviteLinkScreen
enum PeerInfoAvatarEditingMode {
case generic
@ -2746,7 +2747,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, dismissTextInput: {
}, scrollToMessageId: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in
guard let strongSelf = self else {
return
@ -10605,8 +10606,12 @@ func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (in
}
}
if let contactsController = contactsController as? ContactMultiselectionController {
selectAddMemberDisposable.set((contactsController.result
|> deliverOnMainQueue).start(next: { [weak contactsController] result in
selectAddMemberDisposable.set((
combineLatest(queue: .mainQueue(),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: groupPeer.id)),
contactsController.result
)
|> deliverOnMainQueue).start(next: { [weak contactsController] exportedInvitation, result in
var peers: [ContactListPeerId] = []
if case let .result(peerIdsValue, _) = result {
peers = peerIdsValue
@ -10615,6 +10620,40 @@ func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (in
contactsController?.displayProgress = true
addMemberDisposable.set((addMembers(peers)
|> deliverOnMainQueue).start(error: { error in
if let exportedInvitation, let link = exportedInvitation.link {
switch error {
case .restricted, .notMutualContact, .kicked:
let _ = (context.engine.data.get(
EngineDataList(peers.compactMap { item -> EnginePeer.Id? in
switch item {
case let .peer(peerId):
return peerId
default:
return nil
}
}.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
|> deliverOnMainQueue).start(next: { peerItems in
let peers = peerItems.compactMap { $0 }
if !peers.isEmpty, let contactsController, let navigationController = contactsController.navigationController as? NavigationController {
var viewControllers = navigationController.viewControllers
if let index = viewControllers.firstIndex(where: { $0 === contactsController }) {
let inviteScreen = SendInviteLinkScreen(context: context, link: link, peers: peers)
viewControllers.remove(at: index)
viewControllers.append(inviteScreen)
navigationController.setViewControllers(viewControllers, animated: true)
}
} else {
contactsController?.dismiss()
}
})
return
default:
break
}
}
if peers.count == 1, case .restricted = error {
switch peers[0] {
case let .peer(peerId):
@ -10634,7 +10673,7 @@ func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (in
}
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} else if case .tooMuchJoined = error {
} else if case .tooMuchJoined = error {
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} else if peers.count == 1, case .kicked = error {
parentController?.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Channel_AddUserKickedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))