mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
1197 lines
63 KiB
Swift
1197 lines
63 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import SyncCore
|
|
import LegacyComponents
|
|
import TelegramPresentationData
|
|
import ItemListUI
|
|
import PresentationDataUtils
|
|
import AccountContext
|
|
import TextFormat
|
|
import OverlayStatusController
|
|
import TelegramStringFormatting
|
|
import ShareController
|
|
import AlertUI
|
|
import PresentationDataUtils
|
|
import GalleryUI
|
|
import LegacyUI
|
|
import ItemListAvatarAndNameInfoItem
|
|
import WebSearchUI
|
|
import PeerAvatarGalleryUI
|
|
import NotificationMuteSettingsUI
|
|
import MapResourceToAvatarSizes
|
|
import NotificationSoundSelectionUI
|
|
import Markdown
|
|
|
|
private final class ChannelInfoControllerArguments {
|
|
let account: Account
|
|
let avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext
|
|
let tapAvatarAction: () -> Void
|
|
let changeProfilePhoto: () -> Void
|
|
let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void
|
|
let updateEditingDescriptionText: (String) -> Void
|
|
let openChannelTypeSetup: () -> Void
|
|
let openDiscussionGroupSetup: () -> Void
|
|
let changeNotificationMuteSettings: () -> Void
|
|
let openSharedMedia: () -> Void
|
|
let openStats: () -> Void
|
|
let openAdmins: () -> Void
|
|
let openMembers: () -> Void
|
|
let openBanned: () -> Void
|
|
let reportChannel: () -> Void
|
|
let leaveChannel: () -> Void
|
|
let deleteChannel: () -> Void
|
|
let displayAddressNameContextMenu: (String) -> Void
|
|
let displayContextMenu: (ChannelInfoEntryTag, String) -> Void
|
|
let aboutLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
|
|
let toggleSignatures:(Bool) -> Void
|
|
init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, openChannelTypeSetup: @escaping () -> Void, openDiscussionGroupSetup: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openStats: @escaping () -> Void, openAdmins: @escaping () -> Void, openMembers: @escaping () -> Void, openBanned: @escaping () -> Void, reportChannel: @escaping () -> Void, leaveChannel: @escaping () -> Void, deleteChannel: @escaping () -> Void, displayAddressNameContextMenu: @escaping (String) -> Void, displayContextMenu: @escaping (ChannelInfoEntryTag, String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, toggleSignatures: @escaping(Bool)->Void) {
|
|
self.account = account
|
|
self.avatarAndNameInfoContext = avatarAndNameInfoContext
|
|
self.tapAvatarAction = tapAvatarAction
|
|
self.changeProfilePhoto = changeProfilePhoto
|
|
self.updateEditingName = updateEditingName
|
|
self.updateEditingDescriptionText = updateEditingDescriptionText
|
|
self.openChannelTypeSetup = openChannelTypeSetup
|
|
self.openDiscussionGroupSetup = openDiscussionGroupSetup
|
|
self.changeNotificationMuteSettings = changeNotificationMuteSettings
|
|
self.openSharedMedia = openSharedMedia
|
|
self.openStats = openStats
|
|
self.openAdmins = openAdmins
|
|
self.openMembers = openMembers
|
|
self.openBanned = openBanned
|
|
self.reportChannel = reportChannel
|
|
self.leaveChannel = leaveChannel
|
|
self.deleteChannel = deleteChannel
|
|
self.displayAddressNameContextMenu = displayAddressNameContextMenu
|
|
self.displayContextMenu = displayContextMenu
|
|
self.aboutLinkAction = aboutLinkAction
|
|
self.toggleSignatures = toggleSignatures
|
|
}
|
|
}
|
|
|
|
private enum ChannelInfoSection: ItemListSectionId {
|
|
case info
|
|
case discriptionAndType
|
|
case sharedMediaAndNotifications
|
|
case sign
|
|
case members
|
|
case reportOrLeave
|
|
}
|
|
|
|
private enum ChannelInfoEntryTag {
|
|
case about
|
|
case link
|
|
}
|
|
|
|
private enum ChannelInfoEntry: ItemListNodeEntry {
|
|
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?)
|
|
case about(theme: PresentationTheme, text: String, value: String)
|
|
case addressName(theme: PresentationTheme, text: String, value: String)
|
|
case channelPhotoSetup(theme: PresentationTheme, text: String)
|
|
case channelTypeSetup(theme: PresentationTheme, text: String, value: String)
|
|
case discussionGroupSetup(theme: PresentationTheme, text: String, value: String)
|
|
case discussionGroupSetupInfo(theme: PresentationTheme, text: String)
|
|
case channelDescriptionSetup(theme: PresentationTheme, placeholder: String, value: String)
|
|
case admins(theme: PresentationTheme, text: String, value: String)
|
|
case members(theme: PresentationTheme, text: String, value: String)
|
|
case banned(theme: PresentationTheme, text: String, value: String)
|
|
case notifications(theme: PresentationTheme, text: String, value: String)
|
|
case sharedMedia(theme: PresentationTheme, text: String)
|
|
case stats(theme: PresentationTheme, text: String)
|
|
case signMessages(theme: PresentationTheme, text: String, value: Bool)
|
|
case signInfo(theme: PresentationTheme, text: String)
|
|
case report(theme: PresentationTheme, text: String)
|
|
case leave(theme: PresentationTheme, text: String)
|
|
case deleteChannel(theme: PresentationTheme, text: String)
|
|
|
|
var section: ItemListSectionId {
|
|
switch self {
|
|
case .info, .about, .addressName, .channelPhotoSetup, .channelDescriptionSetup:
|
|
return ChannelInfoSection.info.rawValue
|
|
case .channelTypeSetup, .discussionGroupSetup, .discussionGroupSetupInfo:
|
|
return ChannelInfoSection.discriptionAndType.rawValue
|
|
case .signMessages, .signInfo:
|
|
return ChannelInfoSection.sign.rawValue
|
|
case .admins, .members, .banned:
|
|
return ChannelInfoSection.members.rawValue
|
|
case .sharedMedia, .notifications, .stats:
|
|
return ChannelInfoSection.sharedMediaAndNotifications.rawValue
|
|
case .report, .leave, .deleteChannel:
|
|
return ChannelInfoSection.reportOrLeave.rawValue
|
|
}
|
|
}
|
|
|
|
var stableId: Int32 {
|
|
switch self {
|
|
case .info:
|
|
return 0
|
|
case .channelPhotoSetup:
|
|
return 1
|
|
case .addressName:
|
|
return 2
|
|
case .about:
|
|
return 3
|
|
case .channelDescriptionSetup:
|
|
return 4
|
|
case .channelTypeSetup:
|
|
return 5
|
|
case .discussionGroupSetup:
|
|
return 6
|
|
case .discussionGroupSetupInfo:
|
|
return 7
|
|
case .signMessages:
|
|
return 8
|
|
case .signInfo:
|
|
return 9
|
|
case .admins:
|
|
return 10
|
|
case .members:
|
|
return 11
|
|
case .banned:
|
|
return 12
|
|
case .notifications:
|
|
return 13
|
|
case .sharedMedia:
|
|
return 14
|
|
case .stats:
|
|
return 15
|
|
case .report:
|
|
return 16
|
|
case .leave:
|
|
return 17
|
|
case .deleteChannel:
|
|
return 18
|
|
}
|
|
}
|
|
|
|
static func ==(lhs: ChannelInfoEntry, rhs: ChannelInfoEntry) -> 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 .about(lhsTheme, lhsText, lhsValue):
|
|
if case let .about(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
case let .addressName(lhsTheme, lhsText, lhsValue):
|
|
if case let .addressName(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .channelPhotoSetup(lhsTheme, lhsText):
|
|
if case let .channelPhotoSetup(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .channelTypeSetup(lhsTheme, lhsText, lhsValue):
|
|
if case let .channelTypeSetup(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .discussionGroupSetup(lhsTheme, lhsText, lhsValue):
|
|
if case let .discussionGroupSetup(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .discussionGroupSetupInfo(lhsTheme, lhsText):
|
|
if case let .discussionGroupSetupInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .channelDescriptionSetup(lhsTheme, lhsPlaceholder, lhsValue):
|
|
if case let .channelDescriptionSetup(rhsTheme, rhsPlaceholder, rhsValue) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .admins(lhsTheme, lhsText, lhsValue):
|
|
if case let .admins(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .members(lhsTheme, lhsText, lhsValue):
|
|
if case let .members(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .banned(lhsTheme, lhsText, lhsValue):
|
|
if case let .banned(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .signMessages(lhsTheme, lhsText, lhsValue):
|
|
if case let .signMessages(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .signInfo(lhsTheme, lhsText):
|
|
if case let .signInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, 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 .stats(lhsTheme, lhsText):
|
|
if case let .stats(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .report(lhsTheme, lhsText):
|
|
if case let .report(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 .deleteChannel(lhsTheme, lhsText):
|
|
if case let .deleteChannel(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .notifications(lhsTheme, lhsText, lhsValue):
|
|
if case let .notifications(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
static func <(lhs: ChannelInfoEntry, rhs: ChannelInfoEntry) -> Bool {
|
|
return lhs.stableId < rhs.stableId
|
|
}
|
|
|
|
func item(_ arguments: Any) -> ListViewItem {
|
|
let arguments = arguments as! ChannelInfoControllerArguments
|
|
switch self {
|
|
case let .info(theme, strings, dateTimeFormat, peer, cachedData, state, updatingAvatar):
|
|
return ItemListAvatarAndNameInfoItem(account: arguments.account, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: nil, cachedData: cachedData, state: state, sectionId: self.section, style: .plain, editingNameUpdated: { editingName in
|
|
arguments.updateEditingName(editingName)
|
|
}, avatarTapped: {
|
|
arguments.tapAvatarAction()
|
|
}, context: arguments.avatarAndNameInfoContext, updatingImage: updatingAvatar)
|
|
case let .about(theme, text, value):
|
|
return ItemListTextWithLabelItem(theme: theme, label: text, text: foldMultipleLineBreaks(value), enabledEntityTypes: [.url, .mention, .hashtag], multiline: true, sectionId: self.section, action: nil, longTapAction: {
|
|
arguments.displayContextMenu(ChannelInfoEntryTag.about, value)
|
|
}, linkItemAction: { action, itemLink in
|
|
arguments.aboutLinkAction(action, itemLink)
|
|
}, tag: ChannelInfoEntryTag.about)
|
|
case let .addressName(theme, text, value):
|
|
return ItemListTextWithLabelItem(theme: theme, label: text, text: "https://t.me/\(value)", textColor: .accent, enabledEntityTypes: [], multiline: false, sectionId: self.section, action: {
|
|
arguments.displayAddressNameContextMenu("https://t.me/\(value)")
|
|
}, longTapAction: {
|
|
arguments.displayContextMenu(ChannelInfoEntryTag.link, "https://t.me/\(value)")
|
|
}, tag: ChannelInfoEntryTag.link)
|
|
case let .channelPhotoSetup(theme, text):
|
|
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .plain, action: {
|
|
arguments.changeProfilePhoto()
|
|
})
|
|
case let .channelTypeSetup(theme, text, value):
|
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
|
arguments.openChannelTypeSetup()
|
|
})
|
|
case let .discussionGroupSetup(theme, text, value):
|
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
|
arguments.openDiscussionGroupSetup()
|
|
})
|
|
case let .discussionGroupSetupInfo(theme, text):
|
|
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
|
case let .channelDescriptionSetup(theme, placeholder, value):
|
|
return ItemListMultilineInputItem(theme: theme, text: value, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: 255, display: true), sectionId: self.section, style: .plain, textUpdated: { updatedText in
|
|
arguments.updateEditingDescriptionText(updatedText)
|
|
})
|
|
case let .admins(theme, text, value):
|
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
|
arguments.openAdmins()
|
|
})
|
|
case let .members(theme, text, value):
|
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
|
arguments.openMembers()
|
|
})
|
|
case let .banned(theme, text, value):
|
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
|
arguments.openBanned()
|
|
})
|
|
case let .signMessages(theme, text, value):
|
|
return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .plain, updated: { updated in
|
|
arguments.toggleSignatures(updated)
|
|
})
|
|
case let .signInfo(theme, text):
|
|
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section, style: .plain)
|
|
case let .sharedMedia(theme, text):
|
|
return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .plain, action: {
|
|
arguments.openSharedMedia()
|
|
})
|
|
case let .stats(theme, text):
|
|
return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .plain, action: {
|
|
arguments.openStats()
|
|
})
|
|
case let .notifications(theme, text, value):
|
|
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
|
arguments.changeNotificationMuteSettings()
|
|
})
|
|
case let .report(theme, text):
|
|
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .plain, action: {
|
|
arguments.reportChannel()
|
|
})
|
|
case let .leave(theme, text):
|
|
return ItemListActionItem(theme: theme, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .plain, action: {
|
|
arguments.leaveChannel()
|
|
})
|
|
case let .deleteChannel(theme, text):
|
|
return ItemListActionItem(theme: theme, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .plain, action: {
|
|
arguments.deleteChannel()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct ChannelInfoState: Equatable {
|
|
let updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
|
let editingState: ChannelInfoEditingState?
|
|
let savingData: Bool
|
|
|
|
init(updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?, editingState: ChannelInfoEditingState?, savingData: Bool) {
|
|
self.updatingAvatar = updatingAvatar
|
|
self.editingState = editingState
|
|
self.savingData = savingData
|
|
}
|
|
|
|
init() {
|
|
self.updatingAvatar = nil
|
|
self.editingState = nil
|
|
self.savingData = false
|
|
}
|
|
|
|
static func ==(lhs: ChannelInfoState, rhs: ChannelInfoState) -> Bool {
|
|
if lhs.updatingAvatar != rhs.updatingAvatar {
|
|
return false
|
|
}
|
|
if lhs.editingState != rhs.editingState {
|
|
return false
|
|
}
|
|
if lhs.savingData != rhs.savingData {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func withUpdatedUpdatingAvatar(_ updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?) -> ChannelInfoState {
|
|
return ChannelInfoState(updatingAvatar: updatingAvatar, editingState: self.editingState, savingData: self.savingData)
|
|
}
|
|
|
|
func withUpdatedEditingState(_ editingState: ChannelInfoEditingState?) -> ChannelInfoState {
|
|
return ChannelInfoState(updatingAvatar: self.updatingAvatar, editingState: editingState, savingData: self.savingData)
|
|
}
|
|
|
|
func withUpdatedSavingData(_ savingData: Bool) -> ChannelInfoState {
|
|
return ChannelInfoState(updatingAvatar: self.updatingAvatar, editingState: self.editingState, savingData: savingData)
|
|
}
|
|
}
|
|
|
|
private struct ChannelInfoEditingState: Equatable {
|
|
let editingName: ItemListAvatarAndNameInfoItemName?
|
|
let editingDescriptionText: String
|
|
|
|
func withUpdatedEditingDescriptionText(_ editingDescriptionText: String) -> ChannelInfoEditingState {
|
|
return ChannelInfoEditingState(editingName: self.editingName, editingDescriptionText: editingDescriptionText)
|
|
}
|
|
|
|
static func ==(lhs: ChannelInfoEditingState, rhs: ChannelInfoEditingState) -> Bool {
|
|
if lhs.editingName != rhs.editingName {
|
|
return false
|
|
}
|
|
if lhs.editingDescriptionText != rhs.editingDescriptionText {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
private func channelInfoEntries(account: Account, presentationData: PresentationData, view: PeerView, globalNotificationSettings: GlobalNotificationSettings, state: ChannelInfoState) -> [ChannelInfoEntry] {
|
|
var entries: [ChannelInfoEntry] = []
|
|
|
|
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
|
let canEditChannel = peer.hasPermission(.changeInfo)
|
|
let canEditMembers = peer.hasPermission(.banMembers)
|
|
|
|
let infoState = ItemListAvatarAndNameInfoItemState(editingName: canEditChannel ? state.editingState?.editingName : nil, updatingName: nil)
|
|
entries.append(.info(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer: peer, cachedData: view.cachedData, state: infoState, updatingAvatar: state.updatingAvatar))
|
|
|
|
if let editingState = state.editingState, canEditChannel {
|
|
entries.append(.channelPhotoSetup(theme: presentationData.theme, text: presentationData.strings.Channel_UpdatePhotoItem))
|
|
entries.append(.channelDescriptionSetup(theme: presentationData.theme, placeholder: presentationData.strings.Channel_About_Placeholder, value: editingState.editingDescriptionText))
|
|
}
|
|
|
|
if let _ = state.editingState, peer.flags.contains(.isCreator) {
|
|
let linkText: String
|
|
if let username = peer.username {
|
|
linkText = "@\(username)"
|
|
} else {
|
|
linkText = presentationData.strings.Channel_Setup_TypePrivate
|
|
}
|
|
entries.append(.channelTypeSetup(theme: presentationData.theme, text: presentationData.strings.Channel_TypeSetup_Title, value: linkText))
|
|
|
|
let discussionGroupTitle: String
|
|
if let cachedData = view.cachedData as? CachedChannelData {
|
|
if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
|
|
if let addressName = peer.addressName, !addressName.isEmpty {
|
|
discussionGroupTitle = "@\(addressName)"
|
|
} else {
|
|
discussionGroupTitle = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
|
}
|
|
} else {
|
|
discussionGroupTitle = presentationData.strings.Channel_DiscussionGroupAdd
|
|
}
|
|
} else {
|
|
discussionGroupTitle = "..."
|
|
}
|
|
entries.append(.discussionGroupSetup(theme: presentationData.theme, text: presentationData.strings.Channel_DiscussionGroup, value: discussionGroupTitle))
|
|
entries.append(.discussionGroupSetupInfo(theme: presentationData.theme, text: presentationData.strings.Channel_DiscussionGroupInfo))
|
|
|
|
let messagesShouldHaveSignatures:Bool
|
|
switch peer.info {
|
|
case let .broadcast(info):
|
|
messagesShouldHaveSignatures = info.flags.contains(.messagesShouldHaveSignatures)
|
|
default:
|
|
messagesShouldHaveSignatures = false
|
|
}
|
|
|
|
entries.append(.signMessages(theme: presentationData.theme, text: presentationData.strings.Channel_SignMessages, value: messagesShouldHaveSignatures))
|
|
entries.append(.signInfo(theme: presentationData.theme, text: presentationData.strings.Channel_SignMessages_Help))
|
|
} else {
|
|
if state.editingState == nil || !peer.flags.contains(.isCreator) {
|
|
if let username = peer.username, !username.isEmpty, state.editingState == nil {
|
|
entries.append(.addressName(theme: presentationData.theme, text: presentationData.strings.Channel_LinkItem, value: username))
|
|
}
|
|
}
|
|
|
|
if let _ = state.editingState, let adminRights = peer.adminRights, !adminRights.isEmpty {
|
|
let discussionGroupTitle: String?
|
|
if let cachedData = view.cachedData as? CachedChannelData {
|
|
if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
|
|
if let addressName = peer.addressName, !addressName.isEmpty {
|
|
discussionGroupTitle = "@\(addressName)"
|
|
} else {
|
|
discussionGroupTitle = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
|
}
|
|
} else if canEditChannel {
|
|
discussionGroupTitle = presentationData.strings.Channel_DiscussionGroupAdd
|
|
} else {
|
|
discussionGroupTitle = nil
|
|
}
|
|
} else if canEditChannel {
|
|
discussionGroupTitle = "..."
|
|
} else {
|
|
discussionGroupTitle = nil
|
|
}
|
|
|
|
if let discussionGroupTitle = discussionGroupTitle {
|
|
entries.append(.discussionGroupSetup(theme: presentationData.theme, text: presentationData.strings.Channel_DiscussionGroup, value: discussionGroupTitle))
|
|
if canEditChannel {
|
|
entries.append(.discussionGroupSetupInfo(theme: presentationData.theme, text: presentationData.strings.Channel_DiscussionGroupInfo))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let _ = state.editingState {
|
|
} else {
|
|
if peer.isScam {
|
|
entries.append(.about(theme: presentationData.theme, text: presentationData.strings.Channel_AboutItem, value: presentationData.strings.ChannelInfo_ScamChannelWarning))
|
|
} else if let cachedChannelData = view.cachedData as? CachedChannelData, let about = cachedChannelData.about, !about.isEmpty {
|
|
entries.append(.about(theme: presentationData.theme, text: presentationData.strings.Channel_AboutItem, value: about))
|
|
}
|
|
}
|
|
|
|
if let cachedChannelData = view.cachedData as? CachedChannelData {
|
|
if canEditMembers {
|
|
if peer.adminRights != nil || peer.flags.contains(.isCreator) {
|
|
let adminCount = cachedChannelData.participantsSummary.adminCount ?? 0
|
|
entries.append(.admins(theme: presentationData.theme, text: presentationData.strings.GroupInfo_Administrators, value: "\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")"))
|
|
|
|
let memberCount = cachedChannelData.participantsSummary.memberCount ?? 0
|
|
entries.append(.members(theme: presentationData.theme, text: presentationData.strings.Channel_Info_Subscribers, value: "\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")"))
|
|
|
|
let bannedCount = cachedChannelData.participantsSummary.kickedCount ?? 0
|
|
entries.append(.banned(theme: presentationData.theme, text: presentationData.strings.GroupRemoved_Title, value: "\(bannedCount == 0 ? "" : "\(presentationStringsFormattedNumber(bannedCount, presentationData.dateTimeFormat.groupingSeparator))")"))
|
|
}
|
|
}
|
|
}
|
|
|
|
if state.editingState == nil, let notificationSettings = view.notificationSettings as? TelegramPeerNotificationSettings {
|
|
let notificationsText: String
|
|
if case let .muted(until) = notificationSettings.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 = notificationSettings.messageSound {
|
|
notificationsText = presentationData.strings.UserInfo_NotificationsEnabled
|
|
} else {
|
|
notificationsText = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.channels.sound)
|
|
}
|
|
entries.append(ChannelInfoEntry.notifications(theme: presentationData.theme, text: presentationData.strings.GroupInfo_Notifications, value: notificationsText))
|
|
}
|
|
if state.editingState == nil {
|
|
entries.append(ChannelInfoEntry.sharedMedia(theme: presentationData.theme, text: presentationData.strings.GroupInfo_SharedMedia))
|
|
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.flags.contains(.canViewStats) {
|
|
entries.append(ChannelInfoEntry.stats(theme: presentationData.theme, text: presentationData.strings.ChannelInfo_Stats))
|
|
}
|
|
}
|
|
|
|
if peer.flags.contains(.isCreator) {
|
|
//if state.editingState != nil {
|
|
entries.append(ChannelInfoEntry.deleteChannel(theme: presentationData.theme, text: presentationData.strings.ChannelInfo_DeleteChannel))
|
|
//}
|
|
} else if state.editingState == nil {
|
|
entries.append(ChannelInfoEntry.report(theme: presentationData.theme, text: presentationData.strings.ReportPeer_Report))
|
|
if peer.participationStatus == .member {
|
|
entries.append(ChannelInfoEntry.leave(theme: presentationData.theme, text: presentationData.strings.Channel_LeaveChannel))
|
|
}
|
|
}
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
private func valuesRequiringUpdate(state: ChannelInfoState, view: PeerView) -> (title: String?, description: String?) {
|
|
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 channelInfoController(context: AccountContext, peerId: PeerId) -> ViewController {
|
|
let statePromise = ValuePromise(ChannelInfoState(), ignoreRepeated: true)
|
|
let stateValue = Atomic(value: ChannelInfoState())
|
|
let updateState: ((ChannelInfoState) -> ChannelInfoState) -> Void = { f in
|
|
statePromise.set(stateValue.modify { f($0) })
|
|
}
|
|
|
|
var pushControllerImpl: ((ViewController) -> Void)?
|
|
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
|
var removePeerChatImpl: ((Peer, Bool) -> Void)?
|
|
var endEditingImpl: (() -> Void)?
|
|
var errorImpl: (() -> Void)?
|
|
|
|
let actionsDisposable = DisposableSet()
|
|
|
|
let updatePeerNameDisposable = MetaDisposable()
|
|
actionsDisposable.add(updatePeerNameDisposable)
|
|
|
|
let updatePeerDescriptionDisposable = MetaDisposable()
|
|
actionsDisposable.add(updatePeerDescriptionDisposable)
|
|
|
|
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 statsUrlDisposable = MetaDisposable()
|
|
actionsDisposable.add(statsUrlDisposable)
|
|
|
|
var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)?
|
|
let avatarAndNameInfoContext = ItemListAvatarAndNameInfoItemContext()
|
|
var updateHiddenAvatarImpl: (() -> Void)?
|
|
|
|
var displayContextMenuImpl: ((ChannelInfoEntryTag, String) -> Void)?
|
|
var aboutLinkActionImpl: ((TextLinkItemActionType, TextLinkItem) -> Void)?
|
|
|
|
let arguments = ChannelInfoControllerArguments(account: context.account, avatarAndNameInfoContext: avatarAndNameInfoContext, tapAvatarAction: {
|
|
let _ = (context.account.postbox.loadedPeerWithId(peerId) |> take(1) |> deliverOnMainQueue).start(next: { peer in
|
|
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 _ = (context.account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
|
|
return (transaction.getPeer(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: CGSize(width: 640.0, height: 640.0), 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: 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: 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()
|
|
}
|
|
}
|
|
})
|
|
}, updateEditingName: { editingName in
|
|
updateState { state in
|
|
if let editingState = state.editingState {
|
|
return state.withUpdatedEditingState(ChannelInfoEditingState(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
|
|
}
|
|
}, openChannelTypeSetup: {
|
|
presentControllerImpl?(channelVisibilityController(context: context, peerId: peerId, mode: .generic, upgradedToSupergroup: { _, f in f() }), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
|
|
}, openDiscussionGroupSetup: {
|
|
pushControllerImpl?(channelDiscussionGroupSetupController(context: context, peerId: peerId))
|
|
}, changeNotificationMuteSettings: {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let _ = (context.account.postbox.transaction { transaction -> (TelegramPeerNotificationSettings, GlobalNotificationSettings) in
|
|
let peerSettings: TelegramPeerNotificationSettings = (transaction.getPeerNotificationSettings(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: peerId, sound: sound).start()
|
|
})
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
}, updateSettings: { value in
|
|
changeMuteSettingsDisposable.set(updatePeerMuteSetting(account: context.account, peerId: peerId, muteInterval: value).start())
|
|
})
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
})
|
|
}, openSharedMedia: {
|
|
if let controller = context.sharedContext.makePeerSharedMediaController(context: context, peerId: peerId) {
|
|
pushControllerImpl?(controller)
|
|
}
|
|
}, openStats: {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
var urlSignal = channelStatsUrl(postbox: context.account.postbox, network: context.account.network, peerId: peerId, params: "", darkTheme: presentationData.theme.rootController.keyboardColor.keyboardAppearance == .dark)
|
|
|
|
var cancelImpl: (() -> Void)?
|
|
let progressSignal = Signal<Never, NoError> { subscriber in
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
|
cancelImpl?()
|
|
}))
|
|
presentControllerImpl?(controller, nil)
|
|
return ActionDisposable { [weak controller] in
|
|
Queue.mainQueue().async() {
|
|
controller?.dismiss()
|
|
}
|
|
}
|
|
}
|
|
|> runOn(Queue.mainQueue())
|
|
|> delay(0.05, queue: Queue.mainQueue())
|
|
let progressDisposable = progressSignal.start()
|
|
|
|
urlSignal = urlSignal
|
|
|> afterDisposed {
|
|
Queue.mainQueue().async {
|
|
progressDisposable.dispose()
|
|
}
|
|
}
|
|
cancelImpl = {
|
|
statsUrlDisposable.set(nil)
|
|
}
|
|
|
|
statsUrlDisposable.set((urlSignal
|
|
|> deliverOnMainQueue).start(next: { url in
|
|
pushControllerImpl?(ChannelStatsController(context: context, url: url, peerId: peerId))
|
|
}, error: { _ in
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
|
}))
|
|
}, openAdmins: {
|
|
pushControllerImpl?(channelAdminsController(context: context, peerId: peerId))
|
|
}, openMembers: {
|
|
pushControllerImpl?(channelMembersController(context: context, peerId: peerId))
|
|
}, openBanned: {
|
|
pushControllerImpl?(channelBlacklistController(context: context, peerId: peerId))
|
|
}, reportChannel: {
|
|
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
|
presentControllerImpl?(c, a)
|
|
}, completion: { _ in }), nil)
|
|
}, leaveChannel: {
|
|
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
|
return transaction.getPeer(peerId)
|
|
}
|
|
|> deliverOnMainQueue).start(next: { peer in
|
|
guard let peer = peer else {
|
|
return
|
|
}
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
|
let dismissAction: () -> Void = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: [
|
|
ActionSheetButtonItem(title: presentationData.strings.Channel_LeaveChannel, color: .destructive, action: {
|
|
dismissAction()
|
|
removePeerChatImpl?(peer, false)
|
|
}),
|
|
]),
|
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
])
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
})
|
|
}, deleteChannel: {
|
|
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
|
return transaction.getPeer(peerId)
|
|
}
|
|
|> deliverOnMainQueue).start(next: { peer in
|
|
guard let peer = peer else {
|
|
return
|
|
}
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
|
let dismissAction: () -> Void = { [weak controller] in
|
|
controller?.dismissAnimated()
|
|
}
|
|
controller.setItemGroups([
|
|
ActionSheetItemGroup(items: [
|
|
ActionSheetTextItem(title: presentationData.strings.ChannelInfo_DeleteChannelConfirmation),
|
|
ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_DeleteChannel, color: .destructive, action: {
|
|
dismissAction()
|
|
removePeerChatImpl?(peer, true)
|
|
}),
|
|
]),
|
|
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
|
])
|
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
})
|
|
}, displayAddressNameContextMenu: { text in
|
|
let shareController = ShareController(context: context, subject: .url(text))
|
|
presentControllerImpl?(shareController, nil)
|
|
}, displayContextMenu: { tag, text in
|
|
displayContextMenuImpl?(tag, text)
|
|
}, aboutLinkAction: { action, itemLink in
|
|
aboutLinkActionImpl?(action, itemLink)
|
|
}, toggleSignatures: { enabled in
|
|
actionsDisposable.add(toggleShouldChannelMessagesSignatures(account: context.account, peerId: peerId, enabled: enabled).start())
|
|
})
|
|
|
|
var wasEditing: Bool?
|
|
|
|
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
|
|
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), context.account.viewTracker.peerView(peerId, updateData: true), context.account.postbox.combinedView(keys: [globalNotificationsKey]))
|
|
|> map { presentationData, state, view, combinedView -> (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 canManageChannel = false
|
|
if let peer = peer as? TelegramChannel {
|
|
if peer.flags.contains(.isCreator) {
|
|
canManageChannel = true
|
|
} else if let adminRights = peer.adminRights, !adminRights.isEmpty {
|
|
canManageChannel = true
|
|
}
|
|
}
|
|
|
|
var canEditChannel = false
|
|
var hasSomethingToEdit = false
|
|
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
|
canEditChannel = peer.hasPermission(.changeInfo)
|
|
if canEditChannel {
|
|
hasSomethingToEdit = true
|
|
} else if let adminRights = peer.adminRights, !adminRights.isEmpty {
|
|
if let cachedData = view.cachedData as? CachedChannelData, let _ = cachedData.linkedDiscussionPeerId {
|
|
hasSomethingToEdit = true
|
|
}
|
|
}
|
|
}
|
|
|
|
var leftNavigationButton: ItemListNavigationButton?
|
|
var rightNavigationButton: ItemListNavigationButton?
|
|
if let editingState = state.editingState {
|
|
leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
|
updateState {
|
|
$0.withUpdatedEditingState(nil)
|
|
}
|
|
})
|
|
|
|
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: 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: peerId, description: descriptionValue.isEmpty ? nil : descriptionValue)
|
|
|> mapError { _ in return Void() }
|
|
} else {
|
|
updateDescription = .complete()
|
|
}
|
|
|
|
let signal = combineLatest(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 hasSomethingToEdit {
|
|
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
|
|
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
|
var text = ""
|
|
if let cachedData = view.cachedData as? CachedChannelData, let about = cachedData.about {
|
|
text = about
|
|
}
|
|
updateState { state in
|
|
return state.withUpdatedEditingState(ChannelInfoEditingState(editingName: ItemListAvatarAndNameInfoItemName(channel), editingDescriptionText: text))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
let wasEditingValue = wasEditing
|
|
wasEditing = state.editingState != nil
|
|
|
|
var crossfadeState = false
|
|
if let wasEditingValue = wasEditingValue, wasEditingValue != (state.editingState != nil) {
|
|
crossfadeState = true
|
|
}
|
|
|
|
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.UserInfo_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
|
let listState = ItemListNodeState(entries: channelInfoEntries(account: context.account, presentationData: presentationData, view: view, globalNotificationSettings: globalNotificationSettings, state: state), style: .plain, crossfadeState: crossfadeState, animateChanges: false)
|
|
|
|
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?.present(value, in: .window(.root), with: presentationArguments, blockInteraction: true)
|
|
}
|
|
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] deleted in
|
|
if deleted {
|
|
navigationController?.popToRoot(animated: true)
|
|
}
|
|
}, removed: {
|
|
})
|
|
break
|
|
}
|
|
}
|
|
}
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
displayContextMenuImpl = { [weak controller] tag, text in
|
|
if let strongController = controller {
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
var resultItemNode: ListViewItemNode?
|
|
let _ = strongController.frameForItemNode({ itemNode in
|
|
if let itemNode = itemNode as? ItemListTextWithLabelItemNode {
|
|
if let itemTag = itemNode.tag as? ChannelInfoEntryTag {
|
|
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
|
|
if let controller = controller, let context = context {
|
|
context.sharedContext.handleTextLinkAction(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
|
}
|
|
}
|
|
endEditingImpl = {
|
|
[weak controller] in
|
|
controller?.view.endEditing(true)
|
|
}
|
|
|
|
let hapticFeedback = HapticFeedback()
|
|
errorImpl = { [weak controller] in
|
|
hapticFeedback.error()
|
|
controller?.forEachItemNode { itemNode in
|
|
if let itemNode = itemNode as? ItemListMultilineInputItemNode {
|
|
itemNode.animateError()
|
|
}
|
|
}
|
|
}
|
|
return controller
|
|
}
|