no message

This commit is contained in:
Peter 2017-02-22 21:32:39 +03:00
parent a0ccb729be
commit 22d81e15c3
21 changed files with 1186 additions and 898 deletions

View File

@ -232,6 +232,7 @@
D099EA291DE76655001AF5A8 /* ManagedVideoNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099EA281DE76655001AF5A8 /* ManagedVideoNode.swift */; };
D099EA2D1DE76782001AF5A8 /* PeerMessageManagedMediaId.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099EA2C1DE76782001AF5A8 /* PeerMessageManagedMediaId.swift */; };
D099EA2F1DE775BB001AF5A8 /* ChatContextResultManagedMediaId.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099EA2E1DE775BB001AF5A8 /* ChatContextResultManagedMediaId.swift */; };
D09AEFD41E5BAF67005C1A8B /* ItemListTextEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09AEFD31E5BAF67005C1A8B /* ItemListTextEmptyStateItem.swift */; };
D0A749971E3AA25200AD786E /* NotificationSoundSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */; };
D0AB0BB11D6718DA002C78E7 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D0AB0BB01D6718DA002C78E7 /* libiconv.tbd */; };
D0AB0BB31D6718EB002C78E7 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D0AB0BB21D6718EB002C78E7 /* libz.tbd */; };
@ -245,7 +246,6 @@
D0B843CF1DA922AD005F29E1 /* PeerInfoEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843CE1DA922AD005F29E1 /* PeerInfoEntries.swift */; };
D0B843D11DA922D7005F29E1 /* UserInfoEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843D01DA922D7005F29E1 /* UserInfoEntries.swift */; };
D0B843D31DA922E3005F29E1 /* ChannelInfoEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843D21DA922E3005F29E1 /* ChannelInfoEntries.swift */; };
D0B843D51DA95427005F29E1 /* GroupInfoEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843D41DA95427005F29E1 /* GroupInfoEntries.swift */; };
D0B843D91DAAAA0C005F29E1 /* ItemListPeerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843D81DAAAA0C005F29E1 /* ItemListPeerItem.swift */; };
D0B843DB1DAAB138005F29E1 /* ItemListPeerActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843DA1DAAB138005F29E1 /* ItemListPeerActionItem.swift */; };
D0B844561DAC3AEE005F29E1 /* PresenceStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */; };
@ -321,6 +321,9 @@
D0DF0C9E1D82141F008AEB01 /* ChatInterfaceInputContexts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C9D1D82141F008AEB01 /* ChatInterfaceInputContexts.swift */; };
D0DF0CA11D821B28008AEB01 /* HashtagChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA01D821B28008AEB01 /* HashtagChatInputPanelItem.swift */; };
D0DF0CA41D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA31D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift */; };
D0E305A51E5B2BFB00D7A3A2 /* ValidateAddressNameInteractive.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E305A41E5B2BFB00D7A3A2 /* ValidateAddressNameInteractive.swift */; };
D0E305AD1E5BA3E700D7A3A2 /* ItemListControllerEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E305AC1E5BA3E700D7A3A2 /* ItemListControllerEmptyStateItem.swift */; };
D0E305AF1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E305AE1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift */; };
D0E35A071DE4803400BC6096 /* VerticalListContextResultsChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A061DE4803400BC6096 /* VerticalListContextResultsChatInputContextPanelNode.swift */; };
D0E35A091DE4804900BC6096 /* VerticalListContextResultsChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A081DE4804900BC6096 /* VerticalListContextResultsChatInputPanelItem.swift */; };
D0E7A1BD1D8C246D00C37A6F /* ChatHistoryListNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E7A1BC1D8C246D00C37A6F /* ChatHistoryListNode.swift */; };
@ -693,6 +696,7 @@
D099EA281DE76655001AF5A8 /* ManagedVideoNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedVideoNode.swift; sourceTree = "<group>"; };
D099EA2C1DE76782001AF5A8 /* PeerMessageManagedMediaId.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerMessageManagedMediaId.swift; sourceTree = "<group>"; };
D099EA2E1DE775BB001AF5A8 /* ChatContextResultManagedMediaId.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatContextResultManagedMediaId.swift; sourceTree = "<group>"; };
D09AEFD31E5BAF67005C1A8B /* ItemListTextEmptyStateItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListTextEmptyStateItem.swift; sourceTree = "<group>"; };
D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationSoundSelection.swift; sourceTree = "<group>"; };
D0AB0BB01D6718DA002C78E7 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; };
D0AB0BB21D6718EB002C78E7 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
@ -708,7 +712,6 @@
D0B843CE1DA922AD005F29E1 /* PeerInfoEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInfoEntries.swift; sourceTree = "<group>"; };
D0B843D01DA922D7005F29E1 /* UserInfoEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfoEntries.swift; sourceTree = "<group>"; };
D0B843D21DA922E3005F29E1 /* ChannelInfoEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelInfoEntries.swift; sourceTree = "<group>"; };
D0B843D41DA95427005F29E1 /* GroupInfoEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupInfoEntries.swift; sourceTree = "<group>"; };
D0B843D81DAAAA0C005F29E1 /* ItemListPeerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListPeerItem.swift; sourceTree = "<group>"; };
D0B843DA1DAAB138005F29E1 /* ItemListPeerActionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListPeerActionItem.swift; sourceTree = "<group>"; };
D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceStrings.swift; sourceTree = "<group>"; };
@ -784,6 +787,9 @@
D0DF0C9D1D82141F008AEB01 /* ChatInterfaceInputContexts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceInputContexts.swift; sourceTree = "<group>"; };
D0DF0CA01D821B28008AEB01 /* HashtagChatInputPanelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HashtagChatInputPanelItem.swift; sourceTree = "<group>"; };
D0DF0CA31D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MentionChatInputContextPanelNode.swift; sourceTree = "<group>"; };
D0E305A41E5B2BFB00D7A3A2 /* ValidateAddressNameInteractive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidateAddressNameInteractive.swift; sourceTree = "<group>"; };
D0E305AC1E5BA3E700D7A3A2 /* ItemListControllerEmptyStateItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListControllerEmptyStateItem.swift; sourceTree = "<group>"; };
D0E305AE1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListLoadingIndicatorEmptyStateItem.swift; sourceTree = "<group>"; };
D0E35A061DE4803400BC6096 /* VerticalListContextResultsChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalListContextResultsChatInputContextPanelNode.swift; sourceTree = "<group>"; };
D0E35A081DE4804900BC6096 /* VerticalListContextResultsChatInputPanelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalListContextResultsChatInputPanelItem.swift; sourceTree = "<group>"; };
D0E7A1BC1D8C246D00C37A6F /* ChatHistoryListNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryListNode.swift; sourceTree = "<group>"; };
@ -981,6 +987,7 @@
children = (
D0E6521D1E3A2305004EEA91 /* Items */,
D01B27981E39144C0022A4C0 /* ItemListController.swift */,
D0E305AC1E5BA3E700D7A3A2 /* ItemListControllerEmptyStateItem.swift */,
D01B27941E38F3BF0022A4C0 /* ItemListControllerNode.swift */,
);
name = "Item List";
@ -1688,6 +1695,8 @@
D08774F71E3DE7BF00A97350 /* ItemListEditableDeleteControlNode.swift */,
D0561DDE1E56FE8200E6B9E9 /* ItemListSingleLineInputItem.swift */,
D0561DE51E57424700E6B9E9 /* ItemListMultilineTextItem.swift */,
D0E305AE1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift */,
D09AEFD31E5BAF67005C1A8B /* ItemListTextEmptyStateItem.swift */,
);
name = Items;
sourceTree = "<group>";
@ -1720,7 +1729,6 @@
children = (
D0B843CE1DA922AD005F29E1 /* PeerInfoEntries.swift */,
D0B843D01DA922D7005F29E1 /* UserInfoEntries.swift */,
D0B843D41DA95427005F29E1 /* GroupInfoEntries.swift */,
D0B843D21DA922E3005F29E1 /* ChannelInfoEntries.swift */,
D0B843CC1DA903BB005F29E1 /* PeerInfoController.swift */,
D0486F091E523C8500091F0C /* GroupInfoController.swift */,
@ -2118,6 +2126,7 @@
D087750B1E3E7B7600A97350 /* PreferencesKeys.swift */,
D01D6BFB1E42AB3C006151C6 /* EmojiUtils.swift */,
D0DA44551E4E7F43005FDCA7 /* ShakeAnimation.swift */,
D0E305A41E5B2BFB00D7A3A2 /* ValidateAddressNameInteractive.swift */,
);
name = Utils;
sourceTree = "<group>";
@ -2444,6 +2453,7 @@
D04BB2B91E44E5E400650E93 /* AuthorizationSequencePhoneEntryControllerNode.swift in Sources */,
D0F69E571D6B8BDA0046BCD6 /* GalleryItem.swift in Sources */,
D003702E1DA43052004308D3 /* ItemListAvatarAndNameItem.swift in Sources */,
D0E305A51E5B2BFB00D7A3A2 /* ValidateAddressNameInteractive.swift in Sources */,
D0D03B1C1DECB0FE00220C46 /* internal.c in Sources */,
D0F69D231D6B87D30046BCD6 /* FFMpegMediaFrameSourceContext.swift in Sources */,
D0F69E8D1D6B8C850046BCD6 /* Localizable.swift in Sources */,
@ -2568,6 +2578,7 @@
D0F69E361D6B8B030046BCD6 /* ChatMessageInteractiveMediaNode.swift in Sources */,
D08775191E3F53FC00A97350 /* ContactMultiselectionController.swift in Sources */,
D04BB2B51E44E58E00650E93 /* AuthorizationSequencePhoneEntryController.swift in Sources */,
D0E305AF1E5BA8E000D7A3A2 /* ItemListLoadingIndicatorEmptyStateItem.swift in Sources */,
D087750C1E3E7B7600A97350 /* PreferencesKeys.swift in Sources */,
D0F69E381D6B8B030046BCD6 /* ChatMessageItemView.swift in Sources */,
D0D268671D78793B00C422DA /* ChatInterfaceStateNavigationButtons.swift in Sources */,
@ -2575,6 +2586,7 @@
D07827BD1E004A3400071108 /* ChatListSearchItemHeader.swift in Sources */,
D0F69E901D6B8C850046BCD6 /* RingByteBuffer.swift in Sources */,
D08774F81E3DE7BF00A97350 /* ItemListEditableDeleteControlNode.swift in Sources */,
D0E305AD1E5BA3E700D7A3A2 /* ItemListControllerEmptyStateItem.swift in Sources */,
D0DF0C981D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift in Sources */,
D0F69E731D6B8C340046BCD6 /* ContactsController.swift in Sources */,
D0F69D261D6B87D30046BCD6 /* MediaManager.swift in Sources */,
@ -2647,7 +2659,6 @@
D0215D521E0423EE001A0B1E /* InstantPageShapeItem.swift in Sources */,
D0561DE81E574C3200E6B9E9 /* ChannelAdminsController.swift in Sources */,
D0F69DE51D6B8A420046BCD6 /* ListControllerSpacerItem.swift in Sources */,
D0B843D51DA95427005F29E1 /* GroupInfoEntries.swift in Sources */,
D0BA6F851D784ECD0034826E /* ChatInterfaceStateInputPanels.swift in Sources */,
D0F69DE11D6B8A420046BCD6 /* ListControllerDisclosureActionItem.swift in Sources */,
D0F69E301D6B8B030046BCD6 /* ChatMessageBubbleContentNode.swift in Sources */,
@ -2721,6 +2732,7 @@
D0F69D2E1D6B87D30046BCD6 /* PeerAvatar.swift in Sources */,
D00C7CD71E3664070080C3D5 /* ItemListMultilineInputItem.swift in Sources */,
D0F69E141D6B8ACF0046BCD6 /* ChatControllerInteraction.swift in Sources */,
D09AEFD41E5BAF67005C1A8B /* ItemListTextEmptyStateItem.swift in Sources */,
D0F69D6D1D6B87D30046BCD6 /* MediaTrackDecodableFrame.swift in Sources */,
D0D03B201DECB0FE00220C46 /* stream.c in Sources */,
);

