Swiftgram/TelegramUI/GroupInfoEntries.swift
2017-02-11 17:05:23 +03:00

677 lines
27 KiB
Swift

import Foundation
import Postbox
import TelegramCore
import SwiftSignalKit
import Display
private let addMemberPlusIcon = UIImage(bundleImageName: "Peer Info/PeerItemPlusIcon")?.precomposed()
private enum GroupInfoSection: ItemListSectionId {
case info
case about
case sharedMediaAndNotifications
case infoManagement
case memberManagement
case members
case leave
}
enum GroupInfoMemberStatus {
case member
case admin
}
private struct GroupPeerEntryStableId: PeerInfoEntryStableId {
let peerId: PeerId
func isEqual(to: PeerInfoEntryStableId) -> Bool {
if let to = to as? GroupPeerEntryStableId, to.peerId == self.peerId {
return true
} else {
return false
}
}
var hashValue: Int {
return self.peerId.hashValue
}
}
enum GroupInfoEntry: PeerInfoEntry {
case info(peer: Peer?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState)
case setGroupPhoto
case aboutHeader
case about(text: String)
case sharedMedia
case notifications(settings: PeerNotificationSettings?)
case groupTypeSetup(isPublic: Bool)
case groupDescriptionSetup(text: String)
case groupManagementInfoLabel(text: String)
case membersAdmins(count: Int)
case membersBlacklist(count: Int)
case usersHeader
case addMember
case member(index: Int, peerId: PeerId, peer: Peer?, presence: PeerPresence?, memberStatus: GroupInfoMemberStatus)
case leave
var section: ItemListSectionId {
switch self {
case .info, .setGroupPhoto:
return GroupInfoSection.info.rawValue
case .aboutHeader, .about:
return GroupInfoSection.about.rawValue
case .sharedMedia, .notifications:
return GroupInfoSection.sharedMediaAndNotifications.rawValue
case .groupTypeSetup, .groupDescriptionSetup, .groupManagementInfoLabel:
return GroupInfoSection.infoManagement.rawValue
case .membersAdmins, .membersBlacklist:
return GroupInfoSection.memberManagement.rawValue
case .usersHeader, .addMember, .member:
return GroupInfoSection.members.rawValue
case .leave:
return GroupInfoSection.leave.rawValue
}
}
func isEqual(to: PeerInfoEntry) -> Bool {
guard let entry = to as? GroupInfoEntry else {
return false
}
switch self {
case let .info(lhsPeer, lhsCachedData, lhsState):
if case let .info(rhsPeer, rhsCachedData, rhsState) = entry {
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
}
return true
} else {
return false
}
case .setGroupPhoto:
if case .setGroupPhoto = entry {
return true
} else {
return false
}
case .aboutHeader:
if case .aboutHeader = entry {
return true
} else {
return false
}
case let .about(lhsText):
switch entry {
case .about(lhsText):
return true
default:
return false
}
case .sharedMedia:
switch entry {
case .sharedMedia:
return true
default:
return false
}
case let .notifications(lhsSettings):
switch entry {
case let .notifications(rhsSettings):
if let lhsSettings = lhsSettings, let rhsSettings = rhsSettings {
return lhsSettings.isEqual(to: rhsSettings)
} else if (lhsSettings != nil) != (rhsSettings != nil) {
return false
}
return true
default:
return false
}
case let .groupTypeSetup(isPublic):
if case .groupTypeSetup(isPublic) = entry {
return true
} else {
return false
}
case let .groupDescriptionSetup(text):
if case .groupDescriptionSetup(text) = entry {
return true
} else {
return false
}
case let .groupManagementInfoLabel(text):
if case .groupManagementInfoLabel(text) = entry {
return true
} else {
return false
}
case let .membersAdmins(lhsCount):
if case let .membersAdmins(rhsCount) = entry, lhsCount == rhsCount {
return true
} else {
return false
}
case let .membersBlacklist(lhsCount):
if case let .membersBlacklist(rhsCount) = entry, lhsCount == rhsCount {
return true
} else {
return false
}
case .usersHeader:
if case .usersHeader = entry {
return true
} else {
return false
}
case .addMember:
if case .addMember = entry {
return true
} else {
return false
}
case let .member(lhsIndex, lhsPeerId, lhsPeer, lhsPresence, lhsMemberStatus):
if case let .member(rhsIndex, rhsPeerId, rhsPeer, rhsPresence, rhsMemberStatus) = entry {
if lhsIndex != rhsIndex {
return false
}
if lhsMemberStatus != rhsMemberStatus {
return false
}
if lhsPeerId != rhsPeerId {
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 lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
if !lhsPresence.isEqual(to: rhsPresence) {
return false
}
} else if (lhsPresence != nil) != (rhsPresence != nil) {
return false
}
return true
} else {
return false
}
case .leave:
if case .leave = entry {
return true
} else {
return false
}
}
}
var stableId: PeerInfoEntryStableId {
switch self {
case let .member(_, peerId, _, _, _):
return GroupPeerEntryStableId(peerId: peerId)
default:
return IntPeerInfoEntryStableId(value: self.sortIndex)
}
}
private var sortIndex: Int {
switch self {
case .info:
return 0
case .setGroupPhoto:
return 1
case .aboutHeader:
return 2
case .about:
return 3
case .notifications:
return 4
case .sharedMedia:
return 5
case .groupTypeSetup:
return 6
case .groupDescriptionSetup:
return 7
case .groupManagementInfoLabel:
return 8
case .membersAdmins:
return 9
case .membersBlacklist:
return 10
case .usersHeader:
return 11
case .addMember:
return 12
case let .member(index, _, _, _, _):
return 20 + index
case .leave:
return 1000000
}
}
func isOrderedBefore(_ entry: PeerInfoEntry) -> Bool {
guard let other = entry as? GroupInfoEntry else {
return false
}
return self.sortIndex < other.sortIndex
}
func item(account: Account, interaction: PeerInfoControllerInteraction) -> ListViewItem {
switch self {
case let .info(peer, cachedData, state):
return ItemListAvatarAndNameInfoItem(account: account, peer: peer, presence: nil, cachedData: cachedData, state: state, sectionId: self.section, style: .blocks, editingNameUpdated: { editingName in
})
case .setGroupPhoto:
return ItemListActionItem(title: "Set Group Photo", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
})
case let .notifications(settings):
let label: String
if let settings = settings as? TelegramPeerNotificationSettings, case .muted = settings.muteState {
label = "Disabled"
} else {
label = "Enabled"
}
return ItemListDisclosureItem(title: "Notifications", label: label, sectionId: self.section, style: .blocks, action: {
interaction.changeNotificationMuteSettings()
})
case .sharedMedia:
return ItemListDisclosureItem(title: "Shared Media", label: "", sectionId: self.section, style: .blocks, action: {
interaction.openSharedMedia()
})
case .addMember:
return ItemListPeerActionItem(icon: addMemberPlusIcon, title: "Add Member", sectionId: self.section, action: {
})
case let .groupTypeSetup(isPublic):
return ItemListDisclosureItem(title: "Group Type", label: isPublic ? "Public" : "Private", sectionId: self.section, style: .blocks, action: {
})
case let .groupDescriptionSetup(text):
return ItemListMultilineInputItem(text: text, sectionId: self.section, textUpdated: { updatedText in
interaction.updateState { state in
if let state = state as? GroupInfoState, let editingState = state.editingState {
return state.withUpdatedEditingState(editingState.withUpdatedEditingDescriptionText(updatedText))
}
return state
}
}, action: {
})
case let .membersAdmins(count):
return ItemListDisclosureItem(title: "Admins", label: "\(count)", sectionId: self.section, style: .blocks, action: {
})
case let .membersBlacklist(count):
return ItemListDisclosureItem(title: "Blacklist", label: "\(count)", sectionId: self.section, style: .blocks, action: {
})
case let .member(_, _, peer, presence, memberStatus):
let label: String?
switch memberStatus {
case .admin:
label = "admin"
case .member:
label = nil
}
return ItemListPeerItem(account: account, peer: peer, presence: presence, label: label, sectionId: self.section, action: {
if let peer = peer {
interaction.openPeerInfo(peer.id)
}
})
case .leave:
return ItemListActionItem(title: "Delete and Exit", kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
})
default:
preconditionFailure()
}
}
}
private struct GroupInfoState: PeerInfoState {
let editingState: GroupInfoEditingState?
let updatingName: ItemListAvatarAndNameInfoItemName?
func isEqual(to: PeerInfoState) -> Bool {
if let to = to as? GroupInfoState {
if self.editingState != to.editingState {
return false
}
if self.updatingName != to.updatingName {
return false
}
return true
} else {
return false
}
}
func withUpdatedEditingState(_ editingState: GroupInfoEditingState?) -> GroupInfoState {
return GroupInfoState(editingState: editingState, updatingName: self.updatingName)
}
func withUpdatedUpdatingName(_ updatingName: ItemListAvatarAndNameInfoItemName?) -> GroupInfoState {
return GroupInfoState(editingState: self.editingState, updatingName: updatingName)
}
}
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
}
}
func groupInfoEntries(view: PeerView, state: PeerInfoState?) -> PeerInfoEntries {
var entries: [PeerInfoEntry] = []
if let peer = peerViewMainPeer(view) {
let infoState = ItemListAvatarAndNameInfoItemState(editingName: (state as? GroupInfoState)?.editingState?.editingName, updatingName: (state as? GroupInfoState)?.updatingName)
entries.append(GroupInfoEntry.info(peer: peer, cachedData: view.cachedData, state: infoState))
}
var highlightAdmins = false
var canManageGroup = false
if let group = view.peers[view.peerId] as? TelegramGroup {
if group.flags.contains(.adminsEnabled) {
highlightAdmins = true
switch group.role {
case .admin, .creator:
canManageGroup = true
case .member:
break
}
} else {
canManageGroup = true
}
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
highlightAdmins = true
switch channel.role {
case .creator:
canManageGroup = true
case .editor, .moderator, .member:
break
}
}
if canManageGroup {
entries.append(GroupInfoEntry.setGroupPhoto)
}
if let editingState = (state as? GroupInfoState)?.editingState {
if let cachedChannelData = view.cachedData as? CachedChannelData {
entries.append(GroupInfoEntry.groupTypeSetup(isPublic: cachedChannelData.exportedInvitation != nil))
entries.append(GroupInfoEntry.groupDescriptionSetup(text: editingState.editingDescriptionText))
if let adminCount = cachedChannelData.participantsSummary.adminCount {
entries.append(GroupInfoEntry.membersAdmins(count: adminCount))
}
if let bannedCount = cachedChannelData.participantsSummary.bannedCount {
entries.append(GroupInfoEntry.membersBlacklist(count: bannedCount))
}
}
} else {
entries.append(GroupInfoEntry.notifications(settings: view.notificationSettings))
entries.append(GroupInfoEntry.sharedMedia)
}
if canManageGroup {
entries.append(GroupInfoEntry.addMember)
}
if let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
let sortedParticipants = participants.participants.sorted(by: { lhs, rhs in
let lhsPresence = view.peerPresences[lhs.peerId] as? TelegramUserPresence
let rhsPresence = view.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
}
}
return false
})
for i in 0 ..< sortedParticipants.count {
if let peer = view.peers[sortedParticipants[i].peerId] {
let memberStatus: GroupInfoMemberStatus
if highlightAdmins {
switch sortedParticipants[i] {
case .admin, .creator:
memberStatus = .admin
case .member:
memberStatus = .member
}
} else {
memberStatus = .member
}
entries.append(GroupInfoEntry.member(index: i, peerId: peer.id, peer: peer, presence: view.peerPresences[peer.id], memberStatus: memberStatus))
}
}
} else if let cachedChannelData = view.cachedData as? CachedChannelData, let participants = cachedChannelData.topParticipants {
let sortedParticipants = participants.participants.sorted(by: { lhs, rhs in
let lhsPresence = view.peerPresences[lhs.peerId] as? TelegramUserPresence
let rhsPresence = view.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 .moderator(lhsId, _, lhsInvitedAt):
switch rhs {
case .creator:
return true
case let .moderator(rhsId, _, rhsInvitedAt):
if lhsInvitedAt == rhsInvitedAt {
return lhsId.id < rhsId.id
}
return lhsInvitedAt > rhsInvitedAt
case let .editor(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 .editor(lhsId, _, lhsInvitedAt):
switch rhs {
case .creator:
return true
case let .moderator(rhsId, _, rhsInvitedAt):
if lhsInvitedAt == rhsInvitedAt {
return lhsId.id < rhsId.id
}
return lhsInvitedAt > rhsInvitedAt
case let .editor(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 .moderator(rhsId, _, rhsInvitedAt):
if lhsInvitedAt == rhsInvitedAt {
return lhsId.id < rhsId.id
}
return lhsInvitedAt > rhsInvitedAt
case let .editor(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
}
}
return false
})
for i in 0 ..< sortedParticipants.count {
if let peer = view.peers[sortedParticipants[i].peerId] {
let memberStatus: GroupInfoMemberStatus
if highlightAdmins {
switch sortedParticipants[i] {
case .moderator, .editor, .creator:
memberStatus = .admin
case .member:
memberStatus = .member
}
} else {
memberStatus = .member
}
entries.append(GroupInfoEntry.member(index: i, peerId: peer.id, peer: peer, presence: view.peerPresences[peer.id], memberStatus: memberStatus))
}
}
}
if let group = view.peers[view.peerId] as? TelegramGroup {
if case .Member = group.membership {
entries.append(GroupInfoEntry.leave)
}
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
if case .member = channel.participationStatus {
entries.append(GroupInfoEntry.leave)
}
}
var leftNavigationButton: PeerInfoNavigationButton?
var rightNavigationButton: PeerInfoNavigationButton?
if canManageGroup {
if let state = state as? GroupInfoState, let _ = state.editingState {
leftNavigationButton = PeerInfoNavigationButton(title: "Cancel", action: { state in
if state == nil {
return GroupInfoState(editingState: nil, updatingName: nil)
} else if let state = state as? GroupInfoState {
return state.withUpdatedEditingState(nil)
} else {
return state
}
})
rightNavigationButton = PeerInfoNavigationButton(title: "Done", action: { state in
if state == nil {
return GroupInfoState(editingState: nil, updatingName: nil)
} else if let state = state as? GroupInfoState {
return state.withUpdatedEditingState(nil)
} else {
return state
}
})
} else {
var editingName: ItemListAvatarAndNameInfoItemName?
if let peer = peerViewMainPeer(view) {
editingName = ItemListAvatarAndNameInfoItemName(peer.indexName)
}
let editingDescriptionText: String
if let cachedChannelData = view.cachedData as? CachedChannelData, let about = cachedChannelData.about {
editingDescriptionText = about
} else {
editingDescriptionText = ""
}
rightNavigationButton = PeerInfoNavigationButton(title: "Edit", action: { state in
if state == nil {
return GroupInfoState(editingState: GroupInfoEditingState(editingName: editingName, editingDescriptionText: editingDescriptionText), updatingName: nil)
} else if let state = state as? GroupInfoState {
return state.withUpdatedEditingState(GroupInfoEditingState(editingName: editingName, editingDescriptionText: editingDescriptionText))
} else {
return state
}
})
}
}
return PeerInfoEntries(entries: entries, leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton)
}