mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
2511 lines
134 KiB
Swift
2511 lines
134 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import SyncCore
|
|
import LegacyComponents
|
|
import TelegramPresentationData
|
|
import SafariServices
|
|
import TelegramUIPreferences
|
|
import ItemListUI
|
|
import PresentationDataUtils
|
|
import TextFormat
|
|
import AccountContext
|
|
import TelegramStringFormatting
|
|
import TemporaryCachedPeerDataManager
|
|
import ShareController
|
|
import AlertUI
|
|
import PresentationDataUtils
|
|
import MediaResources
|
|
import PhotoResources
|
|
import LocationResources
|
|
import GalleryUI
|
|
import LegacyUI
|
|
import LocationUI
|
|
import ItemListPeerItem
|
|
import ContactListUI
|
|
import ItemListAvatarAndNameInfoItem
|
|
import ItemListPeerActionItem
|
|
import WebSearchUI
|
|
import Geocoding
|
|
import PeerAvatarGalleryUI
|
|
import Emoji
|
|
import NotificationMuteSettingsUI
|
|
import MapResourceToAvatarSizes
|
|
import NotificationSoundSelectionUI
|
|
import ItemListAddressItem
|
|
import AppBundle
|
|
import Markdown
|
|
import LocalizedPeerData
|
|
|
|
private let maxParticipantsDisplayedLimit: Int32 = 50
|
|
private let maxParticipantsDisplayedCollapseLimit: Int32 = 60
|
|
|
|
private final class GroupInfoArguments {
|
|
let context: AccountContext
|
|
|
|
let avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext
|
|
let tapAvatarAction: () -> Void
|
|
let changeProfilePhoto: () -> Void
|
|
let pushController: (ViewController) -> Void
|
|
let presentController: (ViewController, ViewControllerPresentationArguments) -> Void
|
|
let changeNotificationMuteSettings: () -> Void
|
|
let openPreHistory: () -> Void
|
|
let openSharedMedia: () -> Void
|
|
let openAdministrators: () -> Void
|
|
let openPermissions: () -> Void
|
|
let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void
|
|
let updateEditingDescriptionText: (String) -> Void
|
|
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
|
let addMember: () -> Void
|
|
let promotePeer: (RenderedChannelParticipant) -> Void
|
|
let restrictPeer: (RenderedChannelParticipant) -> Void
|
|
let removePeer: (PeerId) -> Void
|
|
let leave: () -> Void
|
|
let displayUsernameShareMenu: (String) -> Void
|
|
let displayUsernameContextMenu: (String) -> Void
|
|
let displayAboutContextMenu: (String) -> Void
|
|
let aboutLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
|
|
let openStickerPackSetup: () -> Void
|
|
let openGroupTypeSetup: () -> Void
|
|
let openLinkedChannelSetup: () -> Void
|
|
let openLocation: (PeerGeoLocation) -> Void
|
|
let changeLocation: () -> Void
|
|
let displayLocationContextMenu: (String) -> Void
|
|
let expandParticipants: () -> Void
|
|
|
|
init(context: AccountContext, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, ViewControllerPresentationArguments) -> Void, changeNotificationMuteSettings: @escaping () -> Void, openPreHistory: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openAdministrators: @escaping () -> Void, openPermissions: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addMember: @escaping () -> Void, promotePeer: @escaping (RenderedChannelParticipant) -> Void, restrictPeer: @escaping (RenderedChannelParticipant) -> Void, removePeer: @escaping (PeerId) -> Void, leave: @escaping () -> Void, displayUsernameShareMenu: @escaping (String) -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayAboutContextMenu: @escaping (String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, openStickerPackSetup: @escaping () -> Void, openGroupTypeSetup: @escaping () -> Void, openLinkedChannelSetup: @escaping () -> Void, openLocation: @escaping (PeerGeoLocation) -> Void, changeLocation: @escaping () -> Void, displayLocationContextMenu: @escaping (String) -> Void, expandParticipants: @escaping () -> Void) {
|
|
self.context = context
|
|
self.avatarAndNameInfoContext = avatarAndNameInfoContext
|
|
self.tapAvatarAction = tapAvatarAction
|
|
self.changeProfilePhoto = changeProfilePhoto
|
|
self.pushController = pushController
|
|
self.presentController = presentController
|
|
self.changeNotificationMuteSettings = changeNotificationMuteSettings
|
|
self.openPreHistory = openPreHistory
|
|
self.openSharedMedia = openSharedMedia
|
|
self.openAdministrators = openAdministrators
|
|
self.openPermissions = openPermissions
|
|
self.updateEditingName = updateEditingName
|
|
self.updateEditingDescriptionText = updateEditingDescriptionText
|
|
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
|
self.addMember = addMember
|
|
self.promotePeer = promotePeer
|
|
self.restrictPeer = restrictPeer
|
|
self.removePeer = removePeer
|
|
self.leave = leave
|
|
self.displayUsernameShareMenu = displayUsernameShareMenu
|
|
self.displayUsernameContextMenu = displayUsernameContextMenu
|
|
self.displayAboutContextMenu = displayAboutContextMenu
|
|
self.aboutLinkAction = aboutLinkAction
|
|
self.openStickerPackSetup = openStickerPackSetup
|
|
self.openGroupTypeSetup = openGroupTypeSetup
|
|
self.openLinkedChannelSetup = openLinkedChannelSetup
|
|
self.openLocation = openLocation
|
|
self.changeLocation = changeLocation
|
|
self.displayLocationContextMenu = displayLocationContextMenu
|
|
self.expandParticipants = expandParticipants
|
|
}
|
|
}
|
|
|
|
private enum GroupInfoSection: ItemListSectionId {
|
|
case info
|
|
case about
|
|
case infoManagement
|
|
case sharedMediaAndNotifications
|
|
case memberManagement
|
|
case members
|
|
case leave
|
|
}
|
|
|
|
private enum GroupInfoEntryTag {
|
|
case about
|
|
case link
|
|
case location
|
|
}
|
|
|
|
private enum GroupInfoMemberStatus: Equatable {
|
|
case member
|
|
case admin(rank: String?)
|
|
case owner(rank: String?)
|
|
}
|
|
|
|
private enum GroupEntryStableId: Hashable, Equatable {
|
|
case peer(PeerId)
|
|
case index(Int)
|
|
|
|
static func ==(lhs: GroupEntryStableId, rhs: GroupEntryStableId) -> Bool {
|
|
switch lhs {
|
|
case let .peer(peerId):
|
|
if case .peer(peerId) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .index(index):
|
|
if case .index(index) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
var hashValue: Int {
|
|
switch self {
|
|
case let .peer(peerId):
|
|
return peerId.hashValue
|
|
case let .index(index):
|
|
return index.hashValue
|
|
}
|
|
}
|
|
}
|
|
|
|
enum ParticipantRevealActionType {
|
|
case promote
|
|
case restrict
|
|
case remove
|
|
}
|
|
|
|
struct ParticipantRevealAction: Equatable {
|
|
let type: ItemListPeerItemRevealOptionType
|
|
let title: String
|
|
let action: ParticipantRevealActionType
|
|
}
|
|
|
|
private enum GroupInfoEntry: ItemListNodeEntry {
|
|
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?)
|
|
case setGroupPhoto(PresentationTheme, String)
|
|
case groupDescriptionSetup(PresentationTheme, String, String)
|
|
case about(PresentationTheme, String)
|
|
case locationHeader(PresentationTheme, String)
|
|
case location(PresentationTheme, PeerGeoLocation)
|
|
case changeLocation(PresentationTheme, String)
|
|
case link(PresentationTheme, String)
|
|
case sharedMedia(PresentationTheme, String)
|
|
case notifications(PresentationTheme, String, String)
|
|
case stickerPack(PresentationTheme, String, String)
|
|
case groupTypeSetup(PresentationTheme, String, String)
|
|
case linkedChannelSetup(PresentationTheme, String, String)
|
|
case preHistory(PresentationTheme, String, String)
|
|
case administrators(PresentationTheme, String, String)
|
|
case permissions(PresentationTheme, String, String)
|
|
case addMember(PresentationTheme, String, editing: Bool)
|
|
case member(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, index: Int, peerId: PeerId, peer: Peer, participant: RenderedChannelParticipant?, presence: PeerPresence?, memberStatus: GroupInfoMemberStatus, editing: ItemListPeerItemEditing, revealActions: [ParticipantRevealAction], enabled: Bool, selectable: Bool)
|
|
case expand(PresentationTheme, String)
|
|
case leave(PresentationTheme, String)
|
|
|
|
var section: ItemListSectionId {
|
|
switch self {
|
|
case .info, .setGroupPhoto, .groupDescriptionSetup, .about:
|
|
return GroupInfoSection.info.rawValue
|
|
case .locationHeader, .location, .changeLocation, .link:
|
|
return GroupInfoSection.about.rawValue
|
|
case .groupTypeSetup, .linkedChannelSetup, .preHistory, .stickerPack:
|
|
return GroupInfoSection.infoManagement.rawValue
|
|
case .sharedMedia, .notifications:
|
|
return GroupInfoSection.sharedMediaAndNotifications.rawValue
|
|
case .permissions, .administrators:
|
|
return GroupInfoSection.memberManagement.rawValue
|
|
case .addMember, .member, .expand:
|
|
return GroupInfoSection.members.rawValue
|
|
case .leave:
|
|
return GroupInfoSection.leave.rawValue
|
|
}
|
|
}
|
|
|
|
static func ==(lhs: GroupInfoEntry, rhs: GroupInfoEntry) -> Bool {
|
|
switch lhs {
|
|
case let .info(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsCachedData, lhsState, lhsUpdatingAvatar):
|
|
if case let .info(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsCachedData, rhsState, rhsUpdatingAvatar) = rhs {
|
|
if lhsTheme !== rhsTheme {
|
|
return false
|
|
}
|
|
if lhsStrings !== rhsStrings {
|
|
return false
|
|
}
|
|
if lhsDateTimeFormat != rhsDateTimeFormat {
|
|
return false
|
|
}
|
|
if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer {
|
|
if !lhsPeer.isEqual(rhsPeer) {
|
|
return false
|
|
}
|
|
} else if (lhsPeer == nil) != (rhsPeer != nil) {
|
|
return false
|
|
}
|
|
if let lhsCachedData = lhsCachedData, let rhsCachedData = rhsCachedData {
|
|
if !lhsCachedData.isEqual(to: rhsCachedData) {
|
|
return false
|
|
}
|
|
} else if (lhsCachedData != nil) != (rhsCachedData != nil) {
|
|
return false
|
|
}
|
|
if lhsState != rhsState {
|
|
return false
|
|
}
|
|
if lhsUpdatingAvatar != rhsUpdatingAvatar {
|
|
return false
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .setGroupPhoto(lhsTheme, lhsText):
|
|
if case let .setGroupPhoto(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .groupDescriptionSetup(lhsTheme, lhsPlaceholder, lhsText):
|
|
if case let .groupDescriptionSetup(rhsTheme, rhsPlaceholder, rhsText) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .sharedMedia(lhsTheme, lhsText):
|
|
if case let .sharedMedia(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .leave(lhsTheme, lhsText):
|
|
if case let .leave(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .about(lhsTheme, lhsText):
|
|
if case let .about(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .locationHeader(lhsTheme, lhsText):
|
|
if case let .locationHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .location(lhsTheme, lhsLocation):
|
|
if case let .location(rhsTheme, rhsLocation) = rhs, lhsTheme === rhsTheme, lhsLocation == rhsLocation {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .changeLocation(lhsTheme, lhsText):
|
|
if case let .changeLocation(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .link(lhsTheme, lhsText):
|
|
if case let .link(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .notifications(lhsTheme, lhsTitle, lhsText):
|
|
if case let .notifications(rhsTheme, rhsTitle, rhsText) = rhs {
|
|
if lhsTheme !== rhsTheme {
|
|
return false
|
|
}
|
|
if lhsTitle != rhsTitle {
|
|
return false
|
|
}
|
|
if lhsText != rhsText {
|
|
return false
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .stickerPack(lhsTheme, lhsTitle, lhsValue):
|
|
if case let .stickerPack(rhsTheme, rhsTitle, rhsValue) = rhs {
|
|
if lhsTheme !== rhsTheme {
|
|
return false
|
|
}
|
|
if lhsTitle != rhsTitle {
|
|
return false
|
|
}
|
|
if lhsValue != rhsValue {
|
|
return false
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .preHistory(lhsTheme, lhsTitle, lhsValue):
|
|
if case let .preHistory(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .groupTypeSetup(lhsTheme, lhsTitle, lhsText):
|
|
if case let .groupTypeSetup(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .linkedChannelSetup(lhsTheme, lhsTitle, lhsText):
|
|
if case let .linkedChannelSetup(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .permissions(lhsTheme, lhsTitle, lhsText):
|
|
if case let .permissions(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .administrators(lhsTheme, lhsTitle, lhsText):
|
|
if case let .administrators(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .addMember(lhsTheme, lhsTitle, lhsEditing):
|
|
if case let .addMember(rhsTheme, rhsTitle, rhsEditing) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsEditing == rhsEditing {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .member(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsIndex, lhsPeerId, lhsPeer, lhsParticipant, lhsPresence, lhsMemberStatus, lhsEditing, lhsActions, lhsEnabled, lhsSelectable):
|
|
if case let .member(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsIndex, rhsPeerId, rhsPeer, rhsParticipant, rhsPresence, rhsMemberStatus, rhsEditing, rhsActions, rhsEnabled, rhsSelectable) = rhs {
|
|
if lhsTheme !== rhsTheme {
|
|
return false
|
|
}
|
|
if lhsStrings !== rhsStrings {
|
|
return false
|
|
}
|
|
if lhsDateTimeFormat != rhsDateTimeFormat {
|
|
return false
|
|
}
|
|
if lhsNameDisplayOrder != rhsNameDisplayOrder {
|
|
return false
|
|
}
|
|
if lhsIndex != rhsIndex {
|
|
return false
|
|
}
|
|
if lhsMemberStatus != rhsMemberStatus {
|
|
return false
|
|
}
|
|
if lhsPeerId != rhsPeerId {
|
|
return false
|
|
}
|
|
if !lhsPeer.isEqual(rhsPeer) {
|
|
return false
|
|
}
|
|
if lhsParticipant != rhsParticipant {
|
|
return false
|
|
}
|
|
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
|
if !lhsPresence.isEqual(to: rhsPresence) {
|
|
return false
|
|
}
|
|
} else if (lhsPresence != nil) != (rhsPresence != nil) {
|
|
return false
|
|
}
|
|
if lhsEditing != rhsEditing {
|
|
return false
|
|
}
|
|
if lhsActions != rhsActions {
|
|
return false
|
|
}
|
|
if lhsEnabled != rhsEnabled {
|
|
return false
|
|
}
|
|
if lhsSelectable != rhsSelectable {
|
|
return false
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .expand(lhsTheme, lhsText):
|
|
if case let .expand(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
var stableId: GroupEntryStableId {
|
|
switch self {
|
|
case let .member(_, _, _, _, _, peerId, _, _, _, _, _, _, _, _):
|
|
return .peer(peerId)
|
|
default:
|
|
return .index(self.sortIndex)
|
|
}
|
|
}
|
|
|
|
private var sortIndex: Int {
|
|
switch self {
|
|
case .info:
|
|
return 0
|
|
case .setGroupPhoto:
|
|
return 1
|
|
case .groupDescriptionSetup:
|
|
return 2
|
|
case .about:
|
|
return 3
|
|
case .locationHeader:
|
|
return 4
|
|
case .location:
|
|
return 5
|
|
case .changeLocation:
|
|
return 6
|
|
case .link:
|
|
return 7
|
|
case .groupTypeSetup:
|
|
return 8
|
|
case .linkedChannelSetup:
|
|
return 9
|
|
case .preHistory:
|
|
return 10
|
|
case .stickerPack:
|
|
return 11
|
|
case .notifications:
|
|
return 12
|
|
case .sharedMedia:
|
|
return 13
|
|
case .permissions:
|
|
return 14
|
|
case .administrators:
|
|
return 15
|
|
case .addMember:
|
|
return 16
|
|
case let .member(_, _, _, _, index, _, _, _, _, _, _, _, _, _):
|
|
return 20 + index
|
|
case .expand:
|
|
return 200000 + 1
|
|
case .leave:
|
|
return 200000 + 2
|
|
}
|
|
}
|
|
|
|
static func <(lhs: GroupInfoEntry, rhs: GroupInfoEntry) -> Bool {
|
|
return lhs.sortIndex < rhs.sortIndex
|
|
}
|
|
|
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
|
let arguments = arguments as! GroupInfoArguments
|
|
switch self {
|
|
case let .info(theme, strings, dateTimeFormat, peer, cachedData, state, updatingAvatar):
|
|
return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: nil, cachedData: cachedData, state: state, sectionId: self.section, style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in
|
|
arguments.updateEditingName(editingName)
|
|
}, avatarTapped: {
|
|
arguments.tapAvatarAction()
|
|
}, context: arguments.avatarAndNameInfoContext, updatingImage: updatingAvatar)
|
|
case let .setGroupPhoto(theme, text):
|
|
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
|
arguments.changeProfilePhoto()
|
|
})
|
|
case let .about(theme, text):
|
|
return ItemListMultilineTextItem(presentationData: presentationData, text: foldMultipleLineBreaks(text), enabledEntityTypes: [.url, .mention, .hashtag], sectionId: self.section, style: .blocks, longTapAction: {
|
|
arguments.displayAboutContextMenu(text)
|
|
}, linkItemAction: { action, itemLink in
|
|
arguments.aboutLinkAction(action, itemLink)
|
|
}, tag: GroupInfoEntryTag.about)
|
|
case let .locationHeader(theme, text):
|
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
|
case let .location(theme, location):
|
|
let imageSignal = chatMapSnapshotImage(account: arguments.context.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90))
|
|
return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: {
|
|
arguments.openLocation(location)
|
|
}, longTapAction: {
|
|
arguments.displayLocationContextMenu(location.address.replacingOccurrences(of: "\n", with: ", "))
|
|
}, tag: GroupInfoEntryTag.location)
|
|
case let .changeLocation(theme, text):
|
|
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
|
arguments.changeLocation()
|
|
}, clearHighlightAutomatically: false)
|
|
case let .link(theme, url):
|
|
return ItemListActionItem(presentationData: presentationData, title: url, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
|
arguments.displayUsernameShareMenu(url)
|
|
}, longTapAction: {
|
|
arguments.displayUsernameContextMenu(url)
|
|
}, tag: GroupInfoEntryTag.link)
|
|
case let .notifications(theme, title, text):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: title, label: text, sectionId: self.section, style: .blocks, action: {
|
|
arguments.changeNotificationMuteSettings()
|
|
})
|
|
case let .stickerPack(theme, title, value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
|
arguments.openStickerPackSetup()
|
|
})
|
|
case let .preHistory(theme, title, value):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: title, label: value, sectionId: self.section, style: .blocks, action: {
|
|
arguments.openPreHistory()
|
|
})
|
|
case let .sharedMedia(theme, title):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: title, label: "", sectionId: self.section, style: .blocks, action: {
|
|
arguments.openSharedMedia()
|
|
})
|
|
case let .addMember(theme, title, editing):
|
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: title, sectionId: self.section, editing: editing, action: {
|
|
arguments.addMember()
|
|
})
|
|
case let .groupTypeSetup(theme, title, text):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: title, label: text, sectionId: self.section, style: .blocks, action: {
|
|
arguments.openGroupTypeSetup()
|
|
})
|
|
case let .linkedChannelSetup(theme, title, text):
|
|
return ItemListDisclosureItem(presentationData: presentationData, title: title, label: text, sectionId: self.section, style: .blocks, action: {
|
|
arguments.openLinkedChannelSetup()
|
|
})
|
|
case let .groupDescriptionSetup(theme, placeholder, text):
|
|
return ItemListMultilineInputItem(presentationData: presentationData, text: text, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: 255, display: true), sectionId: self.section, style: .blocks, textUpdated: { updatedText in
|
|
arguments.updateEditingDescriptionText(updatedText)
|
|
})
|
|
case let .permissions(theme, title, text):
|
|
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesChat.groupInfoPermissionsIcon(theme), title: title, label: text, sectionId: self.section, style: .blocks, action: {
|
|
arguments.openPermissions()
|
|
})
|
|
case let .administrators(theme, title, text):
|
|
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesChat.groupInfoAdminsIcon(theme), title: title, label: text, sectionId: self.section, style: .blocks, action: {
|
|
arguments.openAdministrators()
|
|
})
|
|
case let .member(theme, strings, dateTimeFormat, nameDisplayOrder, _, _, peer, participant, presence, memberStatus, editing, actions, enabled, selectable):
|
|
let label: String?
|
|
switch memberStatus {
|
|
case let .owner(rank):
|
|
label = rank?.trimmingEmojis ?? strings.GroupInfo_LabelOwner
|
|
case let .admin(rank):
|
|
label = rank?.trimmingEmojis ?? strings.GroupInfo_LabelAdmin
|
|
case .member:
|
|
label = nil
|
|
}
|
|
var options: [ItemListPeerItemRevealOption] = []
|
|
for action in actions {
|
|
options.append(ItemListPeerItemRevealOption(type: action.type, title: action.title, action: {
|
|
switch action.action {
|
|
case .promote:
|
|
if let participant = participant {
|
|
arguments.promotePeer(participant)
|
|
}
|
|
case .restrict:
|
|
if let participant = participant {
|
|
arguments.restrictPeer(participant)
|
|
}
|
|
case .remove:
|
|
arguments.removePeer(peer.id)
|
|
}
|
|
}))
|
|
}
|
|
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: presence, text: .presence, label: label == nil ? .none : .text(label!, .standard), editing: editing, revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: enabled, selectable: selectable, sectionId: self.section, action: {
|
|
if let infoController = arguments.context.sharedContext.makePeerInfoController(context: arguments.context, peer: peer, mode: .generic), selectable {
|
|
arguments.pushController(infoController)
|
|
}
|
|
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
|
arguments.setPeerIdWithRevealedOptions(peerId, fromPeerId)
|
|
}, removePeer: { peerId in
|
|
arguments.removePeer(peerId)
|
|
})
|
|
case let .expand(theme, title):
|
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
|
arguments.expandParticipants()
|
|
})
|
|
case let .leave(theme, title):
|
|
return ItemListActionItem(presentationData: presentationData, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
|
arguments.leave()
|
|
})
|
|
default:
|
|
preconditionFailure()
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct TemporaryParticipant: Equatable {
|
|
let peer: Peer
|
|
let presence: PeerPresence?
|
|
let timestamp: Int32
|
|
|
|
static func ==(lhs: TemporaryParticipant, rhs: TemporaryParticipant) -> Bool {
|
|
if !lhs.peer.isEqual(rhs.peer) {
|
|
return false
|
|
}
|
|
if let lhsPresence = lhs.presence, let rhsPresence = rhs.presence {
|
|
if !lhsPresence.isEqual(to: rhsPresence) {
|
|
return false
|
|
}
|
|
} else if (lhs.presence != nil) != (rhs.presence != nil) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
private struct GroupInfoState: Equatable {
|
|
let updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
|
let editingState: GroupInfoEditingState?
|
|
let updatingName: ItemListAvatarAndNameInfoItemName?
|
|
let peerIdWithRevealedOptions: PeerId?
|
|
let expandedParticipants: Bool
|
|
|
|
let temporaryParticipants: [TemporaryParticipant]
|
|
let successfullyAddedParticipantIds: Set<PeerId>
|
|
let removingParticipantIds: Set<PeerId>
|
|
|
|
let savingData: Bool
|
|
|
|
let searchingMembers: Bool
|
|
|
|
static func ==(lhs: GroupInfoState, rhs: GroupInfoState) -> Bool {
|
|
if lhs.updatingAvatar != rhs.updatingAvatar {
|
|
return false
|
|
}
|
|
if lhs.editingState != rhs.editingState {
|
|
return false
|
|
}
|
|
if lhs.updatingName != rhs.updatingName {
|
|
return false
|
|
}
|
|
if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions {
|
|
return false
|
|
}
|
|
if lhs.expandedParticipants != rhs.expandedParticipants {
|
|
return false
|
|
}
|
|
if lhs.temporaryParticipants != rhs.temporaryParticipants {
|
|
return false
|
|
}
|
|
if lhs.successfullyAddedParticipantIds != rhs.successfullyAddedParticipantIds {
|
|
return false
|
|
}
|
|
if lhs.removingParticipantIds != rhs.removingParticipantIds {
|
|
return false
|
|
}
|
|
if lhs.savingData != rhs.savingData {
|
|
return false
|
|
}
|
|
if lhs.searchingMembers != rhs.searchingMembers {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func withUpdatedUpdatingAvatar(_ updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: updatingAvatar, editingState: self.editingState, updatingName: self.updatingName, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, expandedParticipants: self.expandedParticipants, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, searchingMembers: self.searchingMembers)
|
|
}
|
|
|
|
func withUpdatedEditingState(_ editingState: GroupInfoEditingState?) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: self.updatingAvatar, editingState: editingState, updatingName: self.updatingName, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, expandedParticipants: self.expandedParticipants, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, searchingMembers: self.searchingMembers)
|
|
}
|
|
|
|
func withUpdatedUpdatingName(_ updatingName: ItemListAvatarAndNameInfoItemName?) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: self.updatingAvatar, editingState: self.editingState, updatingName: updatingName, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, expandedParticipants: self.expandedParticipants, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, searchingMembers: self.searchingMembers)
|
|
}
|
|
|
|
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: self.updatingAvatar, editingState: self.editingState, updatingName: self.updatingName, peerIdWithRevealedOptions: peerIdWithRevealedOptions, expandedParticipants: self.expandedParticipants, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, searchingMembers: self.searchingMembers)
|
|
}
|
|
|
|
func withUpdatedExpandedParticipants(_ expandedParticipants: Bool) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: self.updatingAvatar, editingState: self.editingState, updatingName: self.updatingName, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, expandedParticipants: expandedParticipants, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, searchingMembers: self.searchingMembers)
|
|
}
|
|
|
|
func withUpdatedTemporaryParticipants(_ temporaryParticipants: [TemporaryParticipant]) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: self.updatingAvatar, editingState: self.editingState, updatingName: self.updatingName, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, expandedParticipants: self.expandedParticipants, temporaryParticipants: temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, searchingMembers: self.searchingMembers)
|
|
}
|
|
|
|
func withUpdatedSuccessfullyAddedParticipantIds(_ successfullyAddedParticipantIds: Set<PeerId>) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: self.updatingAvatar, editingState: self.editingState, updatingName: self.updatingName, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, expandedParticipants: self.expandedParticipants, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, searchingMembers: self.searchingMembers)
|
|
}
|
|
|
|
func withUpdatedRemovingParticipantIds(_ removingParticipantIds: Set<PeerId>) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: self.updatingAvatar, editingState: self.editingState, updatingName: self.updatingName, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, expandedParticipants: self.expandedParticipants, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: removingParticipantIds, savingData: self.savingData, searchingMembers: self.searchingMembers)
|
|
}
|
|
|
|
func withUpdatedSavingData(_ savingData: Bool) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: self.updatingAvatar, editingState: self.editingState, updatingName: self.updatingName, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, expandedParticipants: self.expandedParticipants, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: savingData, searchingMembers: self.searchingMembers)
|
|
}
|
|
|
|
func withUpdatedSearchingMembers(_ searchingMembers: Bool) -> GroupInfoState {
|
|
return GroupInfoState(updatingAvatar: self.updatingAvatar, editingState: self.editingState, updatingName: self.updatingName, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, expandedParticipants: self.expandedParticipants, temporaryParticipants: self.temporaryParticipants, successfullyAddedParticipantIds: self.successfullyAddedParticipantIds, removingParticipantIds: self.removingParticipantIds, savingData: self.savingData, searchingMembers: searchingMembers)
|
|
}
|
|
}
|
|
|
|
private struct GroupInfoEditingState: Equatable {
|
|
let editingName: ItemListAvatarAndNameInfoItemName?
|
|
let editingDescriptionText: String
|
|
|
|
func withUpdatedEditingDescriptionText(_ editingDescriptionText: String) -> GroupInfoEditingState {
|
|
return GroupInfoEditingState(editingName: self.editingName, editingDescriptionText: editingDescriptionText)
|
|
}
|
|
|
|
static func ==(lhs: GroupInfoEditingState, rhs: GroupInfoEditingState) -> Bool {
|
|
if lhs.editingName != rhs.editingName {
|
|
return false
|
|
}
|
|
if lhs.editingDescriptionText != rhs.editingDescriptionText {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
private func canRemoveParticipant(account: Account, isAdmin: Bool, participantId: PeerId, invitedBy: PeerId?) -> Bool {
|
|
if participantId == account.peerId {
|
|
return false
|
|
}
|
|
|
|
if account.peerId == invitedBy {
|
|
return true
|
|
}
|
|
|
|
return isAdmin
|
|
}
|
|
|
|
private func canRemoveParticipant(account: Account, channel: TelegramChannel, participant: ChannelParticipant) -> Bool {
|
|
if participant.peerId == account.peerId {
|
|
return false
|
|
}
|
|
|
|
if channel.flags.contains(.isCreator) {
|
|
return true
|
|
}
|
|
|
|
switch participant {
|
|
case .creator:
|
|
return false
|
|
case let .member(_, _, adminInfo, _, _):
|
|
if channel.hasPermission(.banMembers) {
|
|
if let adminInfo = adminInfo {
|
|
return adminInfo.promotedBy == account.peerId
|
|
} else {
|
|
return false
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private func groupInfoEntries(account: Account, presentationData: PresentationData, view: PeerView, channelMembers: [RenderedChannelParticipant], globalNotificationSettings: GlobalNotificationSettings, state: GroupInfoState) -> [GroupInfoEntry] {
|
|
var entries: [GroupInfoEntry] = []
|
|
|
|
var canEditGroupInfo = false
|
|
var canEditMembers = false
|
|
var canAddMembers = false
|
|
var isPublic = false
|
|
var isCreator = false
|
|
if let group = view.peers[view.peerId] as? TelegramGroup {
|
|
if case .creator = group.role {
|
|
isCreator = true
|
|
}
|
|
switch group.role {
|
|
case .admin, .creator:
|
|
canEditGroupInfo = true
|
|
canEditMembers = true
|
|
canAddMembers = true
|
|
case .member:
|
|
break
|
|
}
|
|
if !group.hasBannedPermission(.banChangeInfo) {
|
|
canEditGroupInfo = true
|
|
}
|
|
if !group.hasBannedPermission(.banAddMembers) {
|
|
canAddMembers = true
|
|
}
|
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
|
isPublic = channel.username != nil
|
|
if !isPublic, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
|
|
isPublic = true
|
|
}
|
|
|
|
isCreator = channel.flags.contains(.isCreator)
|
|
if channel.hasPermission(.changeInfo) {
|
|
canEditGroupInfo = true
|
|
}
|
|
if channel.hasPermission(.banMembers) {
|
|
canEditMembers = true
|
|
}
|
|
if channel.hasPermission(.inviteMembers) {
|
|
canAddMembers = true
|
|
}
|
|
}
|
|
|
|
if let peer = peerViewMainPeer(view) {
|
|
let infoState = ItemListAvatarAndNameInfoItemState(editingName: canEditGroupInfo ? state.editingState?.editingName : nil, updatingName: state.updatingName)
|
|
entries.append(.info(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer: peer, cachedData: view.cachedData, state: infoState, updatingAvatar: state.updatingAvatar))
|
|
}
|
|
|
|
let peerNotificationSettings: TelegramPeerNotificationSettings = (view.notificationSettings as? TelegramPeerNotificationSettings) ?? TelegramPeerNotificationSettings.defaultSettings
|
|
let notificationsText: String
|
|
|
|
if case let .muted(until) = peerNotificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
|
if until < Int32.max - 1 {
|
|
notificationsText = stringForRemainingMuteInterval(strings: presentationData.strings, muteInterval: until)
|
|
} else {
|
|
notificationsText = presentationData.strings.UserInfo_NotificationsDisabled
|
|
}
|
|
} else if case .default = peerNotificationSettings.messageSound {
|
|
notificationsText = presentationData.strings.UserInfo_NotificationsEnabled
|
|
} else {
|
|
notificationsText = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: peerNotificationSettings.messageSound, default: globalNotificationSettings.effective.channels.sound)
|
|
}
|
|
|
|
if let editingState = state.editingState {
|
|
if canEditGroupInfo {
|
|
entries.append(GroupInfoEntry.setGroupPhoto(presentationData.theme, presentationData.strings.GroupInfo_SetGroupPhoto))
|
|
|
|
entries.append(GroupInfoEntry.groupDescriptionSetup(presentationData.theme, presentationData.strings.Channel_About_Placeholder, editingState.editingDescriptionText))
|
|
}
|
|
|
|
if let group = view.peers[view.peerId] as? TelegramGroup, let cachedGroupData = view.cachedData as? CachedGroupData {
|
|
if case .creator = group.role {
|
|
if cachedGroupData.flags.contains(.canChangeUsername) {
|
|
entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_GroupType, presentationData.strings.Channel_Setup_TypePrivate))
|
|
}
|
|
entries.append(GroupInfoEntry.preHistory(presentationData.theme, presentationData.strings.GroupInfo_GroupHistory, presentationData.strings.GroupInfo_GroupHistoryHidden))
|
|
|
|
var activePermissionCount: Int?
|
|
if let defaultBannedRights = group.defaultBannedRights {
|
|
var count = 0
|
|
for (right, _) in allGroupPermissionList {
|
|
if !defaultBannedRights.flags.contains(right) {
|
|
count += 1
|
|
}
|
|
}
|
|
activePermissionCount = count
|
|
}
|
|
entries.append(GroupInfoEntry.permissions(presentationData.theme, presentationData.strings.GroupInfo_Permissions, activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList.count)" }) ?? ""))
|
|
entries.append(.administrators(presentationData.theme, presentationData.strings.GroupInfo_Administrators, ""))
|
|
}
|
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel, let cachedChannelData = view.cachedData as? CachedChannelData {
|
|
if isCreator, let location = cachedChannelData.peerGeoLocation {
|
|
entries.append(.locationHeader(presentationData.theme, presentationData.strings.GroupInfo_Location.uppercased()))
|
|
entries.append(.location(presentationData.theme, location))
|
|
if cachedChannelData.flags.contains(.canChangePeerGeoLocation) {
|
|
entries.append(.changeLocation(presentationData.theme, presentationData.strings.Group_Location_ChangeLocation))
|
|
}
|
|
}
|
|
|
|
if isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) {
|
|
if cachedChannelData.peerGeoLocation != nil {
|
|
if isCreator {
|
|
entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_PublicLink, channel.addressName ?? presentationData.strings.GroupInfo_PublicLinkAdd))
|
|
}
|
|
} else {
|
|
if cachedChannelData.flags.contains(.canChangeUsername) {
|
|
entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_GroupType, isPublic ? presentationData.strings.Channel_Setup_TypePublic : presentationData.strings.Channel_Setup_TypePrivate))
|
|
if let linkedDiscussionPeerId = cachedChannelData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
|
|
let peerTitle: String
|
|
if let addressName = peer.addressName, !addressName.isEmpty {
|
|
peerTitle = "@\(addressName)"
|
|
} else {
|
|
peerTitle = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
|
}
|
|
entries.append(GroupInfoEntry.linkedChannelSetup(presentationData.theme, presentationData.strings.Group_LinkedChannel, peerTitle))
|
|
}
|
|
}
|
|
if !isPublic && cachedChannelData.linkedDiscussionPeerId == nil {
|
|
entries.append(GroupInfoEntry.preHistory(presentationData.theme, presentationData.strings.GroupInfo_GroupHistory, cachedChannelData.flags.contains(.preHistoryEnabled) ? presentationData.strings.GroupInfo_GroupHistoryVisible : presentationData.strings.GroupInfo_GroupHistoryHidden))
|
|
}
|
|
}
|
|
}
|
|
|
|
if cachedChannelData.flags.contains(.canSetStickerSet) && canEditGroupInfo {
|
|
entries.append(GroupInfoEntry.stickerPack(presentationData.theme, presentationData.strings.Stickers_GroupStickers, cachedChannelData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone))
|
|
}
|
|
|
|
var canViewAdminsAndBanned = false
|
|
if let channel = view.peers[view.peerId] as? TelegramChannel {
|
|
if let adminRights = channel.adminRights, !adminRights.isEmpty {
|
|
canViewAdminsAndBanned = true
|
|
} else if channel.flags.contains(.isCreator) {
|
|
canViewAdminsAndBanned = true
|
|
}
|
|
}
|
|
|
|
if canViewAdminsAndBanned {
|
|
var activePermissionCount: Int?
|
|
if let defaultBannedRights = channel.defaultBannedRights {
|
|
var count = 0
|
|
for (right, _) in allGroupPermissionList {
|
|
if !defaultBannedRights.flags.contains(right) {
|
|
count += 1
|
|
}
|
|
}
|
|
activePermissionCount = count
|
|
}
|
|
|
|
entries.append(GroupInfoEntry.permissions(presentationData.theme, presentationData.strings.GroupInfo_Permissions, activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList.count)" }) ?? ""))
|
|
entries.append(GroupInfoEntry.administrators(presentationData.theme, presentationData.strings.GroupInfo_Administrators, cachedChannelData.participantsSummary.adminCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""))
|
|
}
|
|
}
|
|
} else {
|
|
if let peer = peerViewMainPeer(view), peer.isScam {
|
|
entries.append(.about(presentationData.theme, presentationData.strings.GroupInfo_ScamGroupWarning))
|
|
}
|
|
else if let cachedChannelData = view.cachedData as? CachedChannelData {
|
|
if let about = cachedChannelData.about, !about.isEmpty {
|
|
entries.append(.about(presentationData.theme, about))
|
|
}
|
|
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
|
if let location = cachedChannelData.peerGeoLocation {
|
|
entries.append(.locationHeader(presentationData.theme, presentationData.strings.GroupInfo_Location.uppercased()))
|
|
entries.append(.location(presentationData.theme, location))
|
|
}
|
|
if let username = peer.username, !username.isEmpty {
|
|
entries.append(.link(presentationData.theme, "t.me/" + username))
|
|
}
|
|
}
|
|
} else if let cachedGroupData = view.cachedData as? CachedGroupData {
|
|
if let about = cachedGroupData.about, !about.isEmpty {
|
|
entries.append(.about(presentationData.theme, about))
|
|
}
|
|
}
|
|
|
|
entries.append(GroupInfoEntry.notifications(presentationData.theme, presentationData.strings.GroupInfo_Notifications, notificationsText))
|
|
entries.append(GroupInfoEntry.sharedMedia(presentationData.theme, presentationData.strings.GroupInfo_SharedMedia))
|
|
}
|
|
|
|
var canRemoveAnyMember = false
|
|
if let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
|
|
for participant in participants.participants {
|
|
if canRemoveParticipant(account: account, isAdmin: canEditMembers, participantId: participant.peerId, invitedBy: participant.invitedBy) {
|
|
canRemoveAnyMember = true
|
|
break
|
|
}
|
|
}
|
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
|
for participant in channelMembers {
|
|
if canRemoveParticipant(account: account, channel: channel, participant: participant.participant) {
|
|
canRemoveAnyMember = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if canAddMembers {
|
|
entries.append(GroupInfoEntry.addMember(presentationData.theme, presentationData.strings.GroupInfo_AddParticipant, editing: state.editingState != nil && canRemoveAnyMember))
|
|
}
|
|
|
|
if let group = view.peers[view.peerId] as? TelegramGroup, let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
|
|
var updatedParticipants = participants.participants
|
|
let existingParticipantIds = Set(updatedParticipants.map { $0.peerId })
|
|
|
|
var peerPresences: [PeerId: PeerPresence] = view.peerPresences
|
|
var peers: [PeerId: Peer] = view.peers
|
|
var disabledPeerIds = state.removingParticipantIds
|
|
|
|
if !state.temporaryParticipants.isEmpty {
|
|
for participant in state.temporaryParticipants {
|
|
if !existingParticipantIds.contains(participant.peer.id) {
|
|
updatedParticipants.append(.member(id: participant.peer.id, invitedBy: account.peerId, invitedAt: participant.timestamp))
|
|
if let presence = participant.presence, peerPresences[participant.peer.id] == nil {
|
|
peerPresences[participant.peer.id] = presence
|
|
}
|
|
if peers[participant.peer.id] == nil {
|
|
peers[participant.peer.id] = participant.peer
|
|
}
|
|
disabledPeerIds.insert(participant.peer.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
let sortedParticipants = updatedParticipants.sorted(by: { lhs, rhs in
|
|
let lhsPresence = peerPresences[lhs.peerId] as? TelegramUserPresence
|
|
let rhsPresence = peerPresences[rhs.peerId] as? TelegramUserPresence
|
|
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
|
if lhsPresence.status < rhsPresence.status {
|
|
return false
|
|
} else if lhsPresence.status > rhsPresence.status {
|
|
return true
|
|
}
|
|
} else if let _ = lhsPresence {
|
|
return true
|
|
} else if let _ = rhsPresence {
|
|
return false
|
|
}
|
|
|
|
switch lhs {
|
|
case .creator:
|
|
return false
|
|
case let .admin(lhsId, _, lhsInvitedAt):
|
|
switch rhs {
|
|
case .creator:
|
|
return true
|
|
case let .admin(rhsId, _, rhsInvitedAt):
|
|
if lhsInvitedAt == rhsInvitedAt {
|
|
return lhsId.id < rhsId.id
|
|
}
|
|
return lhsInvitedAt > rhsInvitedAt
|
|
case let .member(rhsId, _, rhsInvitedAt):
|
|
if lhsInvitedAt == rhsInvitedAt {
|
|
return lhsId.id < rhsId.id
|
|
}
|
|
return lhsInvitedAt > rhsInvitedAt
|
|
}
|
|
case let .member(lhsId, _, lhsInvitedAt):
|
|
switch rhs {
|
|
case .creator:
|
|
return true
|
|
case let .admin(rhsId, _, rhsInvitedAt):
|
|
if lhsInvitedAt == rhsInvitedAt {
|
|
return lhsId.id < rhsId.id
|
|
}
|
|
return lhsInvitedAt > rhsInvitedAt
|
|
case let .member(rhsId, _, rhsInvitedAt):
|
|
if lhsInvitedAt == rhsInvitedAt {
|
|
return lhsId.id < rhsId.id
|
|
}
|
|
return lhsInvitedAt > rhsInvitedAt
|
|
}
|
|
}
|
|
})
|
|
|
|
for i in 0 ..< sortedParticipants.count {
|
|
if let peer = peers[sortedParticipants[i].peerId] {
|
|
let memberStatus: GroupInfoMemberStatus
|
|
let participant: ChannelParticipant
|
|
switch sortedParticipants[i] {
|
|
case .creator:
|
|
participant = .creator(id: sortedParticipants[i].peerId, rank: nil)
|
|
memberStatus = .owner(rank: nil)
|
|
case .admin:
|
|
participant = .member(id: sortedParticipants[i].peerId, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: .groupSpecific), promotedBy: account.peerId, canBeEditedByAccountPeer: true), banInfo: nil, rank: nil)
|
|
memberStatus = .admin(rank: nil)
|
|
case .member:
|
|
participant = .member(id: sortedParticipants[i].peerId, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil)
|
|
memberStatus = .member
|
|
}
|
|
|
|
var canPromote: Bool
|
|
var canRestrict: Bool
|
|
if sortedParticipants[i].peerId == account.peerId {
|
|
canPromote = false
|
|
canRestrict = false
|
|
} else {
|
|
switch group.role {
|
|
case .creator:
|
|
canPromote = true
|
|
canRestrict = true
|
|
case .member:
|
|
canPromote = false
|
|
switch sortedParticipants[i] {
|
|
case .creator, .admin:
|
|
canPromote = false
|
|
canRestrict = false
|
|
case let .member(member):
|
|
if member.invitedBy == account.peerId {
|
|
canRestrict = true
|
|
} else {
|
|
canRestrict = false
|
|
}
|
|
}
|
|
case .admin:
|
|
switch sortedParticipants[i] {
|
|
case .creator, .admin:
|
|
canPromote = false
|
|
canRestrict = false
|
|
case .member:
|
|
canPromote = false
|
|
canRestrict = true
|
|
}
|
|
}
|
|
}
|
|
|
|
var peerActions: [ParticipantRevealAction] = []
|
|
if canPromote {
|
|
peerActions.append(ParticipantRevealAction(type: .neutral, title: presentationData.strings.GroupInfo_ActionPromote, action: .promote))
|
|
}
|
|
if canRestrict {
|
|
peerActions.append(ParticipantRevealAction(type: .warning, title: presentationData.strings.GroupInfo_ActionRestrict, action: .restrict))
|
|
peerActions.append(ParticipantRevealAction(type: .destructive, title: presentationData.strings.Common_Delete, action: .remove))
|
|
}
|
|
|
|
entries.append(GroupInfoEntry.member(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index: i, peerId: peer.id, peer: peer, participant: RenderedChannelParticipant(participant: participant, peer: peer), presence: peerPresences[peer.id], memberStatus: memberStatus, editing: ItemListPeerItemEditing(editable: canRemoveParticipant(account: account, isAdmin: canEditMembers, participantId: peer.id, invitedBy: sortedParticipants[i].invitedBy), editing: state.editingState != nil && canRemoveAnyMember, revealed: state.peerIdWithRevealedOptions == peer.id), revealActions: peerActions, enabled: !disabledPeerIds.contains(peer.id), selectable: peer.id != account.peerId))
|
|
}
|
|
}
|
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel, let cachedChannelData = view.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
|
var updatedParticipants = channelMembers
|
|
let existingParticipantIds = Set(updatedParticipants.map { $0.peer.id })
|
|
|
|
var peerPresences: [PeerId: PeerPresence] = view.peerPresences
|
|
var peers: [PeerId: Peer] = view.peers
|
|
|
|
if !state.temporaryParticipants.isEmpty {
|
|
for participant in state.temporaryParticipants {
|
|
if !existingParticipantIds.contains(participant.peer.id) {
|
|
updatedParticipants.append(RenderedChannelParticipant(participant: ChannelParticipant.member(id: participant.peer.id, invitedAt: participant.timestamp, adminInfo: nil, banInfo: nil, rank: nil), peer: participant.peer))
|
|
if let presence = participant.presence, peerPresences[participant.peer.id] == nil {
|
|
peerPresences[participant.peer.id] = presence
|
|
}
|
|
if peers[participant.peer.id] == nil {
|
|
peers[participant.peer.id] = participant.peer
|
|
}
|
|
//disabledPeerIds.insert(participant.peer.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
let sortedParticipants: [RenderedChannelParticipant]
|
|
if memberCount < 200 {
|
|
sortedParticipants = updatedParticipants.sorted(by: { lhs, rhs in
|
|
let lhsPresence = lhs.presences[lhs.peer.id] as? TelegramUserPresence
|
|
let rhsPresence = rhs.presences[rhs.peer.id] as? TelegramUserPresence
|
|
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
|
if lhsPresence.status < rhsPresence.status {
|
|
return false
|
|
} else if lhsPresence.status > rhsPresence.status {
|
|
return true
|
|
}
|
|
} else if let _ = lhsPresence {
|
|
return true
|
|
} else if let _ = rhsPresence {
|
|
return false
|
|
}
|
|
|
|
switch lhs.participant {
|
|
case .creator:
|
|
return false
|
|
case let .member(lhsId, lhsInvitedAt, _, _, _):
|
|
switch rhs.participant {
|
|
case .creator:
|
|
return true
|
|
case let .member(rhsId, rhsInvitedAt, _, _, _):
|
|
if lhsInvitedAt == rhsInvitedAt {
|
|
return lhsId.id < rhsId.id
|
|
}
|
|
return lhsInvitedAt > rhsInvitedAt
|
|
}
|
|
}
|
|
})
|
|
} else {
|
|
sortedParticipants = updatedParticipants
|
|
}
|
|
|
|
var expanded = state.expandedParticipants
|
|
let participants: [RenderedChannelParticipant]
|
|
if expanded {
|
|
participants = sortedParticipants
|
|
} else {
|
|
if sortedParticipants.count > maxParticipantsDisplayedCollapseLimit {
|
|
participants = Array(sortedParticipants.prefix(Int(maxParticipantsDisplayedLimit)))
|
|
} else {
|
|
participants = sortedParticipants
|
|
expanded = true
|
|
}
|
|
}
|
|
|
|
for i in 0 ..< participants.count {
|
|
let participant = participants[i]
|
|
let memberStatus: GroupInfoMemberStatus
|
|
switch participant.participant {
|
|
case let .creator(_, rank):
|
|
memberStatus = .owner(rank: rank)
|
|
case let .member(_, _, adminInfo, _, rank):
|
|
if adminInfo != nil {
|
|
memberStatus = .admin(rank: rank)
|
|
} else {
|
|
memberStatus = .member
|
|
}
|
|
}
|
|
|
|
var canPromote: Bool
|
|
var canRestrict: Bool
|
|
if participant.peer.id == account.peerId {
|
|
canPromote = false
|
|
canRestrict = false
|
|
} else {
|
|
switch participant.participant {
|
|
case .creator:
|
|
canPromote = false
|
|
canRestrict = false
|
|
case let .member(_, _, adminRights, bannedRights, _):
|
|
if channel.hasPermission(.addAdmins) {
|
|
canPromote = true
|
|
} else {
|
|
canPromote = false
|
|
}
|
|
if channel.hasPermission(.banMembers) {
|
|
canRestrict = true
|
|
} else {
|
|
canRestrict = false
|
|
}
|
|
if canPromote {
|
|
if let bannedRights = bannedRights {
|
|
if bannedRights.restrictedBy != account.peerId && !channel.flags.contains(.isCreator) {
|
|
canPromote = false
|
|
}
|
|
}
|
|
}
|
|
if canRestrict {
|
|
if let adminRights = adminRights {
|
|
if adminRights.promotedBy != account.peerId && !channel.flags.contains(.isCreator) {
|
|
canRestrict = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var peerActions: [ParticipantRevealAction] = []
|
|
if canPromote {
|
|
peerActions.append(ParticipantRevealAction(type: .neutral, title: presentationData.strings.GroupInfo_ActionPromote, action: .promote))
|
|
}
|
|
if canRestrict {
|
|
peerActions.append(ParticipantRevealAction(type: .warning, title: presentationData.strings.GroupInfo_ActionRestrict, action: .restrict))
|
|
peerActions.append(ParticipantRevealAction(type: .destructive, title: presentationData.strings.Common_Delete, action: .remove))
|
|
}
|
|
|
|
entries.append(GroupInfoEntry.member(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, index: i, peerId: participant.peer.id, peer: participant.peer, participant: participant, presence: participant.presences[participant.peer.id], memberStatus: memberStatus, editing: ItemListPeerItemEditing(editable: !peerActions.isEmpty, editing: state.editingState != nil && canRemoveAnyMember, revealed: state.peerIdWithRevealedOptions == participant.peer.id), revealActions: peerActions, enabled: true, selectable: participant.peer.id != account.peerId))
|
|
}
|
|
|
|
if !expanded {
|
|
entries.append(GroupInfoEntry.expand(presentationData.theme, presentationData.strings.GroupInfo_ShowMoreMembers(Int32(memberCount - maxParticipantsDisplayedLimit))))
|
|
}
|
|
}
|
|
|
|
if let group = view.peers[view.peerId] as? TelegramGroup {
|
|
if case .Member = group.membership {
|
|
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
|
|
}
|
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
|
if case .member = channel.participationStatus {
|
|
if channel.flags.contains(.isCreator) {
|
|
if let cachedChannelData = view.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount <= 200 {
|
|
if state.editingState != nil {
|
|
entries.append(.leave(presentationData.theme, presentationData.strings.ChannelInfo_DeleteGroup))
|
|
} else {
|
|
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
|
|
}
|
|
}
|
|
} else {
|
|
entries.append(.leave(presentationData.theme, presentationData.strings.Group_LeaveGroup))
|
|
}
|
|
}
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
private func valuesRequiringUpdate(state: GroupInfoState, view: PeerView) -> (title: String?, description: String?) {
|
|
if let peer = view.peers[view.peerId] as? TelegramGroup {
|
|
var titleValue: String?
|
|
var descriptionValue: String?
|
|
if let editingState = state.editingState {
|
|
if let title = editingState.editingName?.composedTitle, title != peer.title {
|
|
titleValue = title
|
|
}
|
|
if let cachedData = view.cachedData as? CachedGroupData {
|
|
if let about = cachedData.about {
|
|
if about != editingState.editingDescriptionText {
|
|
descriptionValue = editingState.editingDescriptionText
|
|
}
|
|
} else if !editingState.editingDescriptionText.isEmpty {
|
|
descriptionValue = editingState.editingDescriptionText
|
|
}
|
|
}
|
|
}
|
|
return (titleValue, descriptionValue)
|
|
} else if let peer = view.peers[view.peerId] as? TelegramChannel {
|
|
var titleValue: String?
|
|
var descriptionValue: String?
|
|
if let editingState = state.editingState {
|
|
if let title = editingState.editingName?.composedTitle, title != peer.title {
|
|
titleValue = title
|
|
}
|
|
if let cachedData = view.cachedData as? CachedChannelData {
|
|
if let about = cachedData.about {
|
|
if about != editingState.editingDescriptionText {
|
|
descriptionValue = editingState.editingDescriptionText
|
|
}
|
|
} else if !editingState.editingDescriptionText.isEmpty {
|
|
descriptionValue = editingState.editingDescriptionText
|
|
}
|
|
}
|
|
}
|
|
return (titleValue, descriptionValue)
|
|
} else {
|
|
return (nil, nil)
|
|
}
|
|
}
|
|
|
|
public func groupInfoController(context: AccountContext, peerId originalPeerId: PeerId, membersLoaded: @escaping () -> Void = {}) -> ViewController {
|
|
let statePromise = ValuePromise(GroupInfoState(updatingAvatar: nil, editingState: nil, updatingName: nil, peerIdWithRevealedOptions: nil, expandedParticipants: false, temporaryParticipants: [], successfullyAddedParticipantIds: Set(), removingParticipantIds: Set(), savingData: false, searchingMembers: false), ignoreRepeated: true)
|
|
let stateValue = Atomic(value: GroupInfoState(updatingAvatar: nil, editingState: nil, updatingName: nil, peerIdWithRevealedOptions: nil, expandedParticipants: false, temporaryParticipants: [], successfullyAddedParticipantIds: Set(), removingParticipantIds: Set(), savingData: false, searchingMembers: false))
|
|
let updateState: ((GroupInfoState) -> GroupInfoState) -> Void = { f in
|
|
statePromise.set(stateValue.modify { f($0) })
|
|
}
|
|
|
|
var pushControllerImpl: ((ViewController) -> Void)?
|
|
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
|
var endEditingImpl: (() -> Void)?
|
|
var removePeerChatImpl: ((Peer, Bool) -> Void)?
|
|
var errorImpl: (() -> Void)?
|
|
var clearHighlightImpl: (() -> Void)?
|
|
var dismissInputImpl: (() -> Void)?
|
|
|
|
let actionsDisposable = DisposableSet()
|
|
|
|
let updatePeerNameDisposable = MetaDisposable()
|
|
actionsDisposable.add(updatePeerNameDisposable)
|
|
|
|
let updatePeerDescriptionDisposable = MetaDisposable()
|
|
actionsDisposable.add(updatePeerDescriptionDisposable)
|
|
|
|
let addMemberDisposable = MetaDisposable()
|
|
actionsDisposable.add(addMemberDisposable)
|
|
let selectAddMemberDisposable = MetaDisposable()
|
|
actionsDisposable.add(selectAddMemberDisposable)
|
|
|
|
let removeMemberDisposable = MetaDisposable()
|
|
actionsDisposable.add(removeMemberDisposable)
|
|
|
|
let changeMuteSettingsDisposable = MetaDisposable()
|
|
actionsDisposable.add(changeMuteSettingsDisposable)
|
|
|
|
let hiddenAvatarRepresentationDisposable = MetaDisposable()
|
|
actionsDisposable.add(hiddenAvatarRepresentationDisposable)
|
|
|
|
let updateAvatarDisposable = MetaDisposable()
|
|
actionsDisposable.add(updateAvatarDisposable)
|
|
let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
|
|
|
|
let navigateDisposable = MetaDisposable()
|
|
actionsDisposable.add(navigateDisposable)
|
|
|
|
let upgradeDisposable = MetaDisposable()
|
|
actionsDisposable.add(upgradeDisposable)
|
|
|
|
var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)?
|
|
let avatarAndNameInfoContext = ItemListAvatarAndNameInfoItemContext()
|
|
var updateHiddenAvatarImpl: (() -> Void)?
|
|
|
|
var displayCopyContextMenuImpl: ((String, GroupInfoEntryTag) -> Void)?
|
|
var aboutLinkActionImpl: ((TextLinkItemActionType, TextLinkItem) -> Void)?
|
|
|
|
var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)?
|
|
|
|
let actualPeerId = Promise<PeerId>()
|
|
actualPeerId.set(context.account.viewTracker.peerView(originalPeerId)
|
|
|> map { peerView -> PeerId in
|
|
if let peer = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = peer.migrationReference {
|
|
return migrationReference.peerId
|
|
} else {
|
|
return originalPeerId
|
|
}
|
|
}
|
|
|> distinctUntilChanged)
|
|
|
|
let peerView = Promise<PeerView>()
|
|
let peerViewSignal = actualPeerId.get()
|
|
|> distinctUntilChanged
|
|
|> mapToSignal { peerId -> Signal<PeerView, NoError> in
|
|
return context.account.viewTracker.peerView(peerId, updateData: true)
|
|
}
|
|
peerView.set(peerViewSignal)
|
|
|
|
let arguments = GroupInfoArguments(context: context, avatarAndNameInfoContext: avatarAndNameInfoContext, tapAvatarAction: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
guard let peer = peerView.peers[peerView.peerId] else {
|
|
return
|
|
}
|
|
if peer.profileImageRepresentations.isEmpty {
|
|
return
|
|
}
|
|
|
|
let galleryController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { controller, ready in
|
|
|
|
})
|
|
hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in
|
|
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first?.representation
|
|
updateHiddenAvatarImpl?()
|
|
}))
|
|
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in
|
|
return avatarGalleryTransitionArguments?(entry)
|
|
}))
|
|
})
|
|
}, changeProfilePhoto: {
|
|
endEditingImpl?()
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
let _ = (context.account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
|
|
return (transaction.getPeer(peerView.peerId), currentSearchBotsConfiguration(transaction: transaction))
|
|
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
|
legacyController.statusBar.statusBarStyle = .Ignore
|
|
|
|
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
|
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
|
navigationController.setNavigationBarHidden(true, animated: false)
|
|
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
|
|
|
|
legacyController.bind(controller: navigationController)
|
|
|
|
presentControllerImpl?(legacyController, nil)
|
|
|
|
var hasPhotos = false
|
|
if let peer = peer, !peer.profileImageRepresentations.isEmpty {
|
|
hasPhotos = true
|
|
}
|
|
|
|
let completedImpl: (UIImage) -> Void = { image in
|
|
if let data = image.jpegData(compressionQuality: 0.6) {
|
|
let resource = LocalFileMediaResource(fileId: arc4random64())
|
|
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
|
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
|
|
updateState {
|
|
$0.withUpdatedUpdatingAvatar(.image(representation, true))
|
|
}
|
|
updateAvatarDisposable.set((updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerView.peerId, photo: uploadedPeerPhoto(postbox: context.account.postbox, network: context.account.network, resource: resource), mapResourceToAvatarSizes: { resource, representations in
|
|
return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations)
|
|
}) |> deliverOnMainQueue).start(next: { result in
|
|
switch result {
|
|
case .complete:
|
|
updateState {
|
|
$0.withUpdatedUpdatingAvatar(nil)
|
|
}
|
|
case .progress:
|
|
break
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
|
|
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
|
let _ = currentAvatarMixin.swap(mixin)
|
|
mixin.requestSearchController = { assetsController in
|
|
let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { result in
|
|
assetsController?.dismiss()
|
|
completedImpl(result)
|
|
}))
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
}
|
|
mixin.didFinishWithImage = { image in
|
|
if let image = image {
|
|
completedImpl(image)
|
|
}
|
|
}
|
|
mixin.didFinishWithDelete = {
|
|
let _ = currentAvatarMixin.swap(nil)
|
|
updateState {
|
|
if let profileImage = peer?.smallProfileImage {
|
|
return $0.withUpdatedUpdatingAvatar(.image(profileImage, false))
|
|
} else {
|
|
return $0.withUpdatedUpdatingAvatar(.none)
|
|
}
|
|
}
|
|
updateAvatarDisposable.set((updatePeerPhoto(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, accountPeerId: context.account.peerId, peerId: peerView.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in
|
|
return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations)
|
|
}) |> deliverOnMainQueue).start(next: { result in
|
|
switch result {
|
|
case .complete:
|
|
updateState {
|
|
$0.withUpdatedUpdatingAvatar(nil)
|
|
}
|
|
case .progress:
|
|
break
|
|
}
|
|
}))
|
|
}
|
|
mixin.didDismiss = { [weak legacyController] in
|
|
let _ = currentAvatarMixin.swap(nil)
|
|
legacyController?.dismiss()
|
|
}
|
|
let menuController = mixin.present()
|
|
if let menuController = menuController {
|
|
menuController.customRemoveFromParentViewController = { [weak legacyController] in
|
|
legacyController?.dismiss()
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}, pushController: { controller in
|
|
pushControllerImpl?(controller)
|
|
}, presentController: { controller, presentationArguments in
|
|
presentControllerImpl?(controller, presentationArguments)
|
|
}, changeNotificationMuteSettings: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let _ = (context.account.postbox.transaction { transaction -> (TelegramPeerNotificationSettings, GlobalNotificationSettings) in
|
|
let peerSettings: TelegramPeerNotificationSettings = (transaction.getPeerNotificationSettings(peerView.peerId) as? TelegramPeerNotificationSettings) ?? TelegramPeerNotificationSettings.defaultSettings
|
|
let globalSettings: GlobalNotificationSettings = (transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications) as? GlobalNotificationSettings) ?? GlobalNotificationSettings.defaultSettings
|
|
return (peerSettings, globalSettings)
|
|
}
|
|
|> deliverOnMainQueue).start(next: { peerSettings, globalSettings in
|
|
let soundSettings: NotificationSoundSettings?
|
|
if case .default = peerSettings.messageSound {
|
|
soundSettings = NotificationSoundSettings(value: nil)
|
|
} else {
|
|
soundSettings = NotificationSoundSettings(value: peerSettings.messageSound)
|
|
}
|
|
let controller = notificationMuteSettingsController(presentationData: presentationData, notificationSettings: globalSettings.effective.groupChats, soundSettings: soundSettings, openSoundSettings: {
|
|
let controller = notificationSoundSelectionController(context: context, isModal: true, currentSound: peerSettings.messageSound, defaultSound: globalSettings.effective.groupChats.sound, completion: { sound in
|
|
let _ = updatePeerNotificationSoundInteractive(account: context.account, peerId: peerView.peerId, sound: sound).start()
|
|
})
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
}, updateSettings: { value in
|
|
changeMuteSettingsDisposable.set(updatePeerMuteSetting(account: context.account, peerId: peerView.peerId, muteInterval: value).start())
|
|
})
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
})
|
|
})
|
|
}, openPreHistory: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
presentControllerImpl?(groupPreHistorySetupController(context: context, peerId: peerView.peerId, upgradedToSupergroup: { peerId, f in
|
|
upgradedToSupergroupImpl?(peerId, f)
|
|
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
})
|
|
}, openSharedMedia: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
if let controller = context.sharedContext.makePeerSharedMediaController(context: context, peerId: peerView.peerId) {
|
|
pushControllerImpl?(controller)
|
|
}
|
|
})
|
|
}, openAdministrators: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
pushControllerImpl?(channelAdminsController(context: context, peerId: peerView.peerId))
|
|
})
|
|
}, openPermissions: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
pushControllerImpl?(channelPermissionsController(context: context, peerId: peerView.peerId))
|
|
})
|
|
}, updateEditingName: { editingName in
|
|
updateState { state in
|
|
if let editingState = state.editingState {
|
|
return state.withUpdatedEditingState(GroupInfoEditingState(editingName: editingName, editingDescriptionText: editingState.editingDescriptionText))
|
|
} else {
|
|
return state
|
|
}
|
|
}
|
|
}, updateEditingDescriptionText: { text in
|
|
updateState { state in
|
|
if let editingState = state.editingState {
|
|
return state.withUpdatedEditingState(editingState.withUpdatedEditingDescriptionText(text))
|
|
}
|
|
return state
|
|
}
|
|
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
|
updateState { state in
|
|
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
|
return state.withUpdatedPeerIdWithRevealedOptions(peerId)
|
|
} else {
|
|
return state
|
|
}
|
|
}
|
|
}, addMember: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
let members: Promise<[PeerId]> = Promise()
|
|
if peerView.peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
var membersDisposable: Disposable?
|
|
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { listState in
|
|
members.set(.single(listState.list.map {$0.peer.id}))
|
|
membersDisposable?.dispose()
|
|
})
|
|
membersDisposable = disposable
|
|
} else {
|
|
members.set(.single([]))
|
|
}
|
|
|
|
let _ = (combineLatest(queue: .mainQueue(), context.account.postbox.loadedPeerWithId(peerView.peerId)
|
|
|> deliverOnMainQueue, members.get() |> take(1) |> deliverOnMainQueue)).start(next: { groupPeer, recentIds in
|
|
var confirmationImpl: ((PeerId) -> Signal<Bool, NoError>)?
|
|
var options: [ContactListAdditionalOption] = []
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
var inviteByLinkImpl: (() -> Void)?
|
|
|
|
var canCreateInviteLink = false
|
|
if let group = groupPeer as? TelegramGroup {
|
|
switch group.role {
|
|
case .creator, .admin:
|
|
canCreateInviteLink = true
|
|
default:
|
|
break
|
|
}
|
|
} else if let channel = groupPeer as? TelegramChannel {
|
|
if channel.hasPermission(.inviteMembers) {
|
|
if channel.flags.contains(.isCreator) || (channel.adminRights != nil && channel.username == nil) {
|
|
canCreateInviteLink = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if canCreateInviteLink {
|
|
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
|
|
inviteByLinkImpl?()
|
|
}))
|
|
}
|
|
|
|
let contactsController: ViewController
|
|
if peerView.peerId.namespace == Namespaces.Peer.CloudGroup {
|
|
contactsController = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: context, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in
|
|
if let confirmationImpl = confirmationImpl, case let .peer(peer, _, _) = peer {
|
|
return confirmationImpl(peer.id)
|
|
} else {
|
|
return .single(false)
|
|
}
|
|
}))
|
|
} else {
|
|
contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: false, searchGroups: false), options: options, filters: [.excludeSelf, .disable(recentIds)]))
|
|
}
|
|
|
|
confirmationImpl = { [weak contactsController] peerId in
|
|
return context.account.postbox.loadedPeerWithId(peerId)
|
|
|> deliverOnMainQueue
|
|
|> mapToSignal { peer in
|
|
let result = ValuePromise<Bool>()
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
if let contactsController = contactsController {
|
|
let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).0, actions: [
|
|
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {
|
|
result.set(false)
|
|
}),
|
|
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
|
|
result.set(true)
|
|
})
|
|
])
|
|
contactsController.present(alertController, in: .window(.root))
|
|
}
|
|
|
|
return result.get()
|
|
}
|
|
}
|
|
|
|
let addMember: (ContactListPeer) -> Signal<Void, NoError> = { memberPeer -> Signal<Void, NoError> in
|
|
if case let .peer(selectedPeer, _, _) = memberPeer {
|
|
let memberId = selectedPeer.id
|
|
if peerView.peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
return context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: peerView.peerId, memberId: memberId)
|
|
|> map { _ -> Void in
|
|
return Void()
|
|
}
|
|
|> `catch` { _ -> Signal<Void, NoError> in
|
|
return .complete()
|
|
}
|
|
}
|
|
|
|
if let peer = peerView.peers[memberId] {
|
|
updateState { state in
|
|
var found = false
|
|
for participant in state.temporaryParticipants {
|
|
if participant.peer.id == memberId {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
|
var temporaryParticipants = state.temporaryParticipants
|
|
temporaryParticipants.append(TemporaryParticipant(peer: peer, presence: peerView.peerPresences[memberId], timestamp: timestamp))
|
|
return state.withUpdatedTemporaryParticipants(temporaryParticipants)
|
|
} else {
|
|
return state
|
|
}
|
|
}
|
|
}
|
|
|
|
return addGroupMember(account: context.account, peerId: peerView.peerId, memberId: memberId)
|
|
|> deliverOnMainQueue
|
|
|> afterCompleted {
|
|
updateState { state in
|
|
var successfullyAddedParticipantIds = state.successfullyAddedParticipantIds
|
|
successfullyAddedParticipantIds.insert(memberId)
|
|
|
|
return state.withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds)
|
|
}
|
|
}
|
|
|> `catch` { error -> Signal<Void, NoError> in
|
|
switch error {
|
|
case .generic:
|
|
updateState { state in
|
|
var temporaryParticipants = state.temporaryParticipants
|
|
for i in 0 ..< temporaryParticipants.count {
|
|
if temporaryParticipants[i].peer.id == memberId {
|
|
temporaryParticipants.remove(at: i)
|
|
break
|
|
}
|
|
}
|
|
var successfullyAddedParticipantIds = state.successfullyAddedParticipantIds
|
|
successfullyAddedParticipantIds.remove(memberId)
|
|
|
|
return state.withUpdatedTemporaryParticipants(temporaryParticipants).withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds)
|
|
}
|
|
return .complete()
|
|
case .privacy:
|
|
let _ = (context.account.postbox.loadedPeerWithId(memberId)
|
|
|> deliverOnMainQueue).start(next: { peer in
|
|
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
|
})
|
|
|
|
updateState { state in
|
|
var temporaryParticipants = state.temporaryParticipants
|
|
for i in 0 ..< temporaryParticipants.count {
|
|
if temporaryParticipants[i].peer.id == memberId {
|
|
temporaryParticipants.remove(at: i)
|
|
break
|
|
}
|
|
}
|
|
var successfullyAddedParticipantIds = state.successfullyAddedParticipantIds
|
|
successfullyAddedParticipantIds.remove(memberId)
|
|
|
|
return state.withUpdatedTemporaryParticipants(temporaryParticipants).withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds)
|
|
}
|
|
return .complete()
|
|
case .groupFull:
|
|
let signal = convertGroupToSupergroup(account: context.account, peerId: peerView.peerId)
|
|
|> map(Optional.init)
|
|
|> `catch` { _ -> Signal<PeerId?, NoError> in
|
|
return .single(nil)
|
|
}
|
|
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, NoError> in
|
|
guard let upgradedPeerId = upgradedPeerId else {
|
|
return .single(nil)
|
|
}
|
|
return context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: upgradedPeerId, memberId: memberId)
|
|
|> `catch` { _ -> Signal<Never, NoError> in
|
|
return .complete()
|
|
}
|
|
|> mapToSignal { _ -> Signal<PeerId?, NoError> in
|
|
return .complete()
|
|
}
|
|
|> then(.single(upgradedPeerId))
|
|
}
|
|
|> deliverOnMainQueue
|
|
|> mapToSignal { upgradedPeerId -> Signal<Void, NoError> in
|
|
if let upgradedPeerId = upgradedPeerId {
|
|
upgradedToSupergroupImpl?(upgradedPeerId, {})
|
|
}
|
|
return .complete()
|
|
}
|
|
return signal
|
|
}
|
|
}
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
|
|
let addMembers: ([ContactListPeerId]) -> Signal<Void, AddChannelMemberError> = { members -> Signal<Void, AddChannelMemberError> in
|
|
let memberIds = members.compactMap { contact -> PeerId? in
|
|
switch contact {
|
|
case let .peer(peerId):
|
|
return peerId
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
return context.account.postbox.multiplePeersView(memberIds)
|
|
|> take(1)
|
|
|> deliverOnMainQueue
|
|
|> mapError { _ in return .generic}
|
|
|> mapToSignal { view -> Signal<Void, AddChannelMemberError> in
|
|
if memberIds.count == 1 {
|
|
return context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: peerView.peerId, memberId: memberIds[0])
|
|
|> map { _ -> Void in
|
|
return Void()
|
|
}
|
|
} else {
|
|
return context.peerChannelMemberCategoriesContextsManager.addMembers(account: context.account, peerId: peerView.peerId, memberIds: memberIds) |> map { _ in
|
|
updateState { state in
|
|
var state = state
|
|
for (memberId, peer) in view.peers {
|
|
var found = false
|
|
for participant in state.temporaryParticipants {
|
|
if participant.peer.id == memberId {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
|
var temporaryParticipants = state.temporaryParticipants
|
|
temporaryParticipants.append(TemporaryParticipant(peer: peer, presence: view.presences[memberId], timestamp: timestamp))
|
|
state = state.withUpdatedTemporaryParticipants(temporaryParticipants)
|
|
}
|
|
}
|
|
|
|
return state
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
inviteByLinkImpl = { [weak contactsController] in
|
|
contactsController?.dismiss()
|
|
let mode: ChannelVisibilityControllerMode
|
|
if groupPeer.addressName != nil {
|
|
mode = .generic
|
|
} else {
|
|
mode = .privateLink
|
|
}
|
|
presentControllerImpl?(channelVisibilityController(context: context, peerId: peerView.peerId, mode: mode, upgradedToSupergroup: { updatedPeerId, f in
|
|
upgradedToSupergroupImpl?(updatedPeerId, f)
|
|
}), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
|
|
}
|
|
|
|
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
if let contactsController = contactsController as? ContactSelectionController {
|
|
selectAddMemberDisposable.set((contactsController.result
|
|
|> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in
|
|
guard let memberPeer = memberPeer else {
|
|
return
|
|
}
|
|
|
|
contactsController?.displayProgress = true
|
|
addMemberDisposable.set((addMember(memberPeer)
|
|
|> deliverOnMainQueue).start(completed: {
|
|
contactsController?.dismiss()
|
|
}))
|
|
}))
|
|
contactsController.dismissed = {
|
|
selectAddMemberDisposable.set(nil)
|
|
addMemberDisposable.set(nil)
|
|
}
|
|
}
|
|
if let contactsController = contactsController as? ContactMultiselectionController {
|
|
selectAddMemberDisposable.set((contactsController.result
|
|
|> deliverOnMainQueue).start(next: { [weak contactsController] peers in
|
|
contactsController?.displayProgress = true
|
|
addMemberDisposable.set((addMembers(peers)
|
|
|> deliverOnMainQueue).start(error: { error in
|
|
if peers.count == 1, case .restricted = error {
|
|
switch peers[0] {
|
|
case let .peer(peerId):
|
|
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
|
|> deliverOnMainQueue).start(next: { peer in
|
|
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
|
})
|
|
default:
|
|
break
|
|
}
|
|
} else if case .tooMuchJoined = error {
|
|
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
|
}
|
|
|
|
contactsController?.dismiss()
|
|
},completed: {
|
|
contactsController?.dismiss()
|
|
}))
|
|
}))
|
|
contactsController.dismissed = {
|
|
selectAddMemberDisposable.set(nil)
|
|
addMemberDisposable.set(nil)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}, promotePeer: { participant in
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
pushControllerImpl?(channelAdminController(context: context, peerId: peerView.peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
|
|
}, upgradedToSupergroup: { upgradedPeerId, f in
|
|
upgradedToSupergroupImpl?(upgradedPeerId, f)
|
|
}, transferedOwnership: { _ in }))
|
|
})
|
|
}, restrictPeer: { participant in
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
presentControllerImpl?(channelBannedMemberController(context: context, peerId: peerView.peerId, memberId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
|
|
}, upgradedToSupergroup: { upgradedPeerId, f in
|
|
upgradedToSupergroupImpl?(upgradedPeerId, f)
|
|
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
})
|
|
}, removePeer: { memberId in
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
let signal = context.account.postbox.loadedPeerWithId(memberId)
|
|
|> deliverOnMainQueue
|
|
|> mapToSignal { peer -> Signal<Bool, NoError> in
|
|
let result = ValuePromise<Bool>()
|
|
result.set(true)
|
|
return result.get()
|
|
}
|
|
|> mapToSignal { value -> Signal<Void, NoError> in
|
|
if value {
|
|
updateState { state in
|
|
var temporaryParticipants = state.temporaryParticipants
|
|
for i in 0 ..< state.temporaryParticipants.count {
|
|
if state.temporaryParticipants[i].peer.id == memberId {
|
|
temporaryParticipants.remove(at: i)
|
|
break
|
|
}
|
|
}
|
|
var successfullyAddedParticipantIds = state.successfullyAddedParticipantIds
|
|
successfullyAddedParticipantIds.remove(memberId)
|
|
|
|
var removingParticipantIds = state.removingParticipantIds
|
|
removingParticipantIds.insert(memberId)
|
|
|
|
return state.withUpdatedTemporaryParticipants(temporaryParticipants).withUpdatedSuccessfullyAddedParticipantIds(successfullyAddedParticipantIds).withUpdatedRemovingParticipantIds(removingParticipantIds)
|
|
}
|
|
|
|
if peerView.peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: context.account, peerId: peerView.peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
|
|> afterDisposed {
|
|
Queue.mainQueue().async {
|
|
updateState { state in
|
|
var removingParticipantIds = state.removingParticipantIds
|
|
removingParticipantIds.remove(memberId)
|
|
|
|
return state.withUpdatedRemovingParticipantIds(removingParticipantIds)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return removePeerMember(account: context.account, peerId: peerView.peerId, memberId: memberId)
|
|
|> deliverOnMainQueue
|
|
|> afterDisposed {
|
|
updateState { state in
|
|
var removingParticipantIds = state.removingParticipantIds
|
|
removingParticipantIds.remove(memberId)
|
|
|
|
return state.withUpdatedRemovingParticipantIds(removingParticipantIds)
|
|
}
|
|
}
|
|
} else {
|
|
return .complete()
|
|
}
|
|
}
|
|
removeMemberDisposable.set(signal.start())
|
|
})
|
|
}, leave: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
if let channel = peerView.peers[peerView.peerId] as? TelegramChannel, channel.flags.contains(.isCreator), stateValue.with({ $0 }).editingState != nil {
|
|
let controller = ActionSheetController(presentationData: presentationData)
|
|
let dismissAction: () -> Void = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
|
|
var items: [ActionSheetItem] = []
|
|
items.append(ActionSheetTextItem(title: presentationData.strings.ChannelInfo_DeleteGroupConfirmation))
|
|
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_DeleteGroup, color: .destructive, action: {
|
|
dismissAction()
|
|
removePeerChatImpl?(channel, true)
|
|
}))
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: items),
|
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
])
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
} else if let peer = peerView.peers[peerView.peerId] {
|
|
let controller = ActionSheetController(presentationData: presentationData)
|
|
let dismissAction: () -> Void = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
|
|
var items: [ActionSheetItem] = []
|
|
if peerView.peerId.namespace == Namespaces.Peer.CloudGroup {
|
|
items.append(ActionSheetTextItem(title: presentationData.strings.GroupInfo_DeleteAndExitConfirmation))
|
|
}
|
|
items.append(ActionSheetButtonItem(title: presentationData.strings.Group_LeaveGroup, color: .destructive, action: {
|
|
dismissAction()
|
|
removePeerChatImpl?(peer, false)
|
|
}))
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: items),
|
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
])
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
}
|
|
})
|
|
}, displayUsernameShareMenu: { text in
|
|
let shareController = ShareController(context: context, subject: .url(text))
|
|
presentControllerImpl?(shareController, nil)
|
|
}, displayUsernameContextMenu: { text in
|
|
displayCopyContextMenuImpl?(text, .link)
|
|
}, displayAboutContextMenu: { text in
|
|
displayCopyContextMenuImpl?(text, .about)
|
|
}, aboutLinkAction: { action, itemLink in
|
|
aboutLinkActionImpl?(action, itemLink)
|
|
}, openStickerPackSetup: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
let _ = (context.account.postbox.transaction { transaction -> StickerPackCollectionInfo? in
|
|
return (transaction.getPeerCachedData(peerId: peerView.peerId) as? CachedChannelData)?.stickerPack
|
|
}
|
|
|> deliverOnMainQueue).start(next: { stickerPack in
|
|
presentControllerImpl?(groupStickerPackSetupController(context: context, peerId: peerView.peerId, currentPackInfo: stickerPack), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
})
|
|
})
|
|
}, openGroupTypeSetup: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
presentControllerImpl?(channelVisibilityController(context: context, peerId: peerView.peerId, mode: .generic, upgradedToSupergroup: { updatedPeerId, f in
|
|
upgradedToSupergroupImpl?(updatedPeerId, f)
|
|
}), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
|
|
})
|
|
}, openLinkedChannelSetup: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
pushControllerImpl?(channelDiscussionGroupSetupController(context: context, peerId: peerView.peerId))
|
|
})
|
|
}, openLocation: { location in
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
guard let peer = peerView.peers[peerView.peerId] else {
|
|
return
|
|
}
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let mapMedia = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: MapVenue(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil)
|
|
let controller = legacyLocationController(message: nil, mapMedia: mapMedia, context: context, openPeer: { _ in }, sendLiveLocation: { _, _ in }, stopLiveLocation: {}, openUrl: { url in
|
|
context.sharedContext.applicationBindings.openUrl(url)
|
|
})
|
|
pushControllerImpl?(controller)
|
|
})
|
|
}, changeLocation: {
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
guard let peer = peerView.peers[peerView.peerId] else {
|
|
return
|
|
}
|
|
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = legacyLocationPickerController(context: context, selfPeer: peer, peer: peer, sendLocation: { coordinate, _, address in
|
|
let addressSignal: Signal<String, NoError>
|
|
if let address = address {
|
|
addressSignal = .single(address)
|
|
} else {
|
|
addressSignal = reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
|
|> map { placemark in
|
|
if let placemark = placemark {
|
|
return placemark.fullAddress
|
|
} else {
|
|
return "\(coordinate.latitude), \(coordinate.longitude)"
|
|
}
|
|
}
|
|
}
|
|
|
|
let _ = (addressSignal
|
|
|> mapToSignal { address -> Signal<Bool, NoError> in
|
|
return updateChannelGeoLocation(postbox: context.account.postbox, network: context.account.network, channelId: peer.id, coordinate: (coordinate.latitude, coordinate.longitude), address: address)
|
|
}
|
|
|> deliverOnMainQueue).start(error: { errror in
|
|
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
|
})
|
|
}, sendLiveLocation: { _, _ in }, theme: presentationData.theme, customLocationPicker: true, presentationCompleted: {
|
|
clearHighlightImpl?()
|
|
})
|
|
pushControllerImpl?(controller)
|
|
})
|
|
}, displayLocationContextMenu: { text in
|
|
displayCopyContextMenuImpl?(text, .location)
|
|
}, expandParticipants: {
|
|
updateState {
|
|
$0.withUpdatedExpandedParticipants(true)
|
|
}
|
|
})
|
|
|
|
let loadMoreControl = Atomic<(PeerId, PeerChannelMemberCategoryControl)?>(value: nil)
|
|
let channelMembersPromise = Promise<[RenderedChannelParticipant]>()
|
|
|
|
let channelMembersDisposable = MetaDisposable()
|
|
actionsDisposable.add(channelMembersDisposable)
|
|
|
|
var membersLoadedCalled = false
|
|
|
|
actionsDisposable.add((actualPeerId.get()
|
|
|> distinctUntilChanged
|
|
|> deliverOnMainQueue).start(next: { peerId in
|
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
|
let (disposable, control) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
|
|
channelMembersPromise.set(.single(state.list))
|
|
if case .loading(true) = state.loadingState {
|
|
} else if !membersLoadedCalled {
|
|
membersLoadedCalled = true
|
|
membersLoaded()
|
|
}
|
|
})
|
|
if let control = control {
|
|
let _ = loadMoreControl.swap((peerId, control))
|
|
} else {
|
|
let _ = loadMoreControl.swap(nil)
|
|
}
|
|
channelMembersDisposable.set(disposable)
|
|
} else {
|
|
let _ = loadMoreControl.swap(nil)
|
|
channelMembersPromise.set(.single([]))
|
|
channelMembersDisposable.set(nil)
|
|
if !membersLoadedCalled {
|
|
membersLoadedCalled = true
|
|
membersLoaded()
|
|
}
|
|
}
|
|
}))
|
|
|
|
let previousStateValue = Atomic<GroupInfoState?>(value: nil)
|
|
let previousChannelMembers = Atomic<[PeerId]?>(value: nil)
|
|
|
|
let searchContext = GroupMembersSearchContext(context: context, peerId: originalPeerId)
|
|
|
|
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
|
|
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), peerView.get(), context.account.postbox.combinedView(keys: [globalNotificationsKey]), channelMembersPromise.get())
|
|
|> map { presentationData, state, view, combinedView, channelMembers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|
let peer = peerViewMainPeer(view)
|
|
|
|
var globalNotificationSettings: GlobalNotificationSettings = GlobalNotificationSettings.defaultSettings
|
|
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
|
|
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
|
|
globalNotificationSettings = settings
|
|
}
|
|
}
|
|
|
|
var canEditGroupInfo = false
|
|
if let group = view.peers[view.peerId] as? TelegramGroup {
|
|
switch group.role {
|
|
case .admin, .creator:
|
|
canEditGroupInfo = true
|
|
case .member:
|
|
break
|
|
}
|
|
if !group.hasBannedPermission(.banChangeInfo) {
|
|
canEditGroupInfo = true
|
|
}
|
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
|
if channel.hasPermission(.changeInfo) || !(channel.adminRights?.flags ?? []).isEmpty {
|
|
canEditGroupInfo = true
|
|
}
|
|
}
|
|
|
|
var rightNavigationButton: ItemListNavigationButton?
|
|
var secondaryRightNavigationButton: ItemListNavigationButton?
|
|
if let editingState = state.editingState {
|
|
var doneEnabled = true
|
|
if let editingName = editingState.editingName, editingName.isEmpty {
|
|
doneEnabled = false
|
|
}
|
|
if peer is TelegramChannel {
|
|
if (view.cachedData as? CachedChannelData) == nil {
|
|
doneEnabled = false
|
|
}
|
|
}
|
|
|
|
if state.savingData {
|
|
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: doneEnabled, action: {})
|
|
} else {
|
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: doneEnabled, action: {
|
|
var updateValues: (title: String?, description: String?) = (nil, nil)
|
|
var failed = false
|
|
updateState { state in
|
|
updateValues = valuesRequiringUpdate(state: state, view: view)
|
|
if updateValues.0 != nil || updateValues.1 != nil {
|
|
if (updateValues.description?.count ?? 0) > 255 {
|
|
failed = true
|
|
return state
|
|
}
|
|
return state.withUpdatedSavingData(true)
|
|
} else {
|
|
return state.withUpdatedEditingState(nil)
|
|
}
|
|
}
|
|
|
|
guard !failed else {
|
|
errorImpl?()
|
|
return
|
|
}
|
|
|
|
let updateTitle: Signal<Void, Void>
|
|
if let titleValue = updateValues.title {
|
|
updateTitle = updatePeerTitle(account: context.account, peerId: view.peerId, title: titleValue)
|
|
|> mapError { _ in return Void() }
|
|
} else {
|
|
updateTitle = .complete()
|
|
}
|
|
|
|
let updateDescription: Signal<Void, Void>
|
|
if let descriptionValue = updateValues.description {
|
|
updateDescription = updatePeerDescription(account: context.account, peerId: view.peerId, description: descriptionValue.isEmpty ? nil : descriptionValue)
|
|
|> mapError { _ in return Void() }
|
|
} else {
|
|
updateDescription = .complete()
|
|
}
|
|
|
|
let signal = combineLatest(queue: .mainQueue(),
|
|
updateTitle,
|
|
updateDescription
|
|
)
|
|
|
|
updatePeerNameDisposable.set((signal
|
|
|> deliverOnMainQueue).start(error: { _ in
|
|
updateState { state in
|
|
return state.withUpdatedSavingData(false)
|
|
}
|
|
}, completed: {
|
|
updateState { state in
|
|
return state.withUpdatedSavingData(false).withUpdatedEditingState(nil)
|
|
}
|
|
}))
|
|
})
|
|
}
|
|
} else if canEditGroupInfo {
|
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
|
if let peer = peer as? TelegramGroup {
|
|
var text = ""
|
|
if let cachedData = view.cachedData as? CachedGroupData, let about = cachedData.about {
|
|
text = about
|
|
}
|
|
updateState { state in
|
|
return state.withUpdatedEditingState(GroupInfoEditingState(editingName: ItemListAvatarAndNameInfoItemName(peer), editingDescriptionText: text))
|
|
}
|
|
} else if let channel = peer as? TelegramChannel, case .group = channel.info {
|
|
var text = ""
|
|
if let cachedData = view.cachedData as? CachedChannelData, let about = cachedData.about {
|
|
text = about
|
|
}
|
|
updateState { state in
|
|
return state.withUpdatedEditingState(GroupInfoEditingState(editingName: ItemListAvatarAndNameInfoItemName(channel), editingDescriptionText: text))
|
|
}
|
|
}
|
|
})
|
|
if peer is TelegramChannel {
|
|
secondaryRightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
|
|
updateState { state in
|
|
return state.withUpdatedSearchingMembers(true)
|
|
}
|
|
})
|
|
}
|
|
} else {
|
|
if peer is TelegramChannel {
|
|
rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
|
|
updateState { state in
|
|
return state.withUpdatedSearchingMembers(true)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var searchItem: ItemListControllerSearch?
|
|
if state.searchingMembers {
|
|
searchItem = ChannelMembersSearchItem(context: context, peerId: view.peerId, searchContext: searchContext, cancel: {
|
|
updateState { state in
|
|
return state.withUpdatedSearchingMembers(false)
|
|
}
|
|
}, openPeer: { peer, _ in
|
|
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic) {
|
|
arguments.pushController(infoController)
|
|
}
|
|
}, pushController: { c in
|
|
pushControllerImpl?(c)
|
|
}, dismissInput: {
|
|
dismissInputImpl?()
|
|
})
|
|
}
|
|
|
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.GroupInfo_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
|
|
|
let entries = groupInfoEntries(account: context.account, presentationData: presentationData, view: view, channelMembers: channelMembers, globalNotificationSettings: globalNotificationSettings, state: state)
|
|
var memberIds: [PeerId] = []
|
|
for entry in entries {
|
|
switch entry {
|
|
case let .member(member):
|
|
memberIds.append(member.peerId)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
let previousState = previousStateValue.swap(state)
|
|
let previousMembers = previousChannelMembers.swap(memberIds) ?? []
|
|
|
|
var animateChanges = previousMembers.count > memberIds.count || (previousState != nil && (previousState!.editingState != nil) != (state.editingState != nil))
|
|
if presentationData.disableAnimations {
|
|
if Set(memberIds) == Set(previousMembers) && memberIds != previousMembers {
|
|
animateChanges = false
|
|
}
|
|
}
|
|
|
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, searchItem: searchItem, animateChanges: animateChanges)
|
|
|
|
return (controllerState, (listState, arguments))
|
|
}
|
|
|> afterDisposed {
|
|
actionsDisposable.dispose()
|
|
}
|
|
|
|
let controller = ItemListController(context: context, state: signal)
|
|
|
|
pushControllerImpl = { [weak controller] value in
|
|
(controller?.navigationController as? NavigationController)?.pushViewController(value)
|
|
}
|
|
presentControllerImpl = { [weak controller] value, presentationArguments in
|
|
controller?.view.endEditing(true)
|
|
controller?.present(value, in: .window(.root), with: presentationArguments, blockInteraction: true)
|
|
}
|
|
dismissInputImpl = { [weak controller] in
|
|
controller?.view.endEditing(true)
|
|
}
|
|
upgradedToSupergroupImpl = { [weak controller] upgradedPeerId, f in
|
|
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
|
return transaction.getPeer(upgradedPeerId)
|
|
}
|
|
|> deliverOnMainQueue).start(next: { peer in
|
|
guard let controller = controller, let navigationController = controller.navigationController as? NavigationController, let _ = peer else {
|
|
return
|
|
}
|
|
let infoController = groupInfoController(context: context, peerId: upgradedPeerId, membersLoaded: {
|
|
f()
|
|
})
|
|
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(upgradedPeerId), subject: nil, botStart: nil, mode: .standard(previewing: false))
|
|
var viewControllers: [UIViewController] = []
|
|
if let first = navigationController.viewControllers.first {
|
|
viewControllers.append(first)
|
|
}
|
|
viewControllers.append(chatController)
|
|
viewControllers.append(infoController)
|
|
navigationController.setViewControllers(viewControllers, animated: false)
|
|
})
|
|
}
|
|
removePeerChatImpl = { [weak controller] peer, deleteGloballyIfPossible in
|
|
guard let controller = controller, let navigationController = controller.navigationController as? NavigationController else {
|
|
return
|
|
}
|
|
guard let tabController = navigationController.viewControllers.first as? TabBarController else {
|
|
return
|
|
}
|
|
for childController in tabController.controllers {
|
|
if let chatListController = childController as? ChatListController {
|
|
chatListController.maybeAskForPeerChatRemoval(peer: RenderedPeer(peer: peer), deleteGloballyIfPossible: deleteGloballyIfPossible, completion: { [weak navigationController] removed in
|
|
if removed {
|
|
navigationController?.popToRoot(animated: true)
|
|
}
|
|
}, removed: {
|
|
})
|
|
break
|
|
}
|
|
}
|
|
}
|
|
displayCopyContextMenuImpl = { [weak controller] text, tag in
|
|
if let strongController = controller {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
var resultItemNode: ListViewItemNode?
|
|
let _ = strongController.frameForItemNode({ itemNode in
|
|
var itemTag: GroupInfoEntryTag? = nil
|
|
if let itemNode = itemNode as? ItemListMultilineTextItemNode {
|
|
if let tag = itemNode.tag as? GroupInfoEntryTag {
|
|
itemTag = tag
|
|
}
|
|
}
|
|
else if let itemNode = itemNode as? ItemListActionItemNode {
|
|
if let tag = itemNode.tag as? GroupInfoEntryTag {
|
|
itemTag = tag
|
|
}
|
|
}
|
|
else if let itemNode = itemNode as? ItemListAddressItemNode {
|
|
if let tag = itemNode.tag as? GroupInfoEntryTag {
|
|
itemTag = tag
|
|
}
|
|
}
|
|
if itemTag == tag {
|
|
resultItemNode = itemNode
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
if let resultItemNode = resultItemNode {
|
|
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: {
|
|
UIPasteboard.general.string = text
|
|
})])
|
|
strongController.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in
|
|
if let strongController = controller, let resultItemNode = resultItemNode {
|
|
return (resultItemNode, resultItemNode.contentBounds.insetBy(dx: 0.0, dy: -2.0), strongController.displayNode, strongController.view.bounds)
|
|
} else {
|
|
return nil
|
|
}
|
|
}))
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
aboutLinkActionImpl = { [weak context, weak controller] action, itemLink in
|
|
let _ = (peerView.get()
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { peerView in
|
|
if let controller = controller, let context = context {
|
|
context.sharedContext.handleTextLinkAction(context: context, peerId: peerView.peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
|
}
|
|
})
|
|
}
|
|
|
|
avatarGalleryTransitionArguments = { [weak controller] entry in
|
|
if let controller = controller {
|
|
var result: ((ASDisplayNode, () -> (UIView?, UIView?)), CGRect)?
|
|
controller.forEachItemNode { itemNode in
|
|
if let itemNode = itemNode as? ItemListAvatarAndNameInfoItemNode {
|
|
result = itemNode.avatarTransitionNode()
|
|
}
|
|
}
|
|
if let (node, _) = result {
|
|
return GalleryTransitionArguments(transitionNode: node, addToTransitionSurface: { _ in
|
|
})
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
updateHiddenAvatarImpl = { [weak controller] in
|
|
if let controller = controller {
|
|
controller.forEachItemNode { itemNode in
|
|
if let itemNode = itemNode as? ItemListAvatarAndNameInfoItemNode {
|
|
itemNode.updateAvatarHidden()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
endEditingImpl = {
|
|
[weak controller] in
|
|
controller?.view.endEditing(true)
|
|
}
|
|
clearHighlightImpl = { [weak controller] in
|
|
controller?.clearItemNodesHighlight(animated: true)
|
|
}
|
|
|
|
let hapticFeedback = HapticFeedback()
|
|
errorImpl = { [weak controller] in
|
|
hapticFeedback.error()
|
|
controller?.forEachItemNode { itemNode in
|
|
if let itemNode = itemNode as? ItemListMultilineInputItemNode {
|
|
itemNode.animateError()
|
|
}
|
|
}
|
|
}
|
|
|
|
controller.visibleBottomContentOffsetChanged = { offset in
|
|
if let (peerId, loadMoreControl) = loadMoreControl.with({ $0 }), case let .known(value) = offset, value < 40.0 {
|
|
if stateValue.with({ $0 }).expandedParticipants {
|
|
context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: peerId, control: loadMoreControl)
|
|
}
|
|
}
|
|
}
|
|
return controller
|
|
}
|