View File

@ -6,11 +6,21 @@ import TelegramCore
private let addMemberPlusIcon = UIImage(bundleImageName: "Peer Info/PeerItemPlusIcon")?.precomposed()
private struct ChannelAdminsControllerArguments {
private final class ChannelAdminsControllerArguments {
let account: Account
let updateCurrentAdministrationType: () -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let removeAdmin: (PeerId) -> Void
let addAdmin: () -> Void
init(account: Account, updateCurrentAdministrationType: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removeAdmin: @escaping (PeerId) -> Void, addAdmin: @escaping () -> Void) {
self.account = account
self.updateCurrentAdministrationType = updateCurrentAdministrationType
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.removeAdmin = removeAdmin
self.addAdmin = addAdmin
}
}
private enum ChannelAdminsSection: Int32 {
@ -54,7 +64,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
case administrationInfo(String)
case adminsHeader(String)
case adminPeerItem(Int32, RenderedChannelParticipant, ItemListPeerItemEditing)
case adminPeerItem(Int32, RenderedChannelParticipant, ItemListPeerItemEditing, Bool)
case addAdmin(Bool)
case adminsInfo(String)
@ -79,7 +89,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
return .index(3)
case .adminsInfo:
return .index(4)
case let .adminPeerItem(_, participant, _):
case let .adminPeerItem(_, participant, _, _):
return .peer(participant.peer.id)
}
}
@ -104,8 +114,8 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
} else {
return false
}
case let .adminPeerItem(lhsIndex, lhsParticipant, lhsEditing):
if case let .adminPeerItem(rhsIndex, rhsParticipant, rhsEditing) = rhs {
case let .adminPeerItem(lhsIndex, lhsParticipant, lhsEditing, lhsEnabled):
if case let .adminPeerItem(rhsIndex, rhsParticipant, rhsEditing, rhsEnabled) = rhs {
if lhsIndex != rhsIndex {
return false
}
@ -115,6 +125,9 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
if lhsEditing != rhsEditing {
return false
}
if lhsEnabled != rhsEnabled {
return false
}
return true
} else {
return false
@ -152,11 +165,11 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
default:
return true
}
case let .adminPeerItem(index, _, _):
case let .adminPeerItem(index, _, _, _):
switch rhs {
case .administrationType, .administrationInfo, .adminsHeader:
return false
case let .adminPeerItem(rhsIndex, _, _):
case let .adminPeerItem(rhsIndex, _, _, _):
return index < rhsIndex
default:
return true
@ -184,13 +197,13 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
label = "All Members"
}
return ItemListDisclosureItem(title: "Who can add members", label: label, sectionId: self.section, style: .blocks, action: {
arguments.updateCurrentAdministrationType()
})
case let .administrationInfo(text):
return ItemListTextItem(text: text, sectionId: self.section)
case let .adminsHeader(title):
return ItemListSectionHeaderItem(text: title, sectionId: self.section)
case let .adminPeerItem(_, participant, editing):
case let .adminPeerItem(_, participant, editing, enabled):
let peerText: String
switch participant.participant {
case .creator:
@ -198,10 +211,10 @@ private enum ChannelAdminsEntry: ItemListNodeEntry {
default:
peerText = "Moderator"
}
return ItemListPeerItem(account: arguments.account, peer: participant.peer, presence: nil, text: .text(peerText), label: nil, editing: editing, enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in
}, removePeer: { _ in
return ItemListPeerItem(account: arguments.account, peer: participant.peer, presence: nil, text: .text(peerText), label: nil, editing: editing, enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id)
}, removePeer: { peerId in
arguments.removeAdmin(peerId)
})
case let .addAdmin(editing):
return ItemListPeerActionItem(icon: addMemberPlusIcon, title: "Add Admin", sectionId: self.section, editing: editing, action: {
@ -220,25 +233,75 @@ private enum CurrentAdministrationType {
private struct ChannelAdminsControllerState: Equatable {
let selectedType: CurrentAdministrationType?
let editing: Bool
let peerIdWithRevealedOptions: PeerId?
let removingPeerId: PeerId?
let removedPeerIds: Set<PeerId>
let temporaryAdmins: [RenderedChannelParticipant]
init() {
self.selectedType = nil
self.editing = false
self.peerIdWithRevealedOptions = nil
self.removingPeerId = nil
self.removedPeerIds = Set()
self.temporaryAdmins = []
}
init(selectedType: CurrentAdministrationType?) {
init(selectedType: CurrentAdministrationType?, editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, removedPeerIds: Set<PeerId>, temporaryAdmins: [RenderedChannelParticipant]) {
self.selectedType = selectedType
self.editing = editing
self.peerIdWithRevealedOptions = peerIdWithRevealedOptions
self.removingPeerId = removingPeerId
self.removedPeerIds = removedPeerIds
self.temporaryAdmins = temporaryAdmins
}
static func ==(lhs: ChannelAdminsControllerState, rhs: ChannelAdminsControllerState) -> Bool {
if lhs.selectedType != rhs.selectedType {
return false
}
if lhs.editing != rhs.editing {
return false
}
if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions {
return false
}
if lhs.removingPeerId != rhs.removingPeerId {
return false
}
if lhs.removedPeerIds != rhs.removedPeerIds {
return false
}
if lhs.temporaryAdmins != rhs.temporaryAdmins {
return false
}
return true
}
func withUpdatedSelectedType(_ selectedType: CurrentAdministrationType?) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: selectedType)
return ChannelAdminsControllerState(selectedType: selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins)
}
func withUpdatedEditing(_ editing: Bool) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins)
}
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins)
}
func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins)
}
func withUpdatedRemovedPeerIds(_ removedPeerIds: Set<PeerId>) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: removedPeerIds, temporaryAdmins: self.temporaryAdmins)
}
func withUpdatedTemporaryAdmins(_ temporaryAdmins: [RenderedChannelParticipant]) -> ChannelAdminsControllerState {
return ChannelAdminsControllerState(selectedType: self.selectedType, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: temporaryAdmins)
}
}
@ -275,8 +338,20 @@ private func ChannelAdminsControllerEntries(view: PeerView, state: ChannelAdmins
if let participants = participants {
entries.append(.adminsHeader(isGroup ? "GROUP ADMINS" : "CHANNEL ADMINS"))
var combinedParticipants: [RenderedChannelParticipant] = participants
var existingParticipantIds = Set<PeerId>()
for participant in participants {
existingParticipantIds.insert(participant.peer.id)
}
for participant in state.temporaryAdmins {
if !existingParticipantIds.contains(participant.peer.id) {
combinedParticipants.append(participant)
}
}
var index: Int32 = 0
for participant in participants.sorted(by: { lhs, rhs in
for participant in combinedParticipants.sorted(by: { lhs, rhs in
let lhsInvitedAt: Int32
switch lhs.participant {
case .creator:
@ -301,15 +376,17 @@ private func ChannelAdminsControllerEntries(view: PeerView, state: ChannelAdmins
}
return lhsInvitedAt < rhsInvitedAt
}) {
if !state.removedPeerIds.contains(participant.peer.id) {
var editable = true
if case .creator = participant.participant {
editable = false
}
entries.append(.adminPeerItem(index, participant, ItemListPeerItemEditing(editable: editable, editing: false, revealed: false)))
entries.append(.adminPeerItem(index, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), existingParticipantIds.contains(participant.peer.id)))
index += 1
}
}
entries.append(.addAdmin(false))
entries.append(.addAdmin(state.editing))
entries.append(.adminsInfo(isGroup ? "You can add admins to help you manage your group" : "You can add admins to help you manage your channel"))
}
}
@ -317,62 +394,261 @@ private func ChannelAdminsControllerEntries(view: PeerView, state: ChannelAdmins
return entries
}
/*private func effectiveAdministrationType(state: ChannelAdminsControllerState, peer: TelegramChannel) -> CurrentAdministrationType {
let selectedType: CurrentAdministrationType
if let current = state.selectedType {
selectedType = current
} else {
if let addressName = peer.addressName, !addressName.isEmpty {
selectedType = .publicChannel
} else {
selectedType = .privateChannel
}
}
return selectedType
}*/
public func ChannelAdminsController(account: Account, peerId: PeerId) -> ViewController {
public func channelAdminsController(account: Account, peerId: PeerId) -> ViewController {
let statePromise = ValuePromise(ChannelAdminsControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelAdminsControllerState())
let updateState: ((ChannelAdminsControllerState) -> ChannelAdminsControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
let actionsDisposable = DisposableSet()
let updateAdministrationDisposable = MetaDisposable()
actionsDisposable.add(updateAdministrationDisposable)
let removeAdminDisposable = MetaDisposable()
actionsDisposable.add(removeAdminDisposable)
let addAdminDisposable = MetaDisposable()
actionsDisposable.add(addAdminDisposable)
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
let arguments = ChannelAdminsControllerArguments(account: account, updateCurrentAdministrationType: {
let actionSheet = ActionSheetController()
let result = ValuePromise<Bool>()
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: "All Members", color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
result.set(true)
}),
ActionSheetButtonItem(title: "Only Admins", color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
result.set(false)
})
]), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: "Cancel", color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
let updateSignal = result.get()
|> take(1)
|> mapToSignal { value -> Signal<Void, NoError> in
updateState { state in
return state.withUpdatedSelectedType(value ? .everyoneCanAddMembers : .adminsCanAddMembers)
}
return account.postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Void, NoError> in
if let peer = peer as? TelegramChannel, case let .group(info) = peer.info {
var updatedValue: Bool?
if value && !info.flags.contains(.everyMemberCanInviteMembers) {
updatedValue = true
} else if !value && info.flags.contains(.everyMemberCanInviteMembers) {
updatedValue = false
}
if let updatedValue = updatedValue {
return updateGroupManagementType(account: account, peerId: peerId, type: updatedValue ? .unrestricted : .restrictedToAdmins)
} else {
return .complete()
}
} else {
return .complete()
}
}
}
updateAdministrationDisposable.set(updateSignal.start())
presentControllerImpl?(actionSheet, nil)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
return state.withUpdatedPeerIdWithRevealedOptions(peerId)
} else {
return state
}
}
}, removeAdmin: { adminId in
updateState {
return $0.withUpdatedRemovingPeerId(adminId)
}
let applyPeers: Signal<Void, NoError> = adminsPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { peers -> Signal<Void, NoError> in
if let peers = peers {
var updatedPeers = peers
for i in 0 ..< updatedPeers.count {
if updatedPeers[i].peer.id == adminId {
updatedPeers.remove(at: i)
break
}
}
adminsPromise.set(.single(updatedPeers))
}
return .complete()
}
removeAdminDisposable.set((removePeerAdmin(account: account, peerId: peerId, adminId: adminId)
|> then(applyPeers |> mapError { _ -> RemovePeerAdminError in return .generic }) |> deliverOnMainQueue).start(error: { _ in
updateState {
return $0.withUpdatedRemovingPeerId(nil)
}
}, completed: {
updateState { state in
var updatedTemporaryAdmins = state.temporaryAdmins
for i in 0 ..< updatedTemporaryAdmins.count {
if updatedTemporaryAdmins[i].peer.id == adminId {
updatedTemporaryAdmins.remove(at: i)
break
}
}
return state.withUpdatedRemovingPeerId(nil).withUpdatedTemporaryAdmins(updatedTemporaryAdmins)
}
}))
}, addAdmin: {
var confirmationImpl: ((PeerId) -> Signal<Bool, NoError>)?
let contactsController = ContactSelectionController(account: account, title: "Add admin", confirmation: { peerId in
if let confirmationImpl = confirmationImpl {
return confirmationImpl(peerId)
} else {
return .single(false)
}
})
confirmationImpl = { [weak contactsController] peerId in
return account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue
|> mapToSignal { peer in
let result = ValuePromise<Bool>()
if let contactsController = contactsController {
let alertController = standardTextAlertController(title: nil, text: "Add \(peer.displayTitle) as admin?", actions: [
TextAlertAction(type: .genericAction, title: "Cancel", action: {
result.set(false)
}),
TextAlertAction(type: .defaultAction, title: "OK", action: {
result.set(true)
})
])
contactsController.present(alertController, in: .window)
}
return result.get()
}
}
let addAdmin = contactsController.result
|> deliverOnMainQueue
|> mapToSignal { memberId -> Signal<Void, NoError> in
if let memberId = memberId {
return account.postbox.peerView(id: memberId)
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { view -> Signal<Void, NoError> in
if let peer = view.peers[memberId] {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
updateState { state in
var found = false
for participant in state.temporaryAdmins {
if participant.peer.id == memberId {
found = true
break
}
}
var removedPeerIds = state.removedPeerIds
removedPeerIds.remove(memberId)
if !found {
var temporaryAdmins = state.temporaryAdmins
temporaryAdmins.append(RenderedChannelParticipant(participant: ChannelParticipant.moderator(id: peer.id, invitedBy: account.peerId, invitedAt: timestamp), peer: peer))
return state.withUpdatedTemporaryAdmins(temporaryAdmins).withUpdatedRemovedPeerIds(removedPeerIds)
} else {
return state.withUpdatedRemovedPeerIds(removedPeerIds)
}
}
let applyAdmin: Signal<Void, AddPeerAdminError> = adminsPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue
|> mapError { _ -> AddPeerAdminError in return .generic }
|> mapToSignal { admins -> Signal<Void, AddPeerAdminError> in
if let admins = admins {
var updatedAdmins = admins
var found = false
for i in 0 ..< updatedAdmins.count {
if updatedAdmins[i].peer.id == memberId {
found = true
break
}
}
if !found {
updatedAdmins.append(RenderedChannelParticipant(participant: ChannelParticipant.moderator(id: peer.id, invitedBy: account.peerId, invitedAt: timestamp), peer: peer))
adminsPromise.set(.single(updatedAdmins))
}
}
return .complete()
}
return addPeerAdmin(account: account, peerId: peerId, adminId: memberId)
|> deliverOnMainQueue
|> then(applyAdmin)
|> `catch` { _ -> Signal<Void, NoError> in
updateState { state in
var temporaryAdmins = state.temporaryAdmins
for i in 0 ..< temporaryAdmins.count {
if temporaryAdmins[i].peer.id == memberId {
temporaryAdmins.remove(at: i)
break
}
}
return state.withUpdatedTemporaryAdmins(temporaryAdmins)
}
return .complete()
}
} else {
return .complete()
}
}
} else {
return .complete()
}
}
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
addAdminDisposable.set(addAdmin.start())
})
let peerView = account.viewTracker.peerView(peerId)
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
let peerView = account.viewTracker.peerView(peerId) |> deliverOnMainQueue
let adminsSignal: Signal<[RenderedChannelParticipant]?, NoError> = .single(nil) |> then(channelAdmins(account: account, peerId: peerId) |> map { Optional($0) })
adminsPromise.set(adminsSignal)
let signal = combineLatest(statePromise.get(), peerView, adminsPromise.get())
let signal = combineLatest(statePromise.get(), peerView, adminsPromise.get() |> deliverOnMainQueue)
|> deliverOnMainQueue
|> map { state, view, admins -> (ItemListControllerState, (ItemListNodeState<ChannelAdminsEntry>, ChannelAdminsEntry.ItemGenerationArguments)) in
let peer = peerViewMainPeer(view)
var rightNavigationButton: ItemListNavigationButton?
if let admins = admins, admins.count > 1 {
if state.editing {
rightNavigationButton = ItemListNavigationButton(title: "Done", style: .bold, enabled: true, action: {
updateState { state in
return state.withUpdatedEditing(false)
}
})
} else {
rightNavigationButton = ItemListNavigationButton(title: "Edit", style: .regular, enabled: true, action: {
updateState { state in
return state
return state.withUpdatedEditing(true)
}
})
}
}
let controllerState = ItemListControllerState(title: "Admins", leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, animateChanges: true)
let listState = ItemListNodeState(entries: ChannelAdminsControllerEntries(view: view, state: state, participants: admins), style: .blocks, animateChanges: false)
let listState = ItemListNodeState(entries: ChannelAdminsControllerEntries(view: view, state: state, participants: admins), style: .blocks, animateChanges: true)
return (controllerState, (listState, arguments))
} |> afterDisposed {
@ -380,5 +656,10 @@ public func ChannelAdminsController(account: Account, peerId: PeerId) -> ViewCon
}
let controller = ItemListController(signal)
presentControllerImpl = { [weak controller] c, p in
if let controller = controller {
controller.present(c, in: .window, with: p)
}
}
return controller
}

