Swiftgram/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift
2024-06-15 20:34:07 +04:00

2411 lines
131 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import OverlayStatusController
import AccountContext
import ShareController
import AlertUI
import PresentationDataUtils
import TelegramNotices
import ItemListPeerItem
import ItemListPeerActionItem
import AccountContext
import InviteLinksUI
import ContextUI
import UndoUI
import QrCodeUI
import PremiumUI
import TextFormat
import PremiumUI
import OldChannelsController
private final class ChannelVisibilityControllerArguments {
let context: AccountContext
let updateCurrentType: (CurrentChannelType) -> Void
let updatePublicLinkText: (String?, String) -> Void
let scrollToPublicLinkText: () -> Void
let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void
let revokePeerId: (EnginePeer.Id) -> Void
let copyLink: (ExportedInvitation) -> Void
let shareLink: (ExportedInvitation) -> Void
let linkContextAction: (ASDisplayNode, ContextGesture?) -> Void
let manageInviteLinks: () -> Void
let openLink: (ExportedInvitation) -> Void
let toggleForwarding: (Bool) -> Void
let updateJoinToSend: (CurrentChannelJoinToSend) -> Void
let toggleApproveMembers: (Bool) -> Void
let activateLink: (String) -> Void
let deactivateLink: (String) -> Void
let openAuction: (String) -> Void
init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, revokePeerId: @escaping (EnginePeer.Id) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) {
self.context = context
self.updateCurrentType = updateCurrentType
self.updatePublicLinkText = updatePublicLinkText
self.scrollToPublicLinkText = scrollToPublicLinkText
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.revokePeerId = revokePeerId
self.copyLink = copyLink
self.shareLink = shareLink
self.linkContextAction = linkContextAction
self.manageInviteLinks = manageInviteLinks
self.openLink = openLink
self.toggleForwarding = toggleForwarding
self.updateJoinToSend = updateJoinToSend
self.toggleApproveMembers = toggleApproveMembers
self.activateLink = activateLink
self.deactivateLink = deactivateLink
self.openAuction = openAuction
}
}
private enum ChannelVisibilitySection: Int32 {
case type
case limitInfo
case link
case linkActions
case additional
case joinToSend
case approveMembers
case forwarding
}
private enum ChannelVisibilityEntryTag: ItemListItemTag {
case publicLink
case privateLink
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? ChannelVisibilityEntryTag, self == other {
return true
} else {
return false
}
}
}
private enum ChannelVisibilityEntryId: Hashable {
case index(Int32)
case username(String)
}
private enum ChannelVisibilityEntry: ItemListNodeEntry {
case typeHeader(PresentationTheme, String)
case typePublic(PresentationTheme, String, Bool)
case typePrivate(PresentationTheme, String, Bool)
case typeInfo(PresentationTheme, String)
case publicLinkHeader(PresentationTheme, String)
case publicLinkAvailability(PresentationTheme, String, Bool)
case linksLimitInfo(PresentationTheme, String, Int32, Int32, Int32, Bool)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String)
case privateLinkHeader(PresentationTheme, String)
case privateLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool)
case privateLinkInfo(PresentationTheme, String)
case privateLinkManage(PresentationTheme, String)
case privateLinkManageInfo(PresentationTheme, String)
case publicLinkInfo(PresentationTheme, String)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
case existingLinksInfo(PresentationTheme, String)
case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, EnginePeer, ItemListPeerItemEditing, Bool)
case additionalLinkHeader(PresentationTheme, String)
case additionalLink(PresentationTheme, TelegramPeerUsername, Int32)
case additionalLinkInfo(PresentationTheme, String)
case joinToSendHeader(PresentationTheme, String)
case joinToSendEveryone(PresentationTheme, String, Bool)
case joinToSendMembers(PresentationTheme, String, Bool)
case approveMembers(PresentationTheme, String, Bool)
case approveMembersInfo(PresentationTheme, String)
case forwardingHeader(PresentationTheme, String)
case forwardingDisabled(PresentationTheme, String, Bool)
case forwardingInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
return ChannelVisibilitySection.type.rawValue
case .linksLimitInfo:
return ChannelVisibilitySection.limitInfo.rawValue
case .publicLinkHeader, .publicLinkAvailability, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus:
return ChannelVisibilitySection.link.rawValue
case .additionalLinkHeader, .additionalLink, .additionalLinkInfo:
return ChannelVisibilitySection.additional.rawValue
case .privateLinkManage, .privateLinkManageInfo:
return ChannelVisibilitySection.linkActions.rawValue
case .existingLinksInfo, .existingLinkPeerItem:
return ChannelVisibilitySection.link.rawValue
case .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers:
return ChannelVisibilitySection.joinToSend.rawValue
case .approveMembers, .approveMembersInfo:
return ChannelVisibilitySection.approveMembers.rawValue
case .forwardingHeader, .forwardingDisabled, .forwardingInfo:
return ChannelVisibilitySection.forwarding.rawValue
}
}
var stableId: ChannelVisibilityEntryId {
switch self {
case .typeHeader:
return .index(0)
case .typePublic:
return .index(1)
case .typePrivate:
return .index(2)
case .typeInfo:
return .index(3)
case .publicLinkHeader:
return .index(4)
case .publicLinkAvailability:
return .index(5)
case .linksLimitInfo:
return .index(6)
case .privateLinkHeader:
return .index(7)
case .privateLink:
return .index(8)
case .editablePublicLink:
return .index(9)
case .privateLinkInfo:
return .index(10)
case .publicLinkStatus:
return .index(11)
case .publicLinkInfo:
return .index(12)
case .existingLinksInfo:
return .index(13)
case let .existingLinkPeerItem(index, _, _, _, _, _, _, _):
return .index(14 + index)
case .additionalLinkHeader:
return .index(1000)
case let .additionalLink(_, username, _):
return .username(username.username)
case .additionalLinkInfo:
return .index(2000)
case .privateLinkManage:
return .index(2001)
case .privateLinkManageInfo:
return .index(2002)
case .joinToSendHeader:
return .index(2003)
case .joinToSendEveryone:
return .index(2004)
case .joinToSendMembers:
return .index(2005)
case .approveMembers:
return .index(2006)
case .approveMembersInfo:
return .index(2007)
case .forwardingHeader:
return .index(2008)
case .forwardingDisabled:
return .index(2009)
case .forwardingInfo:
return .index(2010)
}
}
static func ==(lhs: ChannelVisibilityEntry, rhs: ChannelVisibilityEntry) -> Bool {
switch lhs {
case let .typeHeader(lhsTheme, lhsTitle):
if case let .typeHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
case let .typePublic(lhsTheme, lhsTitle, lhsSelected):
if case let .typePublic(rhsTheme, rhsTitle, rhsSelected) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSelected == rhsSelected {
return true
} else {
return false
}
case let .typePrivate(lhsTheme, lhsTitle, lhsSelected):
if case let .typePrivate(rhsTheme, rhsTitle, rhsSelected) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSelected == rhsSelected {
return true
} else {
return false
}
case let .typeInfo(lhsTheme, lhsText):
if case let .typeInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .publicLinkHeader(lhsTheme, lhsTitle):
if case let .publicLinkHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
case let .publicLinkAvailability(lhsTheme, lhsText, lhsValue):
if case let .publicLinkAvailability(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .linksLimitInfo(lhsTheme, lhsText, lhsCount, lhsLimit, lhsPremiumLimit, lhsIsPremiumDisabled):
if case let .linksLimitInfo(rhsTheme, rhsText, rhsCount, rhsLimit, rhsPremiumLimit, rhsIsPremiumDisabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsCount == rhsCount, lhsLimit == rhsLimit, lhsPremiumLimit == rhsPremiumLimit, lhsIsPremiumDisabled == rhsIsPremiumDisabled {
return true
} else {
return false
}
case let .additionalLinkHeader(lhsTheme, lhsText):
if case let .additionalLinkHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .additionalLink(lhsTheme, lhsAddressName, lhsIndex):
if case let .additionalLink(rhsTheme, rhsAddressName, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsIndex == rhsIndex {
return true
} else {
return false
}
case let .additionalLinkInfo(lhsTheme, lhsText):
if case let .additionalLinkInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .privateLinkHeader(lhsTheme, lhsTitle):
if case let .privateLinkHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true
} else {
return false
}
case let .privateLink(lhsTheme, lhsInvite, lhsPeers, lhsImportersCount, lhsDisplayImporters):
if case let .privateLink(rhsTheme, rhsInvite, rhsPeers, rhsImportersCount, rhsDisplayImporters) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, lhsPeers == rhsPeers, lhsImportersCount == rhsImportersCount, lhsDisplayImporters == rhsDisplayImporters {
return true
} else {
return false
}
case let .editablePublicLink(lhsTheme, lhsStrings, lhsPlaceholder, lhsCurrentText):
if case let .editablePublicLink(rhsTheme, rhsStrings, rhsPlaceholder, rhsCurrentText) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsCurrentText == rhsCurrentText {
return true
} else {
return false
}
case let .privateLinkInfo(lhsTheme, lhsText):
if case let .privateLinkInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .privateLinkManage(lhsTheme, lhsText):
if case let .privateLinkManage(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .privateLinkManageInfo(lhsTheme, lhsText):
if case let .privateLinkManageInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .publicLinkInfo(lhsTheme, lhsText):
if case let .publicLinkInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus, lhsUsername):
if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus, lhsUsername == rhsUsername {
return true
} else {
return false
}
case let .existingLinksInfo(lhsTheme, lhsText):
if case let .existingLinksInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .existingLinkPeerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsEditing, lhsEnabled):
if case let .existingLinkPeerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsEditing, rhsEnabled) = rhs {
if lhsIndex != rhsIndex {
return false
}
if lhsTheme !== rhsTheme {
return false
}
if lhsStrings !== rhsStrings {
return false
}
if lhsDateTimeFormat != rhsDateTimeFormat {
return false
}
if lhsNameOrder != rhsNameOrder {
return false
}
if lhsPeer != rhsPeer {
return false
}
if lhsEditing != rhsEditing {
return false
}
if lhsEnabled != rhsEnabled {
return false
}
return true
} else {
return false
}
case let .joinToSendHeader(lhsTheme, lhsText):
if case let .joinToSendHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .joinToSendEveryone(lhsTheme, lhsText, lhsValue):
if case let .joinToSendEveryone(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .joinToSendMembers(lhsTheme, lhsText, lhsValue):
if case let .joinToSendMembers(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .approveMembers(lhsTheme, lhsText, lhsValue):
if case let .approveMembers(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .approveMembersInfo(lhsTheme, lhsText):
if case let .approveMembersInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .forwardingHeader(lhsTheme, lhsText):
if case let .forwardingHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .forwardingDisabled(lhsTheme, lhsText, lhsValue):
if case let .forwardingDisabled(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .forwardingInfo(lhsTheme, lhsText):
if case let .forwardingInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
}
}
static func <(lhs: ChannelVisibilityEntry, rhs: ChannelVisibilityEntry) -> Bool {
switch lhs {
case .typeHeader:
switch rhs {
case .typeHeader:
return false
default:
return true
}
case .typePublic:
switch rhs {
case .typeHeader, .typePublic:
return false
default:
return true
}
case .typePrivate:
switch rhs {
case .typeHeader, .typePublic, .typePrivate:
return false
default:
return true
}
case .typeInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
return false
default:
return true
}
case .publicLinkHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader:
return false
default:
return true
}
case .publicLinkAvailability:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability:
return false
default:
return true
}
case .linksLimitInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo:
return false
default:
return true
}
case .privateLinkHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader:
return false
default:
return true
}
case .privateLink:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink:
return false
default:
return true
}
case .editablePublicLink:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink:
return false
default:
return true
}
case .privateLinkInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo:
return false
default:
return true
}
case .publicLinkStatus:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus:
return false
default:
return true
}
case .publicLinkInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo:
return false
default:
return true
}
case .existingLinksInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo:
return false
default:
return true
}
case let .existingLinkPeerItem(lhsIndex, _, _, _, _, _, _, _):
switch rhs {
case let .existingLinkPeerItem(rhsIndex, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo:
return false
default:
return true
}
case .additionalLinkHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader:
return false
default:
return true
}
case let .additionalLink(_, _, lhsIndex):
switch rhs {
case let .additionalLink(_, _, rhsIndex):
return lhsIndex < rhsIndex
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader:
return false
default:
return true
}
case .additionalLinkInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo:
return false
default:
return true
}
case .privateLinkManage:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage:
return false
default:
return true
}
case .privateLinkManageInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo:
return false
default:
return true
}
case .joinToSendHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader:
return false
default:
return true
}
case .joinToSendEveryone:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone:
return false
default:
return true
}
case .joinToSendMembers:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers:
return false
default:
return true
}
case .approveMembers:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers:
return false
default:
return true
}
case .approveMembersInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers, .approveMembersInfo:
return false
default:
return true
}
case .forwardingHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers, .approveMembersInfo, .forwardingHeader:
return false
default:
return true
}
case .forwardingDisabled:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers, .approveMembersInfo, .forwardingHeader, .forwardingDisabled:
return false
default:
return true
}
case .forwardingInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers, .approveMembersInfo, .forwardingHeader, .forwardingDisabled, .forwardingInfo:
return false
}
}
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ChannelVisibilityControllerArguments
switch self {
case let .typeHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .typePublic(_, text, selected):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateCurrentType(.publicChannel)
})
case let .typePrivate(_, text, selected):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateCurrentType(.privateChannel)
})
case let .typeInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .publicLinkHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .publicLinkAvailability(_, text, value):
return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: text, color: value ? .generic : .destructive, sectionId: self.section)
case let .linksLimitInfo(theme, text, count, limit, premiumLimit, isPremiumDisabled):
return IncreaseLimitHeaderItem(theme: theme, strings: presentationData.strings, icon: .link, count: count, limit: limit, premiumCount: premiumLimit, text: text, isPremiumDisabled: isPremiumDisabled, sectionId: self.section)
case let .privateLinkHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .privateLink(_, invite, peers, importersCount, displayImporters):
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: importersCount, peers: peers, displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
if let invite = invite {
arguments.copyLink(invite)
}
}, shareAction: {
if let invite = invite {
arguments.shareLink(invite)
}
}, contextAction: { node, gesture in
arguments.linkContextAction(node, gesture)
}, viewAction: {
if let invite = invite {
arguments.openLink(invite)
}
})
case let .editablePublicLink(theme, _, placeholder, currentText):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: currentText, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), clearType: .always, tag: ChannelVisibilityEntryTag.publicLink, sectionId: self.section, textUpdated: { updatedText in
arguments.updatePublicLinkText(currentText, updatedText)
}, updatedFocus: { focus in
if focus {
arguments.scrollToPublicLinkText()
}
}, action: {
})
case let .privateLinkInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .privateLinkManage(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.manageInviteLinks()
})
case let .privateLinkManageInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .publicLinkInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .publicLinkStatus(_, text, status, username):
var displayActivity = false
let textColor: ItemListActivityTextItem.TextColor
switch status {
case .invalidFormat:
textColor = .destructive
case let .availability(availability):
switch availability {
case .available:
textColor = .constructive
case .invalid:
textColor = .destructive
case .taken:
textColor = .destructive
case .purchaseAvailable:
textColor = .generic
}
case .checking:
textColor = .generic
displayActivity = true
}
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in
arguments.openAuction(username)
}, sectionId: self.section)
case let .existingLinksInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .existingLinkPeerItem(_, _, _, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
var label = ""
if let addressName = peer.addressName {
label = "t.me/" + addressName
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(label, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id)
}, removePeer: { peerId in
arguments.revokePeerId(peerId)
})
case let .additionalLinkHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .additionalLink(_, link, _):
return AdditionalLinkItem(presentationData: presentationData, username: link, sectionId: self.section, style: .blocks, tapAction: {
if !link.flags.contains(.isEditable) {
if link.flags.contains(.isActive) {
arguments.deactivateLink(link.username)
} else {
arguments.activateLink(link.username)
}
}
})
case let .additionalLinkInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .joinToSendHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .joinToSendEveryone(_, text, selected):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateJoinToSend(.everyone)
arguments.toggleApproveMembers(false)
})
case let .joinToSendMembers(_, text, selected):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateJoinToSend(.members)
})
case let .approveMembers(_, text, selected):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: selected, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleApproveMembers(value)
})
case let .approveMembersInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .forwardingHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .forwardingDisabled(_, text, selected):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: selected, sectionId: self.section, style: .blocks, updated: { value in
arguments.toggleForwarding(!value)
})
case let .forwardingInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
}
}
}
private enum CurrentChannelType {
case publicChannel
case privateChannel
}
private enum CurrentChannelLocation: Equatable {
case removed
case location(PeerGeoLocation)
}
private enum CurrentChannelJoinToSend {
case everyone
case members
}
private struct ChannelVisibilityControllerState: Equatable {
let selectedType: CurrentChannelType?
let editingPublicLinkText: String?
let addressNameValidationStatus: AddressNameValidationStatus?
let updatingAddressName: Bool
let revealedRevokePeerId: EnginePeer.Id?
let revokingPeerId: EnginePeer.Id?
let revokingPrivateLink: Bool
let forwardingEnabled: Bool?
let joinToSend: CurrentChannelJoinToSend?
let approveMembers: Bool?
init() {
self.selectedType = nil
self.editingPublicLinkText = nil
self.addressNameValidationStatus = nil
self.updatingAddressName = false
self.revealedRevokePeerId = nil
self.revokingPeerId = nil
self.revokingPrivateLink = false
self.forwardingEnabled = nil
self.joinToSend = nil
self.approveMembers = nil
}
init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: PeerId?, revokingPeerId: PeerId?, revokingPrivateLink: Bool, forwardingEnabled: Bool?, joinToSend: CurrentChannelJoinToSend?, approveMembers: Bool?) {
self.selectedType = selectedType
self.editingPublicLinkText = editingPublicLinkText
self.addressNameValidationStatus = addressNameValidationStatus
self.updatingAddressName = updatingAddressName
self.revealedRevokePeerId = revealedRevokePeerId
self.revokingPeerId = revokingPeerId
self.revokingPrivateLink = revokingPrivateLink
self.forwardingEnabled = forwardingEnabled
self.joinToSend = joinToSend
self.approveMembers = approveMembers
}
static func ==(lhs: ChannelVisibilityControllerState, rhs: ChannelVisibilityControllerState) -> Bool {
if lhs.selectedType != rhs.selectedType {
return false
}
if lhs.editingPublicLinkText != rhs.editingPublicLinkText {
return false
}
if lhs.addressNameValidationStatus != rhs.addressNameValidationStatus {
return false
}
if lhs.updatingAddressName != rhs.updatingAddressName {
return false
}
if lhs.revealedRevokePeerId != rhs.revealedRevokePeerId {
return false
}
if lhs.revokingPeerId != rhs.revokingPeerId {
return false
}
if lhs.revokingPrivateLink != rhs.revokingPrivateLink {
return false
}
if lhs.forwardingEnabled != rhs.forwardingEnabled {
return false
}
if lhs.joinToSend != rhs.joinToSend {
return false
}
if lhs.approveMembers != rhs.approveMembers {
return false
}
return true
}
func withUpdatedSelectedType(_ selectedType: CurrentChannelType?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
}
func withUpdatedEditingPublicLinkText(_ editingPublicLinkText: String?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
}
func withUpdatedAddressNameValidationStatus(_ addressNameValidationStatus: AddressNameValidationStatus?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
}
func withUpdatedUpdatingAddressName(_ updatingAddressName: Bool) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
}
func withUpdatedRevealedRevokePeerId(_ revealedRevokePeerId: EnginePeer.Id?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
}
func withUpdatedRevokingPeerId(_ revokingPeerId: EnginePeer.Id?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
}
func withUpdatedRevokingPrivateLink(_ revokingPrivateLink: Bool) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
}
func withUpdatedForwardingEnabled(_ forwardingEnabled: Bool) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
}
func withUpdatedJoinToSend(_ joinToSend: CurrentChannelJoinToSend?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: joinToSend, approveMembers: self.approveMembers)
}
func withUpdatedApproveMembers(_ approveMembers: Bool) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: approveMembers)
}
}
private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [EnginePeer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool, isPremiumDisabled: Bool, temporaryOrder: [String]?) -> [ChannelVisibilityEntry] {
var entries: [ChannelVisibilityEntry] = []
let isInitialSetup: Bool
if case .initialSetup = mode {
isInitialSetup = true
} else {
isInitialSetup = false
}
if let peer = view.peers[view.peerId] as? TelegramChannel {
var isGroup = false
if case .group = peer.info {
isGroup = true
}
let selectedType: CurrentChannelType
if case .privateLink = mode {
selectedType = .privateChannel
} else {
if let current = state.selectedType {
selectedType = current
} else {
if let addressName = peer.addressName, !addressName.isEmpty {
selectedType = .publicChannel
} else if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
selectedType = .publicChannel
} else if case .initialSetup = mode {
selectedType = .privateChannel
} else {
selectedType = .privateChannel
}
}
}
let joinToSend: CurrentChannelJoinToSend
if let current = state.joinToSend {
joinToSend = current
} else {
if peer.flags.contains(.joinToSend) {
joinToSend = .members
} else {
joinToSend = .everyone
}
}
let approveMembers: Bool
if let enabled = state.approveMembers {
approveMembers = enabled
} else {
if peer.flags.contains(.requestToJoin) {
approveMembers = true
} else {
approveMembers = false
}
}
let forwardingEnabled: Bool
if let enabled = state.forwardingEnabled {
forwardingEnabled = enabled
} else {
if peer.flags.contains(.copyProtectionEnabled) {
forwardingEnabled = false
} else {
forwardingEnabled = true
}
}
let currentUsername: String
if let current = state.editingPublicLinkText {
currentUsername = current
} else {
if let username = peer.editableUsername {
currentUsername = username
} else {
currentUsername = ""
}
}
if let _ = (view.cachedData as? CachedChannelData)?.peerGeoLocation {
} else {
switch mode {
case .privateLink, .revokeNames:
break
case .initialSetup, .generic:
entries.append(.typeHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_TypeHeader.uppercased() : presentationData.strings.Channel_Edit_LinkItem.uppercased()))
entries.append(.typePublic(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_TypePublic : presentationData.strings.Channel_Setup_LinkTypePublic, selectedType == .publicChannel))
entries.append(.typePrivate(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_TypePrivate : presentationData.strings.Channel_Setup_LinkTypePrivate, selectedType == .privateChannel))
switch selectedType {
case .publicChannel:
if isGroup {
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp))
} else {
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Channel_Setup_TypePublicHelp))
}
case .privateChannel:
if isGroup {
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp))
} else {
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivateHelp))
}
}
}
}
let otherUsernames = peer.usernames.filter { !$0.flags.contains(.isEditable) }
if case .revokeNames = mode {
let count = Int32(publicChannelsToRevoke?.count ?? 0)
let text: String
if count >= premiumLimits.maxPublicLinksCount {
text = presentationData.strings.Group_Username_RemoveExistingUsernamesFinalInfo
} else {
if isPremiumDisabled {
text = presentationData.strings.Group_Username_RemoveExistingUsernamesNoPremiumInfo
} else {
text = presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(premiumLimits.maxPublicLinksCount)").string
}
}
entries.append(.linksLimitInfo(presentationData.theme, text, count, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount, isPremiumDisabled))
if let publicChannelsToRevoke = publicChannelsToRevoke {
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
var rhsDate: Int32 = 0
if case let .channel(lhs) = lhs {
lhsDate = lhs.creationDate
}
if case let .channel(rhs) = rhs {
rhsDate = rhs.creationDate
}
return lhsDate > rhsDate
}) {
entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1
}
}
} else {
switch selectedType {
case .publicChannel:
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, presentationData.strings.Group_PublicLink_Placeholder, currentUsername))
if let status = state.addressNameValidationStatus {
let text: String
switch status {
case let .invalidFormat(error):
switch error {
case .startsWithDigit:
if isGroup {
text = presentationData.strings.Group_Username_InvalidStartsWithNumber
} else {
text = presentationData.strings.Channel_Username_InvalidStartsWithNumber
}
case .startsWithUnderscore:
if isGroup {
text = presentationData.strings.Group_Username_InvalidStartsWithUnderscore
} else {
text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore
}
case .endsWithUnderscore:
if isGroup {
text = presentationData.strings.Group_Username_InvalidEndsWithUnderscore
} else {
text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore
}
case .tooShort:
if isGroup {
text = presentationData.strings.Group_Username_InvalidTooShort
} else {
text = presentationData.strings.Channel_Username_InvalidTooShort
}
case .invalidCharacters:
text = presentationData.strings.Channel_Username_InvalidCharacters
}
case let .availability(availability):
switch availability {
case .available:
text = presentationData.strings.Channel_Username_UsernameIsAvailable(currentUsername).string
case .invalid:
text = presentationData.strings.Channel_Username_InvalidCharacters
case .taken:
text = presentationData.strings.Channel_Username_InvalidTaken
case .purchaseAvailable:
var markdownString = presentationData.strings.Channel_Username_UsernamePurchaseAvailable
let entities = generateTextEntities(markdownString, enabledTypes: [.mention])
if let entity = entities.first {
markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound))
markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound))
}
text = markdownString
}
case .checking:
text = presentationData.strings.Channel_Username_CheckingUsername
}
entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
}
if isGroup {
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_PublicLink_Info))
} else {
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
}
} else {
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
}
if !otherUsernames.isEmpty {
entries.append(.additionalLinkHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_LinksOrder : presentationData.strings.Channel_Setup_LinksOrder))
var usernames = peer.usernames
if let temporaryOrder = temporaryOrder {
var usernamesMap: [String: TelegramPeerUsername] = [:]
for username in usernames {
usernamesMap[username.username] = username
}
var sortedUsernames: [TelegramPeerUsername] = []
for username in temporaryOrder {
if let username = usernamesMap[username] {
sortedUsernames.append(username)
}
}
usernames = sortedUsernames
}
var i: Int32 = 0
for username in usernames {
entries.append(.additionalLink(presentationData.theme, username, i))
i += 1
}
entries.append(.additionalLinkInfo(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_LinksOrderInfo : presentationData.strings.Channel_Setup_LinksOrderInfo))
}
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
case .privateChannel:
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
if isGroup {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
} else {
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePrivateLinkHelp))
}
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
}
var isDiscussion = false
if let cachedData = view.cachedData as? CachedChannelData, case let .known(peerId) = cachedData.linkedDiscussionPeerId, peerId != nil {
isDiscussion = true
}
if isGroup && (selectedType == .publicChannel || isDiscussion) {
if isDiscussion {
entries.append(.joinToSendHeader(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Title.uppercased()))
entries.append(.joinToSendEveryone(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Everyone, joinToSend == .everyone))
entries.append(.joinToSendMembers(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_OnlyMembers, joinToSend == .members))
}
if !isDiscussion || joinToSend == .members {
entries.append(.approveMembers(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembers, approveMembers))
entries.append(.approveMembersInfo(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembersInfo))
}
}
entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased()))
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingChannelInfo) : (isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled : presentationData.strings.Group_Setup_ForwardingChannelInfoDisabled)))
}
} else if let peer = view.peers[view.peerId] as? TelegramGroup {
if case .revokeNames = mode {
let count = Int32(publicChannelsToRevoke?.count ?? 0)
let text: String
if count >= premiumLimits.maxPublicLinksCount {
text = presentationData.strings.Group_Username_RemoveExistingUsernamesFinalInfo
} else {
if isPremiumDisabled {
text = presentationData.strings.Group_Username_RemoveExistingUsernamesNoPremiumInfo
} else {
text = presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(premiumLimits.maxPublicLinksCount)").string
}
}
entries.append(.linksLimitInfo(presentationData.theme, text, count, limits.maxPublicLinksCount, premiumLimits.maxPublicLinksCount, isPremiumDisabled))
if let publicChannelsToRevoke = publicChannelsToRevoke {
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
var rhsDate: Int32 = 0
if case let .channel(lhs) = lhs {
lhsDate = lhs.creationDate
}
if case let .channel(rhs) = rhs {
rhsDate = rhs.creationDate
}
return lhsDate > rhsDate
}) {
entries.append(.existingLinkPeerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1
}
}
} else {
switch mode {
case .revokeNames:
break
case .privateLink:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
case .generic, .initialSetup:
let selectedType: CurrentChannelType
if let current = state.selectedType {
selectedType = current
} else {
selectedType = .privateChannel
}
let currentUsername: String
if let current = state.editingPublicLinkText {
currentUsername = current
} else {
currentUsername = ""
}
entries.append(.typeHeader(presentationData.theme, presentationData.strings.Group_Setup_TypeHeader.uppercased()))
entries.append(.typePublic(presentationData.theme, presentationData.strings.Channel_Setup_TypePublic, selectedType == .publicChannel))
entries.append(.typePrivate(presentationData.theme, presentationData.strings.Channel_Setup_TypePrivate, selectedType == .privateChannel))
switch selectedType {
case .publicChannel:
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePublicHelp))
case .privateChannel:
entries.append(.typeInfo(presentationData.theme, presentationData.strings.Group_Setup_TypePrivateHelp))
}
switch selectedType {
case .publicChannel:
entries.append(.editablePublicLink(presentationData.theme, presentationData.strings, "", currentUsername))
if let status = state.addressNameValidationStatus {
let text: String
switch status {
case let .invalidFormat(error):
switch error {
case .startsWithDigit:
text = presentationData.strings.Group_Username_InvalidStartsWithNumber
case .startsWithUnderscore:
text = presentationData.strings.Channel_Username_InvalidStartsWithUnderscore
case .endsWithUnderscore:
text = presentationData.strings.Channel_Username_InvalidEndsWithUnderscore
case .tooShort:
text = presentationData.strings.Group_Username_InvalidTooShort
case .invalidCharacters:
text = presentationData.strings.Channel_Username_InvalidCharacters
}
case let .availability(availability):
switch availability {
case .available:
text = presentationData.strings.Channel_Username_UsernameIsAvailable(currentUsername).string
case .invalid:
text = presentationData.strings.Channel_Username_InvalidCharacters
case .taken:
text = presentationData.strings.Channel_Username_InvalidTaken
case .purchaseAvailable:
var markdownString = presentationData.strings.Channel_Username_UsernamePurchaseAvailable
let entities = generateTextEntities(markdownString, enabledTypes: [.mention])
if let entity = entities.first {
markdownString.insert(contentsOf: "]()", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.upperBound))
markdownString.insert(contentsOf: "[", at: markdownString.index(markdownString.startIndex, offsetBy: entity.range.lowerBound))
}
text = markdownString
}
case .checking:
text = presentationData.strings.Channel_Username_CheckingUsername
}
entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
}
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
case .privateChannel:
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup))
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
switch mode {
case .initialSetup, .revokeNames:
break
case .generic, .privateLink:
entries.append(.privateLinkManage(presentationData.theme, presentationData.strings.InviteLink_Manage))
entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo))
}
}
}
let forwardingEnabled: Bool
if let enabled = state.forwardingEnabled {
forwardingEnabled = enabled
} else {
if peer.flags.contains(.copyProtectionEnabled) {
forwardingEnabled = false
} else {
forwardingEnabled = true
}
}
entries.append(.forwardingHeader(presentationData.theme, presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased()))
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
entries.append(.forwardingInfo(presentationData.theme, forwardingEnabled ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingGroupInfoDisabled))
}
}
return entries
}
private func effectiveChannelType(mode: ChannelVisibilityControllerMode, state: ChannelVisibilityControllerState, peer: TelegramChannel, cachedData: CachedPeerData?) -> CurrentChannelType {
let selectedType: CurrentChannelType
if let current = state.selectedType {
selectedType = current
} else {
if let addressName = peer.addressName, !addressName.isEmpty {
selectedType = .publicChannel
} else if let cachedChannelData = cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
selectedType = .publicChannel
} else if case .initialSetup = mode {
selectedType = .publicChannel
} else {
selectedType = .privateChannel
}
}
return selectedType
}
private func updatedAddressName(mode: ChannelVisibilityControllerMode, state: ChannelVisibilityControllerState, peer: EnginePeer, cachedData: CachedPeerData?) -> String? {
if case let .channel(peer) = peer {
let selectedType = effectiveChannelType(mode: mode, state: state, peer: peer, cachedData: cachedData)
let currentUsername: String
switch selectedType {
case .privateChannel:
currentUsername = ""
case .publicChannel:
if let current = state.editingPublicLinkText {
currentUsername = current
} else {
if let username = peer.editableUsername {
currentUsername = username
} else {
currentUsername = ""
}
}
}
if !currentUsername.isEmpty {
if currentUsername != peer.editableUsername {
return currentUsername
} else {
return nil
}
} else if peer.editableUsername != nil {
return ""
} else {
return nil
}
} else if case .legacyGroup = peer {
let currentUsername = state.editingPublicLinkText ?? ""
if !currentUsername.isEmpty {
return currentUsername
} else {
return nil
}
} else {
return nil
}
}
public enum ChannelVisibilityControllerMode {
case initialSetup
case generic
case privateLink
case revokeNames([EnginePeer])
}
public func channelVisibilityController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (EnginePeer.Id, @escaping () -> Void) -> Void, onDismissRemoveController: ViewController? = nil, revokedPeerAddressName: ((EnginePeer.Id) -> Void)? = nil) -> ViewController {
let statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelVisibilityControllerState())
let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
let adminedPublicChannels = Promise<[EnginePeer]?>()
if case let .revokeNames(peers) = mode {
adminedPublicChannels.set(.single(peers))
} else {
adminedPublicChannels.set(context.engine.peers.adminedPublicChannels(scope: .all)
|> map { result in
return result.map(\.peer)
}
|> map(Optional.init))
}
let peersDisablingAddressNameAssignment = Promise<[EnginePeer]?>()
peersDisablingAddressNameAssignment.set(.single(nil) |> then(context.engine.peers.channelAddressNameAssignmentAvailability(peerId: peerId.namespace == Namespaces.Peer.CloudChannel ? peerId : nil) |> mapToSignal { result -> Signal<[EnginePeer]?, NoError> in
if case .addressNameLimitReached = result {
return context.engine.peers.adminedPublicChannels(scope: .all)
|> map { result in
return result.map(\.peer)
}
|> map(Optional.init)
} else {
return .single([])
}
}))
var dismissImpl: (() -> Void)?
var dismissInputImpl: (() -> Void)?
var nextImpl: (() -> Void)?
var scrollToPublicLinkTextImpl: (() -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
var getControllerImpl: (() -> ViewController?)?
var dismissTooltipsImpl: (() -> Void)?
let actionsDisposable = DisposableSet()
let checkAddressNameDisposable = MetaDisposable()
actionsDisposable.add(checkAddressNameDisposable)
let updateAddressNameDisposable = MetaDisposable()
actionsDisposable.add(updateAddressNameDisposable)
let revokeAddressNameDisposable = MetaDisposable()
actionsDisposable.add(revokeAddressNameDisposable)
let deactivateAllAddressNamesDisposable = MetaDisposable()
actionsDisposable.add(deactivateAllAddressNamesDisposable)
let revokeLinkDisposable = MetaDisposable()
actionsDisposable.add(revokeLinkDisposable)
let toggleCopyProtectionDisposable = MetaDisposable()
actionsDisposable.add(toggleCopyProtectionDisposable)
let toggleJoinToSendDisposable = MetaDisposable()
actionsDisposable.add(toggleJoinToSendDisposable)
let toggleRequestToJoinDisposable = MetaDisposable()
actionsDisposable.add(toggleRequestToJoinDisposable)
let temporaryOrder = Promise<[String]?>(nil)
let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in
if type == .publicChannel {
let _ = combineLatest(
queue: Queue.mainQueue(),
adminedPublicChannels.get() |> filter { $0 != nil } |> take(1),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)),
context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
)
).start(next: { peers, accountPeer, peer, data in
let (limits, premiumLimits) = data
let isPremium = accountPeer?.isPremium ?? false
let hasAdditionalUsernames = (peer?._asPeer().usernames.firstIndex(where: { !$0.flags.contains(.isEditable) }) ?? nil) != nil
if let peers = peers {
let count = Int32(peers.count)
if count < limits.maxPublicLinksCount || (count < premiumLimits.maxPublicLinksCount && isPremium) || hasAdditionalUsernames {
updateState { state in
return state.withUpdatedSelectedType(type)
}
} else {
let controller = channelVisibilityController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, mode: .revokeNames(peers), upgradedToSupergroup: { _, _ in }, revokedPeerAddressName: { revokedPeerId in
let updatedPublicChannels = peers.filter { $0.id != revokedPeerId }
adminedPublicChannels.set(.single(updatedPublicChannels) |> then(
context.engine.peers.adminedPublicChannels(scope: .all)
|> map { result in
return result.map(\.peer)
}
|> map(Optional.init))
)
})
controller.navigationPresentation = .modal
pushControllerImpl?(controller)
}
} else {
}
})
} else {
updateState { state in
return state.withUpdatedSelectedType(type)
}
}
}, updatePublicLinkText: { currentText, text in
if text.isEmpty {
checkAddressNameDisposable.set(nil)
updateState { state in
return state.withUpdatedEditingPublicLinkText(text).withUpdatedAddressNameValidationStatus(nil)
}
} else if currentText == text {
checkAddressNameDisposable.set(nil)
updateState { state in
return state.withUpdatedEditingPublicLinkText(text).withUpdatedAddressNameValidationStatus(nil).withUpdatedAddressNameValidationStatus(nil)
}
} else {
updateState { state in
return state.withUpdatedEditingPublicLinkText(text)
}
checkAddressNameDisposable.set((context.engine.peers.validateAddressNameInteractive(domain: .peer(peerId), name: text)
|> deliverOnMainQueue).start(next: { result in
updateState { state in
return state.withUpdatedAddressNameValidationStatus(result)
}
}))
}
}, scrollToPublicLinkText: {
scrollToPublicLinkTextImpl?()
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
if (peerId == nil && fromPeerId == state.revealedRevokePeerId) || (peerId != nil && fromPeerId == nil) {
return state.withUpdatedRevealedRevokePeerId(peerId)
} else {
return state
}
}
}, revokePeerId: { peerId in
updateState { state in
return state.withUpdatedRevokingPeerId(peerId)
}
revokeAddressNameDisposable.set((context.engine.peers.updateAddressName(domain: .peer(peerId), name: nil) |> deliverOnMainQueue).start(error: { _ in
updateState { state in
return state.withUpdatedRevokingPeerId(nil)
}
}, completed: {
revokedPeerAddressName?(peerId)
dismissImpl?()
}))
}, copyLink: { invite in
UIPasteboard.general.string = invite.link
dismissTooltipsImpl?()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}, shareLink: { invite in
guard let inviteLink = invite.link else {
return
}
let shareController = ShareController(context: context, subject: .url(inviteLink), updatedPresentationData: updatedPresentationData)
shareController.actionCompleted = {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
presentControllerImpl?(shareController, nil)
}, linkContextAction: { node, gesture in
guard let node = node as? ContextReferenceContentNode, let controller = getControllerImpl?() else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId))
|> deliverOnMainQueue).start(next: { exportedInvitation in
if let link = exportedInvitation?.link {
UIPasteboard.general.string = link
dismissTooltipsImpl?()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
})
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId))
|> deliverOnMainQueue).start(next: { invite in
if let invite = invite {
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil)
})
}
})
})))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
isGroup = false
} else {
isGroup = true
}
let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction()
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId))
|> deliverOnMainQueue).start(next: { exportedInvitation in
if let link = exportedInvitation?.link {
var revoke = false
updateState { state in
if !state.revokingPrivateLink {
revoke = true
return state.withUpdatedRevokingPrivateLink(true)
} else {
return state
}
}
if revoke {
revokeLinkDisposable.set((context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: link) |> deliverOnMainQueue).start(completed: {
updateState {
$0.withUpdatedRevokingPrivateLink(false)
}
}))
}
}
})
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, manageInviteLinks: {
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
pushControllerImpl?(controller)
}, openLink: { invite in
let controller = InviteLinkViewController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: invite, invitationsContext: nil, revokedInvitationsContext: nil, importersContext: nil)
pushControllerImpl?(controller)
}, toggleForwarding: { value in
updateState { state in
return state.withUpdatedForwardingEnabled(value)
}
}, updateJoinToSend: { value in
updateState { state in
return state.withUpdatedJoinToSend(value)
}
}, toggleApproveMembers: { value in
updateState { state in
return state.withUpdatedApproveMembers(value)
}
}, activateLink: { name in
dismissInputImpl?()
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if case let .channel(channel) = peer, case .broadcast = channel.info {
isGroup = false
} else {
isGroup = true
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let title: String
let text: String
let action: String
if isGroup {
title = presentationData.strings.Group_Setup_ActivateAlertTitle
text = presentationData.strings.Group_Setup_ActivateAlertText
action = presentationData.strings.Group_Setup_ActivateAlertShow
} else {
title = presentationData.strings.Channel_Setup_ActivateAlertTitle
text = presentationData.strings.Channel_Setup_ActivateAlertText
action = presentationData.strings.Channel_Setup_ActivateAlertShow
}
presentControllerImpl?(textAlertController(context: context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: action, action: {
let _ = (context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: true)
|> deliverOnMainQueue).start(error: { error in
let errorText: String
switch error {
case .activeLimitReached:
if isGroup {
errorText = presentationData.strings.Group_Setup_ActiveLimitReachedError
} else {
errorText = presentationData.strings.Channel_Setup_ActiveLimitReachedError
}
default:
errorText = presentationData.strings.Login_UnknownError
}
presentControllerImpl?(textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
})
})]), nil)
})
}, deactivateLink: { name in
dismissInputImpl?()
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if case let .channel(channel) = peer, case .broadcast = channel.info {
isGroup = false
} else {
isGroup = true
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let title: String
let text: String
let action: String
if isGroup {
title = presentationData.strings.Group_Setup_DeactivateAlertTitle
text = presentationData.strings.Group_Setup_DeactivateAlertText
action = presentationData.strings.Group_Setup_DeactivateAlertHide
} else {
title = presentationData.strings.Channel_Setup_DeactivateAlertTitle
text = presentationData.strings.Channel_Setup_DeactivateAlertText
action = presentationData.strings.Channel_Setup_DeactivateAlertHide
}
presentControllerImpl?(textAlertController(context: context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: action, action: {
let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: false).start()
})]), nil)
})
}, openAuction: { username in
dismissInputImpl?()
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/username/\(username)", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
})
let peerView = context.account.viewTracker.peerView(peerId)
|> deliverOnMainQueue
let previousHadNamesToRevoke = Atomic<Bool?>(value: nil)
let previousInvitation = Atomic<ExportedInvitation?>(value: nil)
let previousUsernames = Atomic<[String]?>(value: nil)
let mainLink = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId)
)
let importersState = Promise<PeerInvitationImportersState?>(nil)
let importersContext: Signal<PeerInvitationImportersContext?, NoError> = mainLink
|> distinctUntilChanged
|> deliverOnMainQueue
|> map { invite -> PeerInvitationImportersContext? in
return invite.flatMap { context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .invite(invite: $0, requested: false)) }
} |> afterNext { context in
if let context = context {
importersState.set(context.state |> map(Optional.init))
} else {
importersState.set(.single(nil))
}
}
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
let signal = combineLatest(
presentationData,
statePromise.get() |> deliverOnMainQueue,
peerView,
adminedPublicChannels.get(),
importersContext,
importersState.get(),
context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true),
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)
),
temporaryOrder.get()
)
|> deliverOnMainQueue
|> map { presentationData, state, view, publicChannelsToRevoke, importersContext, importers, data, temporaryOrder -> (ItemListControllerState, (ItemListNodeState, Any)) in
let peer = peerViewMainPeer(view)
let (limits, premiumLimits, accountPeer) = data
let isPremium = accountPeer?.isPremium ?? false
var footerItem: ItemListControllerFooterItem?
var isGroup = false
if let peer = peer as? TelegramChannel {
if case .group = peer.info {
isGroup = true
}
} else if let _ = peer as? TelegramGroup {
isGroup = true
}
var rightNavigationButton: ItemListNavigationButton?
if case .revokeNames = mode {
let count = Int32(publicChannelsToRevoke?.count ?? 0)
if !premiumConfiguration.isPremiumDisabled && count < premiumLimits.maxPublicLinksCount {
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Premium_IncreaseLimit, colorful: true, action: {
let controller = PremiumIntroScreen(context: context, source: .publicLinks)
pushControllerImpl?(controller)
})
}
} else {
if let peer = peer as? TelegramChannel {
var doneEnabled = true
if let selectedType = state.selectedType {
switch selectedType {
case .privateChannel:
break
case .publicChannel:
var hasLocation = false
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
hasLocation = true
}
if let addressNameValidationStatus = state.addressNameValidationStatus {
switch addressNameValidationStatus {
case .availability(.available):
break
default:
doneEnabled = false
}
} else {
doneEnabled = !(peer.addressName?.isEmpty ?? true) || hasLocation
}
}
}
let isInitialSetup: Bool
if case .initialSetup = mode {
isInitialSetup = true
} else {
isInitialSetup = false
}
rightNavigationButton = ItemListNavigationButton(content: .text(isInitialSetup ? presentationData.strings.Common_Next : presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
var updatedAddressNameValue: String?
updateState { state in
updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: .channel(peer), cachedData: view.cachedData)
return state
}
if let selectedType = state.selectedType, case .privateChannel = selectedType, peer.usernames.firstIndex(where: { $0.isActive && !$0.flags.contains(.isEditable) }) != nil {
deactivateAllAddressNamesDisposable.set(context.engine.peers.deactivateAllAddressNames(peerId: peerId).start())
}
if let updatedCopyProtection = state.forwardingEnabled {
toggleCopyProtectionDisposable.set(context.engine.peers.toggleMessageCopyProtection(peerId: peerId, enabled: !updatedCopyProtection).start())
}
if let updatedJoinToSend = state.joinToSend {
toggleJoinToSendDisposable.set(context.engine.peers.toggleChannelJoinToSend(peerId: peerId, enabled: updatedJoinToSend == .members).start())
}
if let updatedApproveMembers = state.approveMembers {
toggleRequestToJoinDisposable.set(context.engine.peers.toggleChannelJoinRequest(peerId: peerId, enabled: updatedApproveMembers).start())
}
if let updatedAddressNameValue = updatedAddressNameValue {
let invokeAction: () -> Void = {
updateState { state in
return state.withUpdatedUpdatingAddressName(true)
}
_ = ApplicationSpecificNotice.markAsSeenSetPublicChannelLink(accountManager: context.sharedContext.accountManager).start()
updateAddressNameDisposable.set((context.engine.peers.updateAddressName(domain: .peer(peerId), name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue) |> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic))
|> deliverOnMainQueue).start(error: { _ in
updateState { state in
return state.withUpdatedUpdatingAddressName(false)
}
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}, completed: {
updateState { state in
return state.withUpdatedUpdatingAddressName(false)
}
switch mode {
case .initialSetup:
nextImpl?()
case .generic, .privateLink, .revokeNames:
dismissImpl?()
}
}))
}
if !updatedAddressNameValue.isEmpty {
_ = (ApplicationSpecificNotice.getSetPublicChannelLink(accountManager: context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { showAlert in
if showAlert {
let text: String
if case .broadcast = peer.info {
text = presentationData.strings.Channel_Edit_PrivatePublicLinkAlert
} else {
text = presentationData.strings.Group_Edit_PrivatePublicLinkAlert
}
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: invokeAction)]), nil)
} else {
invokeAction()
}
})
} else {
invokeAction()
}
} else {
switch mode {
case .initialSetup:
nextImpl?()
case .generic, .privateLink, .revokeNames:
dismissImpl?()
}
}
})
} else if let peer = peer as? TelegramGroup {
var doneEnabled = true
if let selectedType = state.selectedType {
switch selectedType {
case .privateChannel:
break
case .publicChannel:
if let addressNameValidationStatus = state.addressNameValidationStatus {
switch addressNameValidationStatus {
case .availability(.available):
break
default:
doneEnabled = false
}
} else {
doneEnabled = !(peer.addressName?.isEmpty ?? true)
}
}
}
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
var updatedAddressNameValue: String?
updateState { state in
updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: .legacyGroup(peer), cachedData: nil)
return state
}
if let updatedCopyProtection = state.forwardingEnabled {
toggleCopyProtectionDisposable.set(context.engine.peers.toggleMessageCopyProtection(peerId: peerId, enabled: !updatedCopyProtection).start())
}
if let updatedAddressNameValue = updatedAddressNameValue {
let invokeAction: () -> Void = {
updateState { state in
return state.withUpdatedUpdatingAddressName(true)
}
_ = ApplicationSpecificNotice.markAsSeenSetPublicChannelLink(accountManager: context.sharedContext.accountManager).start()
let signal = context.engine.peers.convertGroupToSupergroup(peerId: peerId)
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, ConvertGroupToSupergroupError> in
return context.engine.peers.updateAddressName(domain: .peer(upgradedPeerId), name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue)
|> `catch` { _ -> Signal<Void, NoError> in
return .complete()
}
|> mapToSignal { _ -> Signal<PeerId?, NoError> in
return .complete()
}
|> then(.single(upgradedPeerId))
|> castError(ConvertGroupToSupergroupError.self)
}
|> deliverOnMainQueue
updateAddressNameDisposable.set((signal
|> deliverOnMainQueue).start(next: { updatedPeerId in
if let updatedPeerId = updatedPeerId {
upgradedToSupergroup(updatedPeerId, {
dismissImpl?()
})
} else {
dismissImpl?()
}
}, error: { error in
updateState { state in
return state.withUpdatedUpdatingAddressName(false)
}
switch error {
case .tooManyChannels:
pushControllerImpl?(oldChannelsController(context: context, updatedPresentationData: updatedPresentationData, intent: .upgrade))
default:
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
}))
}
_ = (ApplicationSpecificNotice.getSetPublicChannelLink(accountManager: context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { showAlert in
if showAlert {
let text: String
if isGroup {
text = presentationData.strings.Group_Edit_PrivatePublicLinkAlert
} else {
text = presentationData.strings.Channel_Edit_PrivatePublicLinkAlert
}
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: invokeAction)]), nil)
} else {
invokeAction()
}
})
} else {
switch mode {
case .initialSetup:
nextImpl?()
case .generic, .privateLink, .revokeNames:
dismissImpl?()
}
}
})
}
}
if state.revokingPeerId != nil {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
}
let leftNavigationButton: ItemListNavigationButton?
switch mode {
case .initialSetup:
leftNavigationButton = nil
case .generic, .privateLink, .revokeNames:
leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
}
var crossfade: Bool = false
var animateChanges: Bool = false
if let cachedData = view.cachedData as? CachedChannelData {
let invitation = cachedData.exportedInvitation
let previousInvitation = previousInvitation.swap(invitation)
if invitation != previousInvitation {
crossfade = true
}
}
let hasNamesToRevoke = publicChannelsToRevoke != nil && !publicChannelsToRevoke!.isEmpty
let hadNamesToRevoke = previousHadNamesToRevoke.swap(hasNamesToRevoke)
if let peer = view.peers[view.peerId] as? TelegramChannel {
let currentUsernames = peer.usernames.map { $0.username }
let previousUsernames = previousUsernames.swap(currentUsernames)
let selectedType: CurrentChannelType
if case .privateLink = mode {
selectedType = .privateChannel
} else {
if let current = state.selectedType {
selectedType = current
} else {
if let addressName = peer.addressName, !addressName.isEmpty {
selectedType = .publicChannel
} else if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
selectedType = .publicChannel
} else {
selectedType = .privateChannel
}
}
}
if selectedType == .publicChannel, let hadNamesToRevoke = hadNamesToRevoke, !crossfade {
crossfade = hadNamesToRevoke != hasNamesToRevoke
}
if let hadNamesToRevoke = hadNamesToRevoke {
animateChanges = hadNamesToRevoke != hasNamesToRevoke
}
if temporaryOrder != nil || previousUsernames != currentUsernames {
animateChanges = true
}
}
let title: String
switch mode {
case .generic, .initialSetup:
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
title = presentationData.strings.Group_PublicLink_Title
} else {
title = isGroup ? presentationData.strings.GroupInfo_GroupType : presentationData.strings.Channel_TypeSetup_Title
}
case .privateLink:
title = presentationData.strings.GroupInfo_InviteLink_Title
case .revokeNames:
title = presentationData.strings.Premium_LimitReached
}
let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, importers: importers, state: state, limits: limits, premiumLimits: premiumLimits, isPremium: isPremium, isPremiumDisabled: premiumConfiguration.isPremiumDisabled, temporaryOrder: temporaryOrder)
var focusItemTag: ItemListItemTag?
if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
focusItemTag = ChannelVisibilityEntryTag.publicLink
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, focusItemTag: focusItemTag, footerItem: footerItem, crossfadeState: crossfade, animateChanges: animateChanges)
return (controllerState, (listState, arguments))
} |> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(context: context, state: signal)
controller.willDisappear = { _ in
dismissTooltipsImpl?()
}
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ChannelVisibilityEntry]) -> Signal<Bool, NoError> in
let fromEntry = entries[fromIndex]
guard case let .additionalLink(_, fromUsername, _) = fromEntry else {
return .single(false)
}
var referenceId: String?
var beforeAll = false
var afterAll = false
var maxIndex: Int?
var currentUsernames: [String] = []
var i = 0
for entry in entries {
switch entry {
case let .additionalLink(_, link, _):
currentUsernames.append(link.username)
if !link.isActive && maxIndex == nil {
maxIndex = max(0, i - 1)
}
i += 1
default:
break
}
}
if toIndex < entries.count {
switch entries[toIndex] {
case let .additionalLink(_, toUsername, _):
if toUsername.isActive {
referenceId = toUsername.username
} else {
afterAll = true
}
default:
if entries[toIndex] < fromEntry {
beforeAll = true
} else {
afterAll = true
}
}
} else {
afterAll = true
}
var previousIndex: Int?
for i in 0 ..< currentUsernames.count {
if currentUsernames[i] == fromUsername.username {
previousIndex = i
currentUsernames.remove(at: i)
break
}
}
var didReorder = false
if let referenceId = referenceId {
var inserted = false
for i in 0 ..< currentUsernames.count {
if currentUsernames[i] == referenceId {
if fromIndex < toIndex {
didReorder = previousIndex != i + 1
currentUsernames.insert(fromUsername.username, at: i + 1)
} else {
didReorder = previousIndex != i
currentUsernames.insert(fromUsername.username, at: i)
}
inserted = true
break
}
}
if !inserted {
didReorder = previousIndex != currentUsernames.count
if let maxIndex = maxIndex {
currentUsernames.insert(fromUsername.username, at: maxIndex)
} else {
currentUsernames.append(fromUsername.username)
}
}
} else if beforeAll {
didReorder = previousIndex != 0
currentUsernames.insert(fromUsername.username, at: 0)
} else if afterAll {
didReorder = previousIndex != currentUsernames.count
if let maxIndex = maxIndex {
currentUsernames.insert(fromUsername.username, at: maxIndex)
} else {
currentUsernames.append(fromUsername.username)
}
}
temporaryOrder.set(.single(currentUsernames))
if didReorder {
DispatchQueue.main.async {
dismissInputImpl?()
}
}
return .single(didReorder)
})
controller.setReorderCompleted({ (entries: [ChannelVisibilityEntry]) -> Void in
var currentUsernames: [TelegramPeerUsername] = []
for entry in entries {
switch entry {
case let .additionalLink(_, username, _):
currentUsernames.append(username)
default:
break
}
}
let _ = (context.engine.peers.reorderAddressNames(domain: .peer(peerId), names: currentUsernames)
|> deliverOnMainQueue).start(completed: {
temporaryOrder.set(.single(nil))
})
})
controller.beganInteractiveDragging = {
dismissInputImpl?()
}
dismissImpl = { [weak controller, weak onDismissRemoveController] in
guard let controller = controller else {
return
}
controller.view.endEditing(true)
if let onDismissRemoveController = onDismissRemoveController, let navigationController = controller.navigationController {
navigationController.setViewControllers(navigationController.viewControllers.filter { c in
if c === controller || c === onDismissRemoveController {
return false
} else {
return true
}
}, animated: true)
} else {
controller.dismiss()
}
}
dismissInputImpl = { [weak controller] in
controller?.view.endEditing(true)
}
nextImpl = { [weak controller] in
if let controller = controller {
if case .initialSetup = mode {
let selectionController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .channelCreation, onlyWriteable: true))
(controller.navigationController as? NavigationController)?.replaceAllButRootController(selectionController, animated: true)
let _ = (selectionController.result
|> deliverOnMainQueue).start(next: { [weak selectionController] result in
guard let selectionController = selectionController, let navigationController = selectionController.navigationController as? NavigationController else {
return
}
var peerIds: [ContactListPeerId] = []
if case let .result(peerIdsValue, _) = result {
peerIds = peerIdsValue
}
let filteredPeerIds = peerIds.compactMap({ peerId -> PeerId? in
if case let .peer(id) = peerId {
return id
} else {
return nil
}
})
if filteredPeerIds.isEmpty {
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer else {
return
}
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), keepStack: .never, animated: true))
})
} else {
selectionController.displayProgress = true
let _ = (context.engine.peers.addChannelMembers(peerId: peerId, memberIds: filteredPeerIds)
|> deliverOnMainQueue).start(error: { [weak selectionController] _ in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer else {
return
}
guard let selectionController = selectionController, let navigationController = selectionController.navigationController as? NavigationController else {
return
}
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), keepStack: .never, animated: true))
})
}, completed: { [weak selectionController] in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer else {
return
}
guard let selectionController = selectionController, let navigationController = selectionController.navigationController as? NavigationController else {
return
}
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), keepStack: .never, animated: true))
})
})
}
})
} else {
if let navigationController = controller.navigationController as? NavigationController {
navigationController.replaceAllButRootController(context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil), animated: true)
}
}
}
}
scrollToPublicLinkTextImpl = { [weak controller] in
DispatchQueue.main.async {
if let strongController = controller {
var resultItemNode: ListViewItemNode?
let _ = strongController.frameForItemNode({ itemNode in
if let itemNode = itemNode as? ItemListSingleLineInputItemNode {
if let tag = itemNode.tag as? ChannelVisibilityEntryTag {
if tag == .publicLink {
resultItemNode = itemNode
return true
}
}
}
return false
})
if let resultItemNode = resultItemNode {
strongController.ensureItemNodeVisible(resultItemNode)
}
}
}
}
presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
presentInGlobalOverlayImpl = { [weak controller] c in
if let controller = controller {
controller.presentInGlobalOverlay(c)
}
}
getControllerImpl = { [weak controller] in
return controller
}
dismissTooltipsImpl = { [weak controller] in
controller?.window?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
})
controller?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismissWithCommitAction()
}
return true
})
}
return controller
}
final class InviteLinkContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceNode: ContextReferenceContentNode
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}