View File

@ -1,2 +1,307 @@
import Foundation
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
private final class ChannelBlacklistControllerArguments {
let account: Account
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let removePeer: (PeerId) -> Void
init(account: Account, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void) {
self.account = account
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.removePeer = removePeer
}
}
private enum ChannelBlacklistSection: Int32 {
case peers
}
private enum ChannelBlacklistEntryStableId: Hashable {
case peer(PeerId)
var hashValue: Int {
switch self {
case let .peer(peerId):
return peerId.hashValue
}
}
static func ==(lhs: ChannelBlacklistEntryStableId, rhs: ChannelBlacklistEntryStableId) -> Bool {
switch lhs {
case let .peer(peerId):
if case .peer(peerId) = rhs {
return true
} else {
return false
}
}
}
}
private enum ChannelBlacklistEntry: ItemListNodeEntry {
case peerItem(Int32, RenderedChannelParticipant, ItemListPeerItemEditing, Bool)
var section: ItemListSectionId {
switch self {
case .peerItem:
return ChannelBlacklistSection.peers.rawValue
}
}
var stableId: ChannelBlacklistEntryStableId {
switch self {
case let .peerItem(_, participant, _, _):
return .peer(participant.peer.id)
}
}
static func ==(lhs: ChannelBlacklistEntry, rhs: ChannelBlacklistEntry) -> Bool {
switch lhs {
case let .peerItem(lhsIndex, lhsParticipant, lhsEditing, lhsEnabled):
if case let .peerItem(rhsIndex, rhsParticipant, rhsEditing, rhsEnabled) = rhs {
if lhsIndex != rhsIndex {
return false
}
if lhsParticipant != rhsParticipant {
return false
}
if lhsEditing != rhsEditing {
return false
}
if lhsEnabled != rhsEnabled {
return false
}
return true
} else {
return false
}
}
}
static func <(lhs: ChannelBlacklistEntry, rhs: ChannelBlacklistEntry) -> Bool {
switch lhs {
case let .peerItem(index, _, _, _):
switch rhs {
case let .peerItem(rhsIndex, _, _, _):
return index < rhsIndex
}
}
}
func item(_ arguments: ChannelBlacklistControllerArguments) -> ListViewItem {
switch self {
case let .peerItem(_, participant, editing, enabled):
return ItemListPeerItem(account: arguments.account, peer: participant.peer, presence: nil, text: .none, label: nil, editing: editing, enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id)
}, removePeer: { peerId in
arguments.removePeer(peerId)
})
}
}
}
private struct ChannelBlacklistControllerState: Equatable {
let editing: Bool
let peerIdWithRevealedOptions: PeerId?
let removingPeerId: PeerId?
init() {
self.editing = false
self.peerIdWithRevealedOptions = nil
self.removingPeerId = nil
}
init(editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?) {
self.editing = editing
self.peerIdWithRevealedOptions = peerIdWithRevealedOptions
self.removingPeerId = removingPeerId
}
static func ==(lhs: ChannelBlacklistControllerState, rhs: ChannelBlacklistControllerState) -> Bool {
if lhs.editing != rhs.editing {
return false
}
if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions {
return false
}
if lhs.removingPeerId != rhs.removingPeerId {
return false
}
return true
}
func withUpdatedEditing(_ editing: Bool) -> ChannelBlacklistControllerState {
return ChannelBlacklistControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId)
}
func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelBlacklistControllerState {
return ChannelBlacklistControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId)
}
func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelBlacklistControllerState {
return ChannelBlacklistControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId)
}
}
private func channelBlacklistControllerEntries(view: PeerView, state: ChannelBlacklistControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelBlacklistEntry] {
var entries: [ChannelBlacklistEntry] = []
if let participants = participants {
var index: Int32 = 0
for participant in participants.sorted(by: { lhs, rhs in
let lhsInvitedAt: Int32
switch lhs.participant {
case .creator:
lhsInvitedAt = Int32.min
case let .editor(_, _, invitedAt):
lhsInvitedAt = invitedAt
case let .moderator(_, _, invitedAt):
lhsInvitedAt = invitedAt
case let .member(_, invitedAt):
lhsInvitedAt = invitedAt
}
let rhsInvitedAt: Int32
switch rhs.participant {
case .creator:
rhsInvitedAt = Int32.min
case let .editor(_, _, invitedAt):
rhsInvitedAt = invitedAt
case let .moderator(_, _, invitedAt):
rhsInvitedAt = invitedAt
case let .member(_, invitedAt):
rhsInvitedAt = invitedAt
}
return lhsInvitedAt < rhsInvitedAt
}) {
var editable = true
if case .creator = participant.participant {
editable = false
}
entries.append(.peerItem(index, participant, ItemListPeerItemEditing(editable: editable, editing: state.editing, revealed: participant.peer.id == state.peerIdWithRevealedOptions), state.removingPeerId != participant.peer.id))
index += 1
}
}
return entries
}
public func channelBlacklistController(account: Account, peerId: PeerId) -> ViewController {
let statePromise = ValuePromise(ChannelBlacklistControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelBlacklistControllerState())
let updateState: ((ChannelBlacklistControllerState) -> ChannelBlacklistControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
let actionsDisposable = DisposableSet()
let updateAdministrationDisposable = MetaDisposable()
actionsDisposable.add(updateAdministrationDisposable)
let removePeerDisposable = MetaDisposable()
actionsDisposable.add(removePeerDisposable)
let peersPromise = Promise<[RenderedChannelParticipant]?>(nil)
let arguments = ChannelBlacklistControllerArguments(account: account, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
return state.withUpdatedPeerIdWithRevealedOptions(peerId)
} else {
return state
}
}
}, removePeer: { memberId in
updateState {
return $0.withUpdatedRemovingPeerId(memberId)
}
let applyPeers: Signal<Void, NoError> = peersPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { peers -> Signal<Void, NoError> in
if let peers = peers {
var updatedPeers = peers
for i in 0 ..< updatedPeers.count {
if updatedPeers[i].peer.id == memberId {
updatedPeers.remove(at: i)
break
}
}
peersPromise.set(.single(updatedPeers))
}
return .complete()
}
removePeerDisposable.set((removeChannelBlacklistedPeer(account: account, peerId: peerId, memberId: memberId) |> then(applyPeers) |> deliverOnMainQueue).start(error: { _ in
updateState {
return $0.withUpdatedRemovingPeerId(nil)
}
}, completed: {
updateState {
return $0.withUpdatedRemovingPeerId(nil)
}
}))
})
let peerView = account.viewTracker.peerView(peerId)
let peersSignal: Signal<[RenderedChannelParticipant]?, NoError> = .single(nil) |> then(channelBlacklist(account: account, peerId: peerId) |> map { Optional($0) })
peersPromise.set(peersSignal)
let signal = combineLatest(statePromise.get(), peerView, peersPromise.get())
|> deliverOnMainQueue
|> map { state, view, peers -> (ItemListControllerState, (ItemListNodeState<ChannelBlacklistEntry>, ChannelBlacklistEntry.ItemGenerationArguments)) in
var rightNavigationButton: ItemListNavigationButton?
if let peers = peers, !peers.isEmpty {
if state.editing {
rightNavigationButton = ItemListNavigationButton(title: "Done", style: .bold, enabled: true, action: {
updateState { state in
return state.withUpdatedEditing(false)
}
})
} else {
rightNavigationButton = ItemListNavigationButton(title: "Edit", style: .regular, enabled: true, action: {
updateState { state in
return state.withUpdatedEditing(true)
}
})
}
}
var emptyStateItem: ItemListControllerEmptyStateItem?
if let peers = peers {
if peers.isEmpty {
emptyStateItem = ItemListTextEmptyStateItem(text: "Blacklisted users are removed from the group and can only come back if invited by an admin. Invite links don't work for them.")
}
} else if peers == nil {
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem()
}
let controllerState = ItemListControllerState(title: "Blacklist", leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, animateChanges: true)
let listState = ItemListNodeState(entries: channelBlacklistControllerEntries(view: view, state: state, participants: peers), style: .blocks, emptyStateItem: emptyStateItem, animateChanges: true)
return (controllerState, (listState, arguments))
} |> afterDisposed {
actionsDisposable.dispose()
}
let controller = ItemListController(signal)
presentControllerImpl = { [weak controller] c, p in
if let controller = controller {
controller.present(c, in: .window, with: p)
}
}
return controller
}

View File

@ -4,18 +4,32 @@ import SwiftSignalKit
import Postbox
import TelegramCore
private struct ChannelVisibilityControllerArguments {
private final class ChannelVisibilityControllerArguments {
let account: Account
let updateCurrentType: (CurrentChannelType) -> Void
let updatePublicLinkText: (String) -> Void
let displayPrivateLinkMenu: () -> Void
let updatePublicLinkText: (String?, String) -> Void
let displayPrivateLinkMenu: (String) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
let revokePeerId: (PeerId) -> Void
init(account: Account, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, displayPrivateLinkMenu: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void) {
self.account = account
self.updateCurrentType = updateCurrentType
self.updatePublicLinkText = updatePublicLinkText
self.displayPrivateLinkMenu = displayPrivateLinkMenu
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
self.revokePeerId = revokePeerId
}
}
private enum ChannelVisibilitySection: Int32 {
case type
case link
case existingPublicLinks
}
private enum ChannelVisibilityEntryTag {
case privateLink
}
private enum ChannelVisibilityEntry: ItemListNodeEntry {
@ -24,23 +38,24 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case typePrivate(Bool)
case typeInfo(String)
case publicLinkAvailability(Bool)
case privateLink(String?)
case editablePublicLink(String)
case editablePublicLink(String?, String)
case privateLinkInfo(String)
case publicLinkInfo(String)
case publicLinkStatus(String, AddressNameStatus)
case publicLinkStatus(String, AddressNameValidationStatus)
case existingLinksInfo(String)
case existingLinkPeerItem(Int32, Peer, ItemListPeerItemEditing)
case existingLinkPeerItem(Int32, Peer, ItemListPeerItemEditing, Bool)
var section: ItemListSectionId {
switch self {
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
return ChannelVisibilitySection.type.rawValue
case .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus:
case .publicLinkAvailability, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus:
return ChannelVisibilitySection.link.rawValue
case .existingLinksInfo, .existingLinkPeerItem:
return ChannelVisibilitySection.existingPublicLinks.rawValue
return ChannelVisibilitySection.link.rawValue
}
}
@ -55,21 +70,23 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case .typeInfo:
return 3
case .privateLink:
case .publicLinkAvailability:
return 4
case .editablePublicLink:
case .privateLink:
return 5
case .privateLinkInfo:
case .editablePublicLink:
return 6
case .publicLinkStatus:
case .privateLinkInfo:
return 7
case .publicLinkInfo:
case .publicLinkStatus:
return 8
case .publicLinkInfo:
return 9
case .existingLinksInfo:
return 9
case let .existingLinkPeerItem(index, _, _):
return 10 + index
return 10
case let .existingLinkPeerItem(index, _, _, _):
return 11 + index
}
}
@ -99,14 +116,20 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else {
return false
}
case let .publicLinkAvailability(value):
if case .publicLinkAvailability(value) = rhs {
return true
} else {
return false
}
case let .privateLink(lhsLink):
if case let .privateLink(rhsLink) = rhs, lhsLink == rhsLink {
return true
} else {
return false
}
case let .editablePublicLink(text):
if case .editablePublicLink(text) = rhs {
case let .editablePublicLink(lhsCurrentText, lhsText):
if case let .editablePublicLink(rhsCurrentText, rhsText) = rhs, lhsCurrentText == rhsCurrentText, lhsText == rhsText {
return true
} else {
return false
@ -135,8 +158,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else {
return false
}
case let .existingLinkPeerItem(lhsIndex, lhsPeer, lhsEditing):
if case let .existingLinkPeerItem(rhsIndex, rhsPeer, rhsEditing) = rhs {
case let .existingLinkPeerItem(lhsIndex, lhsPeer, lhsEditing, lhsEnabled):
if case let .existingLinkPeerItem(rhsIndex, rhsPeer, rhsEditing, rhsEnabled) = rhs {
if lhsIndex != rhsIndex {
return false
}
@ -146,6 +169,9 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
if lhsEditing != rhsEditing {
return false
}
if lhsEnabled != rhsEnabled {
return false
}
return true
} else {
return false
@ -171,13 +197,21 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
})
case let .typeInfo(text):
return ItemListTextItem(text: text, sectionId: self.section)
case let .publicLinkAvailability(value):
if value {
return ItemListActivityTextItem(displayActivity: true, text: NSAttributedString(string: "Checking", textColor: UIColor(0x6d6d72)), sectionId: self.section)
} else {
return ItemListActivityTextItem(displayActivity: false, text: NSAttributedString(string: "Sorry, you have reserved too many public usernames. You can revoke the link from one of your older groups or channels, or create a private entity instead.", textColor: UIColor(0xcf3030)), sectionId: self.section)
}
case let .privateLink(link):
return ItemListActionItem(title: link ?? "Loading", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
})
case let .editablePublicLink(text):
return ItemListActionItem(title: link ?? "Loading", kind: .neutral, alignment: .natural, sectionId: self.section, style: .blocks, action: {
if let link = link {
arguments.displayPrivateLinkMenu(link)
}
}, tag: ChannelVisibilityEntryTag.privateLink)
case let .editablePublicLink(currentText, text):
return ItemListSingleLineInputItem(title: NSAttributedString(string: "t.me/", textColor: .black), text: text, placeholder: "", sectionId: self.section, textUpdated: { updatedText in
arguments.updatePublicLinkText(updatedText)
arguments.updatePublicLinkText(currentText, updatedText)
}, action: {
})
@ -189,31 +223,44 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
var displayActivity = false
let text: NSAttributedString
switch status {
case let .invalidFormat(error):
switch error {
case .startsWithDigit:
text = NSAttributedString(string: "Names can't start with a digit.", textColor: UIColor(0xcf3030))
case .startsWithUnderscore:
text = NSAttributedString(string: "Names can't start with an underscore.", textColor: UIColor(0xcf3030))
case .endsWithUnderscore:
text = NSAttributedString(string: "Names can't end with an underscore.", textColor: UIColor(0xcf3030))
case .tooShort:
text = NSAttributedString(string: "Names must have at least 5 characters.", textColor: UIColor(0xcf3030))
case .invalidCharacters:
text = NSAttributedString(string: "Sorry, this name is invalid.", textColor: UIColor(0xcf3030))
}
case let .availability(availability):
switch availability {
case .available:
text = NSAttributedString(string: "\(addressName) is available.", textColor: UIColor(0x26972c))
case .checking:
text = NSAttributedString(string: "Checking name...", textColor: .gray)
displayActivity = true
case let .invalid(reason):
switch reason {
case .alreadyTaken:
text = NSAttributedString(string: "\(addressName) is already taken.", textColor: .red)
case .digitStart:
text = NSAttributedString(string: "Names can't start with a digit.", textColor: UIColor(0xcf3030))
case .invalid, .underscopeEnd, .underscopeStart:
case .invalid:
text = NSAttributedString(string: "Sorry, this name is invalid.", textColor: UIColor(0xcf3030))
case .short:
text = NSAttributedString(string: "Names must have at least 5 characters.", textColor: UIColor(0xcf3030))
case .taken:
text = NSAttributedString(string: "\(addressName) is already taken.", textColor: UIColor(0xcf3030))
}
case .checking:
text = NSAttributedString(string: "Checking name...", textColor: UIColor(0x6d6d72))
displayActivity = true
}
return ItemListActivityTextItem(displayActivity: displayActivity, text: text, sectionId: self.section)
case let .existingLinksInfo(text):
return ItemListTextItem(text: text, sectionId: self.section)
case let .existingLinkPeerItem(_, peer, editing):
return ItemListPeerItem(account: arguments.account, peer: peer, presence: nil, text: .activity, label: nil, editing: editing, enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in
}, removePeer: { _ in
case let .existingLinkPeerItem(_, peer, editing, enabled):
var label = ""
if let addressName = peer.addressName {
label = "t.me/" + addressName
}
return ItemListPeerItem(account: arguments.account, peer: peer, presence: nil, text: .text(label), label: nil, editing: editing, enabled: enabled, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id)
}, removePeer: { peerId in
arguments.revokePeerId(peerId)
})
}
}
@ -224,53 +271,30 @@ private enum CurrentChannelType {
case privateChannel
}
private enum AddressNameStatus: Equatable {
case available
case checking
case invalid(UsernameAvailabilityError)
static func ==(lhs: AddressNameStatus, rhs: AddressNameStatus) -> Bool {
switch lhs {
case .available:
if case .available = rhs {
return true
} else {
return false
}
case .checking:
if case .checking = rhs {
return true
} else {
return false
}
case let .invalid(reason):
if case .invalid(reason) = rhs {
return true
} else {
return false
}
}
}
}
private struct ChannelVisibilityControllerState: Equatable {
let selectedType: CurrentChannelType?
let editingPublicLinkText: String?
let addressNameStatus: AddressNameStatus?
let addressNameValidationStatus: AddressNameValidationStatus?
let updatingAddressName: Bool
let revealedRevokePeerId: PeerId?
let revokingPeerId: PeerId?
init() {
self.selectedType = nil
self.editingPublicLinkText = nil
self.addressNameStatus = nil
self.addressNameValidationStatus = nil
self.updatingAddressName = false
self.revealedRevokePeerId = nil
self.revokingPeerId = nil
}
init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameStatus: AddressNameStatus?, updatingAddressName: Bool) {
init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: PeerId?, revokingPeerId: PeerId?) {
self.selectedType = selectedType
self.editingPublicLinkText = editingPublicLinkText
self.addressNameStatus = addressNameStatus
self.addressNameValidationStatus = addressNameValidationStatus
self.updatingAddressName = updatingAddressName
self.revealedRevokePeerId = revealedRevokePeerId
self.revokingPeerId = revokingPeerId
}
static func ==(lhs: ChannelVisibilityControllerState, rhs: ChannelVisibilityControllerState) -> Bool {
@ -280,34 +304,48 @@ private struct ChannelVisibilityControllerState: Equatable {
if lhs.editingPublicLinkText != rhs.editingPublicLinkText {
return false
}
if lhs.addressNameStatus != rhs.addressNameStatus {
if lhs.addressNameValidationStatus != rhs.addressNameValidationStatus {
return false
}
if lhs.updatingAddressName != rhs.updatingAddressName {
return false
}
if lhs.revealedRevokePeerId != rhs.revealedRevokePeerId {
return false
}
if lhs.revokingPeerId != rhs.revokingPeerId {
return false
}
return true
}
func withUpdatedSelectedType(_ selectedType: CurrentChannelType?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameStatus: self.addressNameStatus, updatingAddressName: self.updatingAddressName)
return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId)
}
func withUpdatedEditingPublicLinkText(_ editingPublicLinkText: String?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameStatus: self.addressNameStatus, updatingAddressName: self.updatingAddressName)
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId)
}
func withUpdatedAddressNameStatus(_ addressNameStatus: AddressNameStatus?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameStatus: addressNameStatus, updatingAddressName: self.updatingAddressName)
func withUpdatedAddressNameValidationStatus(_ addressNameValidationStatus: AddressNameValidationStatus?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId)
}
func withUpdatedUpdatingAddressName(_ updatingAddressName: Bool) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameStatus: self.addressNameStatus, updatingAddressName: updatingAddressName)
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId)
}
func withUpdatedRevealedRevokePeerId(_ revealedRevokePeerId: PeerId?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId)
}
func withUpdatedRevokingPeerId(_ revokingPeerId: PeerId?) -> ChannelVisibilityControllerState {
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId)
}
}
private func channelVisibilityControllerEntries(view: PeerView, state: ChannelVisibilityControllerState) -> [ChannelVisibilityEntry] {
private func channelVisibilityControllerEntries(view: PeerView, publicChannelsToRevoke: [Peer]?, state: ChannelVisibilityControllerState) -> [ChannelVisibilityEntry] {
var entries: [ChannelVisibilityEntry] = []
if let peer = view.peers[view.peerId] as? TelegramChannel {
@ -359,11 +397,39 @@ private func channelVisibilityControllerEntries(view: PeerView, state: ChannelVi
switch selectedType {
case .publicChannel:
entries.append(.editablePublicLink(currentAddressName))
if let status = state.addressNameStatus {
var displayAvailability = false
if peer.addressName == nil {
displayAvailability = publicChannelsToRevoke == nil || !(publicChannelsToRevoke!.isEmpty)
}
if displayAvailability {
if let publicChannelsToRevoke = publicChannelsToRevoke {
entries.append(.publicLinkAvailability(false))
var index: Int32 = 0
for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in
var lhsDate: Int32 = 0
var rhsDate: Int32 = 0
if let lhs = lhs as? TelegramChannel {
lhsDate = lhs.creationDate
}
if let rhs = rhs as? TelegramChannel {
rhsDate = rhs.creationDate
}
return lhsDate > rhsDate
}) {
entries.append(.existingLinkPeerItem(index, peer, ItemListPeerItemEditing(editable: true, editing: true, revealed: state.revealedRevokePeerId == peer.id), state.revokingPeerId == nil))
index += 1
}
} else {
entries.append(.publicLinkAvailability(true))
}
} else {
entries.append(.editablePublicLink(peer.addressName, currentAddressName))
if let status = state.addressNameValidationStatus {
entries.append(.publicLinkStatus(currentAddressName, status))
}
entries.append(.publicLinkInfo("People can share this link with others and find your group using Telegram search."))
}
case .privateChannel:
entries.append(.privateLink((view.cachedData as? CachedChannelData)?.exportedInvitation?.link))
entries.append(.publicLinkInfo("People can join your group by following this link. You can revoke the link at any time."))
@ -426,7 +492,18 @@ public func channelVisibilityController(account: Account, peerId: PeerId) -> Vie
statePromise.set(stateValue.modify { f($0) })
}
let peersDisablingAddressNameAssignment = Promise<[Peer]?>()
peersDisablingAddressNameAssignment.set(.single(nil) |> then(channelAddressNameAssignmentAvailability(account: account, peerId: peerId) |> mapToSignal { result -> Signal<[Peer]?, NoError> in
if case .addressNameLimitReached = result {
return adminedPublicChannels(account: account)
|> map { Optional($0) }
} else {
return .single([])
}
}))
var dismissImpl: (() -> Void)?
var displayPrivateLinkMenuImpl: ((String) -> Void)?
let actionsDisposable = DisposableSet()
@ -436,46 +513,67 @@ public func channelVisibilityController(account: Account, peerId: PeerId) -> Vie
let updateAddressNameDisposable = MetaDisposable()
actionsDisposable.add(updateAddressNameDisposable)
let revokeAddressNameDisposable = MetaDisposable()
actionsDisposable.add(revokeAddressNameDisposable)
let arguments = ChannelVisibilityControllerArguments(account: account, updateCurrentType: { type in
updateState { state in
return state.withUpdatedSelectedType(type)
}
}, updatePublicLinkText: { text in
}, updatePublicLinkText: { currentText, text in
if text.isEmpty {
checkAddressNameDisposable.set(nil)
updateState { state in
return state.withUpdatedEditingPublicLinkText(text).withUpdatedAddressNameStatus(nil)
return state.withUpdatedEditingPublicLinkText(text).withUpdatedAddressNameValidationStatus(nil)
}
} else if currentText == text {
checkAddressNameDisposable.set(nil)
updateState { state in
return state.withUpdatedEditingPublicLinkText(text).withUpdatedAddressNameValidationStatus(nil).withUpdatedAddressNameValidationStatus(nil)
}
} else {
updateState { state in
return state.withUpdatedEditingPublicLinkText(text)
}
checkAddressNameDisposable.set((addressNameAvailability(account: account, domain: .peer(peerId), def: nil, current: text)
checkAddressNameDisposable.set((validateAddressNameInteractive(account: account, domain: .peer(peerId), name: text)
|> deliverOnMainQueue).start(next: { result in
updateState { state in
let status: AddressNameStatus
switch result {
case let .fail(_, error):
status = .invalid(error)
case .none:
status = .available
case .success:
status = .available
case .progress:
status = .checking
}
return state.withUpdatedAddressNameStatus(status)
return state.withUpdatedAddressNameValidationStatus(result)
}
}))
}
}, displayPrivateLinkMenu: {
}, displayPrivateLinkMenu: { text in
displayPrivateLinkMenuImpl?(text)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
if (peerId == nil && fromPeerId == state.revealedRevokePeerId) || (peerId != nil && fromPeerId == nil) {
return state.withUpdatedRevealedRevokePeerId(peerId)
} else {
return state
}
}
}, revokePeerId: { peerId in
updateState { state in
return state.withUpdatedRevokingPeerId(peerId)
}
revokeAddressNameDisposable.set((updateAddressName(account: account, domain: .peer(peerId), name: nil) |> deliverOnMainQueue).start(error: { _ in
updateState { state in
return state.withUpdatedRevokingPeerId(nil)
}
}, completed: {
updateState { state in
return state.withUpdatedRevokingPeerId(nil)
}
peersDisablingAddressNameAssignment.set(.single([]))
}))
})
let peerView = account.viewTracker.peerView(peerId)
let signal = combineLatest(statePromise.get(), peerView)
|> map { state, view -> (ItemListControllerState, (ItemListNodeState<ChannelVisibilityEntry>, ChannelVisibilityEntry.ItemGenerationArguments)) in
let signal = combineLatest(statePromise.get(), peerView, peersDisablingAddressNameAssignment.get())
|> map { state, view, publicChannelsToRevoke -> (ItemListControllerState, (ItemListNodeState<ChannelVisibilityEntry>, ChannelVisibilityEntry.ItemGenerationArguments)) in
let peer = peerViewMainPeer(view)
var rightNavigationButton: ItemListNavigationButton?
@ -486,9 +584,9 @@ public func channelVisibilityController(account: Account, peerId: PeerId) -> Vie
case .privateChannel:
break
case .publicChannel:
if let addressNameStatus = state.addressNameStatus {
switch addressNameStatus {
case .available:
if let addressNameValidationStatus = state.addressNameValidationStatus {
switch addressNameValidationStatus {
case .availability(.available):
break
default:
doneEnabled = false
@ -510,7 +608,7 @@ public func channelVisibilityController(account: Account, peerId: PeerId) -> Vie
}
if let updatedAddressNameValue = updatedAddressNameValue {
updateAddressNameDisposable.set((updatePeerAddressName(account: account, peerId: peerId, username: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue)
updateAddressNameDisposable.set((updateAddressName(account: account, domain: .peer(peerId), name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue)
|> deliverOnMainQueue).start(error: { _ in
updateState { state in
return state.withUpdatedUpdatingAddressName(false)
@ -540,7 +638,7 @@ public func channelVisibilityController(account: Account, peerId: PeerId) -> Vie
})
let controllerState = ItemListControllerState(title: isGroup ? "Group Type" : "Channel Link", leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, animateChanges: false)
let listState = ItemListNodeState(entries: channelVisibilityControllerEntries(view: view, state: state), style: .blocks, animateChanges: false)
let listState = ItemListNodeState(entries: channelVisibilityControllerEntries(view: view, publicChannelsToRevoke: publicChannelsToRevoke, state: state), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments))
} |> afterDisposed {
@ -551,5 +649,34 @@ public func channelVisibilityController(account: Account, peerId: PeerId) -> Vie
dismissImpl = { [weak controller] in
controller?.dismiss()
}
displayPrivateLinkMenuImpl = { [weak controller] text in
if let strongController = controller {
var resultItemNode: ListViewItemNode?
let _ = strongController.frameForItemNode({ itemNode in
if let itemNode = itemNode as? ItemListActionItemNode {
if let tag = itemNode.tag as? ChannelVisibilityEntryTag {
if tag == .privateLink {
resultItemNode = itemNode
return true
}
}
}
return false
})
if let resultItemNode = resultItemNode {
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text("Copy"), action: {
UIPasteboard.general.string = text
})])
strongController.present(contextMenuController, in: .window, with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in
if let resultItemNode = resultItemNode {
return (resultItemNode, resultItemNode.contentBounds.insetBy(dx: 0.0, dy: -2.0))
} else {
return nil
}
}))
}
}
}
return controller
}

View File

@ -213,7 +213,7 @@ class ChatControllerNode: ASDisplayNode {
}
if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode {
inputMediaNode.updateLayout(width: layout.size.width, transition: .immediate, interfaceState: self.chatPresentationInterfaceState)
let _ = inputMediaNode.updateLayout(width: layout.size.width, transition: .immediate, interfaceState: self.chatPresentationInterfaceState)
}
var insets: UIEdgeInsets
@ -236,10 +236,9 @@ class ChatControllerNode: ASDisplayNode {
var duration: Double = 0.0
var curve: UInt = 0
var animated = true
switch transition {
case .immediate:
animated = false
break
case let .animated(animationDuration, animationCurve):
duration = animationDuration
switch animationCurve {
@ -340,14 +339,14 @@ class ChatControllerNode: ASDisplayNode {
var inputPanelsHeight: CGFloat = 0.0
var inputPanelFrame: CGRect?
if let inputPanelNode = self.inputPanelNode {
if self.inputPanelNode != nil {
assert(inputPanelSize != nil)
inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - inputPanelsHeight - inputPanelSize!.height), size: CGSize(width: layout.size.width, height: inputPanelSize!.height))
inputPanelsHeight += inputPanelSize!.height
}
var accessoryPanelFrame: CGRect?
if let accessoryPanelNode = self.accessoryPanelNode {
if self.accessoryPanelNode != nil {
assert(accessoryPanelSize != nil)
accessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - inputPanelsHeight - accessoryPanelSize!.height), size: CGSize(width: layout.size.width, height: accessoryPanelSize!.height))
inputPanelsHeight += accessoryPanelSize!.height
@ -429,7 +428,7 @@ class ChatControllerNode: ASDisplayNode {
if let dismissedInputPanelNode = dismissedInputPanelNode {
var frameCompleted = false
var alphaCompleted = false
var completed = { [weak self, weak dismissedInputPanelNode] in
let completed = { [weak self, weak dismissedInputPanelNode] in
if let strongSelf = self, let dismissedInputPanelNode = dismissedInputPanelNode, strongSelf.inputPanelNode === dismissedInputPanelNode {
return
}
@ -452,7 +451,7 @@ class ChatControllerNode: ASDisplayNode {
if let dismissedAccessoryPanelNode = dismissedAccessoryPanelNode {
var frameCompleted = false
var alphaCompleted = false
var completed = { [weak dismissedAccessoryPanelNode] in
let completed = { [weak dismissedAccessoryPanelNode] in
if frameCompleted && alphaCompleted {
dismissedAccessoryPanelNode?.removeFromSupernode()
}
@ -475,7 +474,7 @@ class ChatControllerNode: ASDisplayNode {
if let dismissedInputContextPanelNode = dismissedInputContextPanelNode {
var frameCompleted = false
var animationCompleted = false
var completed = { [weak dismissedInputContextPanelNode] in
let completed = { [weak dismissedInputContextPanelNode] in
if let dismissedInputContextPanelNode = dismissedInputContextPanelNode, frameCompleted, animationCompleted {
dismissedInputContextPanelNode.removeFromSupernode()
}
@ -496,8 +495,16 @@ class ChatControllerNode: ASDisplayNode {
}
if let dismissedInputNode = dismissedInputNode {
transition.updateFrame(node: dismissedInputNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - CGFloat(FLT_EPSILON)), size: CGSize(width: layout.size.width, height: max(insets.bottom, dismissedInputNode.bounds.size.height))), completion: { [weak dismissedInputNode] _ in
transition.updateFrame(node: dismissedInputNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - CGFloat(FLT_EPSILON)), size: CGSize(width: layout.size.width, height: max(insets.bottom, dismissedInputNode.bounds.size.height))), completion: { [weak self, weak dismissedInputNode] completed in
if completed {
if let strongSelf = self {
if strongSelf.inputNode !== dismissedInputNode {
dismissedInputNode?.removeFromSupernode()
}
} else {
dismissedInputNode?.removeFromSupernode()
}
}
})
}
}
@ -521,14 +528,14 @@ class ChatControllerNode: ASDisplayNode {
}
if self.chatPresentationInterfaceState != chatPresentationInterfaceState {
var updatedInputFocus = self.chatPresentationInterfaceStateRequiresInputFocus(self.chatPresentationInterfaceState) != self.chatPresentationInterfaceStateRequiresInputFocus(chatPresentationInterfaceState)
var updateInputTextState = self.chatPresentationInterfaceState.interfaceState.effectiveInputState != chatPresentationInterfaceState.interfaceState.effectiveInputState
let updatedInputFocus = self.chatPresentationInterfaceStateRequiresInputFocus(self.chatPresentationInterfaceState) != self.chatPresentationInterfaceStateRequiresInputFocus(chatPresentationInterfaceState)
let updateInputTextState = self.chatPresentationInterfaceState.interfaceState.effectiveInputState != chatPresentationInterfaceState.interfaceState.effectiveInputState
self.chatPresentationInterfaceState = chatPresentationInterfaceState
let keepSendButtonEnabled = chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil || chatPresentationInterfaceState.interfaceState.editMessage != nil
var extendedSearchLayout = false
if let inputQueryResult = chatPresentationInterfaceState.inputQueryResult {
if case let .contextRequestResult(peer, _) = inputQueryResult {
if case .contextRequestResult = inputQueryResult {
extendedSearchLayout = true
}
}
@ -612,7 +619,7 @@ class ChatControllerNode: ASDisplayNode {
self.scheduledLayoutTransitionRequest = (requestId, transition)
(self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in
if let strongSelf = self {
if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest {
if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest, currentRequestId == requestId {
strongSelf.scheduledLayoutTransitionRequest = nil
strongSelf.requestLayout(currentRequestTransition)
}
@ -626,7 +633,7 @@ class ChatControllerNode: ASDisplayNode {
let inputNode = ChatMediaInputNode(account: self.account, controllerInteraction: self.controllerInteraction)
inputNode.interfaceInteraction = interfaceInteraction
self.inputMediaNode = inputNode
inputNode.updateLayout(width: self.bounds.size.width, transition: .immediate, interfaceState: self.chatPresentationInterfaceState)
let _ = inputNode.updateLayout(width: self.bounds.size.width, transition: .immediate, interfaceState: self.chatPresentationInterfaceState)
}
}
}

View File

@ -5,15 +5,6 @@ import Display
import TelegramCore
private let composeButtonImage = generateImage(CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in
/*
<svg width="24px" height="24px" viewBox="0 -1 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
<desc>Created with Sketch.</desc>
<defs></defs>
<path d="M0,4 L15,4 L14,5 L1,5 L1,22 L18,22 L18,9 L19,8 L19,23 L0,23 L0,4 Z M18.5944456,1.70209754 L19.5995507,2.70718758 L10.0510517,12.255543 L9.54849908,13.7631781 L11.0561568,13.2606331 L20.6046559,3.71227763 L21.6097611,4.71736767 L11.5587094,14.7682681 L7.53828874,15.7733582 L9.04594649,11.250453 L18.5944456,1.70209754 Z M19.0969982,1.19955251 L20.0773504,0.21921503 C20.3690844,-0.0725145755 20.8398084,-0.0729335627 21.1298838,0.217137419 L23.0947435,2.18196761 C23.3833646,2.47058439 23.3838887,2.94326675 23.0926659,3.23448517 L22.1123136,4.21482265 L19.0969982,1.19955251 Z" id="Edit" stroke="none" fill="#000000" fill-rule="evenodd"></path>
</svg>
*/
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(0x007ee5).cgColor)
try? drawSvgPath(context, path: "M0,4 L15,4 L14,5 L1,5 L1,22 L18,22 L18,9 L19,8 L19,23 L0,23 L0,4 Z M18.5944456,1.70209754 L19.5995507,2.70718758 L10.0510517,12.255543 L9.54849908,13.7631781 L11.0561568,13.2606331 L20.6046559,3.71227763 L21.6097611,4.71736767 L11.5587094,14.7682681 L7.53828874,15.7733582 L9.04594649,11.250453 L18.5944456,1.70209754 Z M19.0969982,1.19955251 L20.0773504,0.21921503 C20.3690844,-0.0725145755 20.8398084,-0.0729335627 21.1298838,0.217137419 L23.0947435,2.18196761 C23.3833646,2.47058439 23.3838887,2.94326675 23.0926659,3.23448517 L22.1123136,4.21482265 L19.0969982,1.19955251 Z ")
@ -32,6 +23,8 @@ public class ChatListController: TelegramController {
private var titleDisposable: Disposable?
private var badgeDisposable: Disposable?
private var dismissSearchOnDisappear = false
public override init(account: Account) {
self.account = account
@ -106,7 +99,7 @@ public class ChatListController: TelegramController {
self.chatListDisplayNode.navigationBar = self.navigationBar
self.chatListDisplayNode.requestDeactivateSearch = { [weak self] in
self?.deactivateSearch()
self?.deactivateSearch(animated: true)
}
self.chatListDisplayNode.chatListNode.activateSearch = { [weak self] in
@ -161,6 +154,7 @@ public class ChatListController: TelegramController {
}
strongSelf.openMessageFromSearchDisposable.set((storedPeer |> deliverOnMainQueue).start(completed: { [weak strongSelf] in
if let strongSelf = strongSelf {
strongSelf.dismissSearchOnDisappear = true
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, peerId: peer.id))
}
}))
@ -176,6 +170,11 @@ public class ChatListController: TelegramController {
override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if self.dismissSearchOnDisappear {
self.dismissSearchOnDisappear = false
self.deactivateSearch(animated: false)
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@ -208,10 +207,10 @@ public class ChatListController: TelegramController {
}
}
private func deactivateSearch() {
private func deactivateSearch(animated: Bool) {
if !self.displayNavigationBar {
self.chatListDisplayNode.deactivateSearch()
self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring))
self.chatListDisplayNode.deactivateSearch(animated: animated)
self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate)
}
}

View File

@ -107,7 +107,7 @@ class ChatListControllerNode: ASDisplayNode {
}
}
func deactivateSearch() {
func deactivateSearch(animated: Bool) {
if let searchDisplayController = self.searchDisplayController {
var maybePlaceholderNode: SearchBarPlaceholderNode?
self.chatListNode.forEachItemNode { node in
@ -116,7 +116,7 @@ class ChatListControllerNode: ASDisplayNode {
}
}
searchDisplayController.deactivate(placeholder: maybePlaceholderNode)
searchDisplayController.deactivate(placeholder: maybePlaceholderNode, animated: animated)
self.searchDisplayController = nil
}
}

View File

@ -422,7 +422,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
self.enqueuedRecentTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions()
options.insert(.PreferSynchronousResourceLoading)
options.insert(.PreferSynchronousDrawing)
if firstTime {
} else {
}
@ -447,7 +447,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
self.enqueuedTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions()
options.insert(.PreferSynchronousResourceLoading)
options.insert(.PreferSynchronousDrawing)
if firstTime {
} else {
//options.insert(.AnimateAlpha)

View File

@ -100,11 +100,20 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
}
if entities == nil {
var generateEntities = false
for media in message.media {
if media is TelegramMediaImage || media is TelegramMediaFile {
generateEntities = true
break
}
}
if generateEntities {
let parsedEntities = generateTextEntities(message.text)
if !parsedEntities.isEmpty {
entities = TextEntitiesMessageAttribute(entities: parsedEntities)
}
}
}
if let entities = entities {
attributedText = stringWithAppliedEntities(message.text, entities: entities.entities, baseFont: messageFont, boldFont: messageBoldFont, fixedFont: messageFixedFont)
} else {

View File

@ -322,11 +322,11 @@ private enum GroupInfoEntry: ItemListNodeEntry {
})
case let .membersAdmins(count):
return ItemListDisclosureItem(title: "Admins", label: "\(count)", sectionId: self.section, style: .blocks, action: {
arguments.pushController(ChannelAdminsController(account: arguments.account, peerId: arguments.peerId))
arguments.pushController(channelAdminsController(account: arguments.account, peerId: arguments.peerId))
})
case let .membersBlacklist(count):
return ItemListDisclosureItem(title: "Blacklist", label: "\(count)", sectionId: self.section, style: .blocks, action: {
arguments.pushController(channelBlacklistController(account: arguments.account, peerId: arguments.peerId))
})
case let .member(_, _, peer, presence, memberStatus, editing, enabled):
let label: String?
@ -524,10 +524,10 @@ private func groupInfoEntries(account: Account, view: PeerView, state: GroupInfo
entries.append(GroupInfoEntry.groupDescriptionSetup(text: editingState.editingDescriptionText))
if let adminCount = cachedChannelData.participantsSummary.adminCount {
entries.append(GroupInfoEntry.membersAdmins(count: adminCount))
entries.append(GroupInfoEntry.membersAdmins(count: Int(adminCount)))
}
if let bannedCount = cachedChannelData.participantsSummary.bannedCount {
entries.append(GroupInfoEntry.membersBlacklist(count: bannedCount))
entries.append(GroupInfoEntry.membersBlacklist(count: Int(bannedCount)))
}
}
} else {

View File

@ -1,677 +0,0 @@
/*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)
}
*/

View File

@ -21,14 +21,16 @@ class ItemListActionItem: ListViewItem, ItemListItem {
let sectionId: ItemListSectionId
let style: ItemListStyle
let action: () -> Void
let tag: Any?
init(title: String, kind: ItemListActionKind, alignment: ItemListActionAlignment, sectionId: ItemListSectionId, style: ItemListStyle, action: @escaping () -> Void) {
init(title: String, kind: ItemListActionKind, alignment: ItemListActionAlignment, sectionId: ItemListSectionId, style: ItemListStyle, action: @escaping () -> Void, tag: Any? = nil) {
self.title = title
self.kind = kind
self.alignment = alignment
self.sectionId = sectionId
self.style = style
self.action = action
self.tag = tag
}
func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
@ -80,6 +82,12 @@ class ItemListActionItemNode: ListViewItemNode {
private let titleNode: TextNode
private var item: ItemListActionItem?
var tag: Any? {
return self.item?.tag
}
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
@ -141,6 +149,8 @@ class ItemListActionItemNode: ListViewItemNode {
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
let _ = titleApply()
let leftInset: CGFloat

View File

@ -0,0 +1,14 @@
import Foundation
import AsyncDisplayKit
import Display
protocol ItemListControllerEmptyStateItem {
func isEqual(to: ItemListControllerEmptyStateItem) -> Bool
func node(current: ItemListControllerEmptyStateItemNode?) -> ItemListControllerEmptyStateItemNode
}
class ItemListControllerEmptyStateItemNode: ASDisplayNode {
func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
}
}

View File

@ -37,6 +37,7 @@ enum ItemListStyle {
private struct ItemListNodeTransition {
let entries: ItemListNodeEntryTransition
let updateStyle: ItemListStyle?
let emptyStateItem: ItemListControllerEmptyStateItem?
let firstTime: Bool
let animated: Bool
let animateAlpha: Bool
@ -45,11 +46,13 @@ private struct ItemListNodeTransition {
struct ItemListNodeState<Entry: ItemListNodeEntry> {
let entries: [Entry]
let style: ItemListStyle
let emptyStateItem: ItemListControllerEmptyStateItem?
let animateChanges: Bool
init(entries: [Entry], style: ItemListStyle, animateChanges: Bool = true) {
init(entries: [Entry], style: ItemListStyle, emptyStateItem: ItemListControllerEmptyStateItem? = nil, animateChanges: Bool = true) {
self.entries = entries
self.style = style
self.emptyStateItem = emptyStateItem
self.animateChanges = animateChanges
}
}
@ -62,10 +65,13 @@ final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
private var didSetReady = false
let listNode: ListView
private var emptyStateItem: ItemListControllerEmptyStateItem?
private var emptyStateNode: ItemListControllerEmptyStateItemNode?
private let transitionDisposable = MetaDisposable()
private var enqueuedTransitions: [ItemListNodeTransition] = []
private var hadValidLayout = false
private var validLayout: (ContainerViewLayout, CGFloat)?
var dismiss: (() -> Void)?
@ -89,7 +95,7 @@ final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
if previous?.style != state.style {
updatedStyle = state.style
}
return ItemListNodeTransition(entries: transition, updateStyle: updatedStyle, firstTime: previous == nil, animated: previous != nil && state.animateChanges, animateAlpha: previous != nil && !state.animateChanges)
return ItemListNodeTransition(entries: transition, updateStyle: updatedStyle, emptyStateItem: state.emptyStateItem, firstTime: previous == nil, animated: previous != nil && state.animateChanges, animateAlpha: previous != nil && !state.animateChanges)
}) |> deliverOnMainQueue).start(next: { [weak self] transition in
if let strongSelf = self {
strongSelf.enqueueTransition(transition)
@ -144,15 +150,20 @@ final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if !self.hadValidLayout {
self.hadValidLayout = true
if let emptyStateNode = self.emptyStateNode {
emptyStateNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition)
}
let dequeue = self.validLayout == nil
self.validLayout = (layout, navigationBarHeight)
if dequeue {
self.dequeueTransitions()
}
}
private func enqueueTransition(_ transition: ItemListNodeTransition) {
self.enqueuedTransitions.append(transition)
if self.hadValidLayout {
if self.validLayout != nil {
self.dequeueTransitions()
}
}
@ -177,6 +188,7 @@ final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
options.insert(.AnimateInsertion)
} else if transition.animateAlpha {
options.insert(.PreferSynchronousResourceLoading)
options.insert(.PreferSynchronousDrawing)
options.insert(.AnimateAlpha)
}
self.listNode.transaction(deleteIndices: transition.entries.deletions, insertIndicesAndItems: transition.entries.insertions, updateIndicesAndItems: transition.entries.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
@ -187,6 +199,31 @@ final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
}
}
})
var updateEmptyStateItem = false
if let emptyStateItem = self.emptyStateItem, let updatedEmptyStateItem = transition.emptyStateItem {
updateEmptyStateItem = !emptyStateItem.isEqual(to: updatedEmptyStateItem)
} else if (self.emptyStateItem != nil) != (transition.emptyStateItem != nil) {
updateEmptyStateItem = true
}
if updateEmptyStateItem {
self.emptyStateItem = transition.emptyStateItem
if let emptyStateItem = transition.emptyStateItem {
let updatedNode = emptyStateItem.node(current: self.emptyStateNode)
if let emptyStateNode = self.emptyStateNode, updatedNode !== emptyStateNode {
emptyStateNode.removeFromSupernode()
}
if self.emptyStateNode !== updatedNode {
self.emptyStateNode = updatedNode
if let validLayout = self.validLayout {
updatedNode.updateLayout(layout: validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate)
}
self.addSubnode(updatedNode)
}
} else if let emptyStateNode = self.emptyStateNode {
emptyStateNode.removeFromSupernode()
self.emptyStateNode = nil
}
}
}
}
}

View File

@ -176,7 +176,7 @@ class ItemListRevealOptionsItemNode: ListViewItemNode {
override func layout() {
if let revealNode = self.revealNode {
let height = self.bounds.size.height
let height = self.contentSize.height
let revealSize = revealNode.measure(CGSize(width: CGFloat.greatestFiniteMagnitude, height: height))
revealNode.frame = CGRect(origin: CGPoint(x: self.bounds.size.width + max(self.revealOffset, -revealSize.width), y: 0.0), size: revealSize)
}

View File

@ -0,0 +1,47 @@
import Foundation
import AsyncDisplayKit
import Display
final class ItemListLoadingIndicatorEmptyStateItem: ItemListControllerEmptyStateItem {
func isEqual(to: ItemListControllerEmptyStateItem) -> Bool {
return to is ItemListLoadingIndicatorEmptyStateItem
}
func node(current: ItemListControllerEmptyStateItemNode?) -> ItemListControllerEmptyStateItemNode {
if let current = current as? ItemListLoadingIndicatorEmptyStateItemNode {
return current
} else {
return ItemListLoadingIndicatorEmptyStateItemNode()
}
}
}
final class ItemListLoadingIndicatorEmptyStateItemNode: ItemListControllerEmptyStateItemNode {
private var indicator: UIActivityIndicatorView?
private var validLayout: (ContainerViewLayout, CGFloat)?
override func didLoad() {
super.didLoad()
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
self.indicator = indicator
self.view.addSubview(indicator)
if let layout = self.validLayout {
self.updateLayout(layout: layout.0, navigationBarHeight: layout.1, transition: .immediate)
}
indicator.startAnimating()
}
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight)
if let indicator = self.indicator {
self.validLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.statusBar])
insets.top += navigationBarHeight
let size = indicator.bounds.size
indicator.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - size.width) / 2.0), y: insets.top + floor((layout.size.height - insets.top - insets.bottom - size.height) / 2.0)), size: size)
}
}
}

View File

@ -27,6 +27,7 @@ struct ItemListPeerItemEditing: Equatable {
enum ItemListPeerItemText {
case activity
case text(String)
case none
}
final class ItemListPeerItem: ListViewItem, ItemListItem {
@ -65,7 +66,7 @@ final class ItemListPeerItem: ListViewItem, ItemListItem {
node.insets = layout.insets
completion(node, {
return (nil, { apply(false) })
return (node.avatarNode.ready, { apply(false) })
})
}
}
@ -113,7 +114,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
private let highlightedBackgroundNode: ASDisplayNode
private var disabledOverlayNode: ASDisplayNode?
private let avatarNode: AvatarNode
fileprivate let avatarNode: AvatarNode
private let titleNode: TextNode
private let labelNode: TextNode
private let statusNode: TextNode
@ -235,6 +236,8 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
}
case let .text(text):
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: UIColor(0xa6a6a6))
case .none:
break
}
if let label = item.label {
@ -368,7 +371,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 5.0), size: titleLayout.size))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: statusAttributedString == nil ? 13.0 : 5.0), size: titleLayout.size))
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 25.0), size: statusLayout.size))
transition.updateFrame(node: strongSelf.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + width - labelLayout.size.width - 15.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0 - labelLayout.size.height / 10.0)), size: labelLayout.size))
@ -450,7 +453,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
editingOffset = 0.0
}
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 5.0), size: self.titleNode.bounds.size))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 25.0), size: self.statusNode.bounds.size))
transition.updateFrame(node: self.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + width - self.labelNode.bounds.size.width - 15.0, y: floor((self.contentSize.height - self.labelNode.bounds.size.height) / 2.0 - self.labelNode.bounds.size.height / 10.0)), size: self.labelNode.bounds.size))

View File

@ -0,0 +1,65 @@
import Foundation
import AsyncDisplayKit
import Display
final class ItemListTextEmptyStateItem: ItemListControllerEmptyStateItem {
let text: String
init(text: String) {
self.text = text
}
func isEqual(to: ItemListControllerEmptyStateItem) -> Bool {
if let to = to as? ItemListTextEmptyStateItem {
return self.text == to.text
} else {
return false
}
}
func node(current: ItemListControllerEmptyStateItemNode?) -> ItemListControllerEmptyStateItemNode {
let result: ItemListTextEmptyStateItemNode
if let current = current as? ItemListTextEmptyStateItemNode {
result = current
} else {
result = ItemListTextEmptyStateItemNode()
}
result.updateText(text: self.text)
return result
}
}
final class ItemListTextEmptyStateItemNode: ItemListControllerEmptyStateItemNode {
private let textNode: ASTextNode
private var validLayout: (ContainerViewLayout, CGFloat)?
private var text: String?
override init() {
self.textNode = ASTextNode()
self.textNode.isLayerBacked = true
super.init()
self.addSubnode(self.textNode)
}
func updateText(text: String) {
if self.text != text {
self.text = text
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .gray, paragraphAlignment: .center)
if let validLayout = self.validLayout {
self.updateLayout(layout: validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate)
}
}
}
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.statusBar])
insets.top += navigationBarHeight
let textSize = self.textNode.measure(CGSize(width: layout.size.width - 40.0, height: max(1.0, layout.size.height - insets.top - insets.bottom)))
self.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: insets.top + floor((layout.size.height - insets.top - insets.bottom - textSize.height) / 2.0)), size: textSize)
}
}

View File

@ -91,6 +91,7 @@ class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.textField = SearchBarTextField()
self.textField.font = Font.regular(15.0)
self.textField.autocorrectionType = .no
self.textField.returnKeyType = .done
self.cancelButton = ASButtonNode()
@ -166,7 +167,7 @@ class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.textField.placeholderLabel.isHidden = false
}
func animateOut(to node: SearchBarPlaceholderNode, duration: Double, timingFunction: String, completion: () -> Void) {
func transitionOut(to node: SearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: () -> Void) {
node.isHidden = false
completion()
}

View File

@ -54,19 +54,24 @@ final class SearchDisplayController {
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
func deactivate(placeholder: SearchBarPlaceholderNode?) {
func deactivate(placeholder: SearchBarPlaceholderNode?, animated: Bool = true) {
searchBar.deactivate()
if let placeholder = placeholder {
let searchBar = self.searchBar
searchBar.animateOut(to: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak searchBar] in
searchBar.transitionOut(to: placeholder, transition: animated ? ContainedViewLayoutTransition.animated(duration: 0.5, curve: .spring) : ContainedViewLayoutTransition.immediate, completion: {
[weak searchBar] in
searchBar?.removeFromSupernode()
})
}
let contentNode = self.contentNode
if animated {
contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak contentNode] _ in
contentNode?.removeFromSupernode()
})
} else {
contentNode.removeFromSupernode()
}
}
}

View File

@ -0,0 +1,43 @@
import Foundation
import TelegramCore
import SwiftSignalKit
import Postbox
enum AddressNameValidationStatus: Equatable {
case checking
case invalidFormat(AddressNameFormatError)
case availability(AddressNameAvailability)
static func ==(lhs: AddressNameValidationStatus, rhs: AddressNameValidationStatus) -> Bool {
switch lhs {
case .checking:
if case .checking = rhs {
return true
} else {
return false
}
case let .invalidFormat(error):
if case .invalidFormat(error) = rhs {
return true
} else {
return false
}
case let .availability(availability):
if case .availability(availability) = rhs {
return true
} else {
return false
}
}
}
}
func validateAddressNameInteractive(account: Account, domain: AddressNameDomain, name: String) -> Signal<AddressNameValidationStatus, NoError> {
if let error = checkAddressNameFormat(name) {
return .single(.invalidFormat(error))
} else {
return .single(.checking) |> then(addressNameAvailability(account: account, domain: domain, name: name)
|> delay(0.3, queue: Queue.concurrentDefaultQueue())
|> map { result -> AddressNameValidationStatus in .availability(result) })
}
}