Peer Info screen fixes

This commit is contained in:
Ali
2020-02-11 23:10:17 +01:00
parent 9635a73097
commit 3cc7be592c
6 changed files with 267 additions and 67 deletions

View File

@@ -17,7 +17,7 @@ import ContactsPeerItem
import ChatListSearchItemHeader
import ItemListUI
enum ChannelMembersSearchMode {
public enum ChannelMembersSearchMode {
case searchMembers
case searchAdmins
case searchBanned
@@ -239,10 +239,10 @@ private struct GroupMembersSearchContextState {
var members: [RenderedChannelParticipant] = []
}
final class GroupMembersSearchContext {
public final class GroupMembersSearchContext {
fileprivate let state = Promise<GroupMembersSearchContextState>()
init(context: AccountContext, peerId: PeerId) {
public init(context: AccountContext, peerId: PeerId) {
assert(Queue.mainQueue().isCurrent())
let combinedSignal = combineLatest(queue: .mainQueue(), categorySignal(context: context, peerId: peerId, category: .contacts), categorySignal(context: context, peerId: peerId, category: .bots), categorySignal(context: context, peerId: peerId, category: .admins), categorySignal(context: context, peerId: peerId, category: .members))
@@ -275,7 +275,7 @@ private struct ChannelMembersSearchContainerState: Equatable {
var removingParticipantIds = Set<PeerId>()
}
final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNode {
public final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNode {
private let context: AccountContext
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
private let mode: ChannelMembersSearchMode
@@ -298,7 +298,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
private let presentationDataPromise: Promise<PresentationData>
init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
public init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
self.context = context
self.openPeer = openPeer
self.mode = mode
@@ -1170,7 +1170,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
self.removeMemberDisposable.dispose()
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
}
@@ -1179,7 +1179,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
self.listNode.backgroundColor = theme.chatList.backgroundColor
}
override func searchTextUpdated(text: String) {
override public func searchTextUpdated(text: String) {
if text.isEmpty {
self.searchQuery.set(.single(nil))
} else {
@@ -1244,7 +1244,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
}
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
@@ -1268,7 +1268,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
}
}
override func scrollToTop() {
override public func scrollToTop() {
if self.listNode.isHidden {
self.emptyQueryListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
} else {

View File

@@ -14,7 +14,7 @@ enum ChannelMembersSearchControllerMode {
case ban
}
enum ChannelMembersSearchFilter {
public enum ChannelMembersSearchFilter {
case exclude([PeerId])
case disable([PeerId])
}

View File

@@ -813,7 +813,7 @@ public enum ChannelVisibilityControllerMode {
case privateLink
}
public func channelVisibilityController(context: AccountContext, peerId: PeerId, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void) -> ViewController {
public func channelVisibilityController(context: AccountContext, peerId: PeerId, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, onDismissRemoveController: ViewController? = nil) -> ViewController {
let statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelVisibilityControllerState())
let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in
@@ -1231,9 +1231,22 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
}
let controller = ItemListController(context: context, state: signal)
dismissImpl = { [weak controller] in
controller?.view.endEditing(true)
controller?.dismiss()
dismissImpl = { [weak controller, weak onDismissRemoveController] in
guard let controller = controller else {
return
}
controller.view.endEditing(true)
if let onDismissRemoveController = onDismissRemoveController, let navigationController = controller.navigationController {
navigationController.setViewControllers(navigationController.viewControllers.filter { c in
if c === controller || c === onDismissRemoveController {
return false
} else {
return true
}
}, animated: true)
} else {
controller.dismiss()
}
}
dismissInputImpl = { [weak controller] in
controller?.view.endEditing(true)

View File

@@ -87,7 +87,11 @@ private final class VisualMediaItemNode: ASDisplayNode {
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
recognizer.tapActionAtPoint = { _ in
return .waitForSingleTap
}
self.imageNode.view.addGestureRecognizer(recognizer)
}
@objc func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
@@ -375,6 +379,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
self.itemInteraction.selectedMessageIds = chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.canCancelContentTouches = true
self.scrollNode.view.showsVerticalScrollIndicator = false
if #available(iOS 11.0, *) {
self.scrollNode.view.contentInsetAdjustmentBehavior = .never

View File

@@ -1099,30 +1099,135 @@ final class PeerInfoHeaderSingleLineTextFieldNode: ASDisplayNode, PeerInfoHeader
}
}
/*final class PeerInfoHeaderMultiLineTextFieldNode: ASDisplayNode, PeerInfoHeaderTextFieldNode {
private let textNode: TextFieldNode
final class PeerInfoHeaderMultiLineTextFieldNode: ASDisplayNode, PeerInfoHeaderTextFieldNode, ASEditableTextNodeDelegate {
private let textNode: EditableTextNode
private let textNodeContainer: ASDisplayNode
private let measureTextNode: ImmediateTextNode
private let topSeparator: ASDisplayNode
override init() {
self.textNode = TextFieldNode()
private let requestUpdateHeight: () -> Void
private var theme: PresentationTheme?
private var currentParams: (width: CGFloat, safeInset: CGFloat)?
private var currentMeasuredHeight: CGFloat?
var text: String {
return self.textNode.attributedText?.string ?? ""
}
init(requestUpdateHeight: @escaping () -> Void) {
self.requestUpdateHeight = requestUpdateHeight
self.textNode = EditableTextNode()
self.textNodeContainer = ASDisplayNode()
self.measureTextNode = ImmediateTextNode()
self.measureTextNode.maximumNumberOfLines = 0
self.topSeparator = ASDisplayNode()
super.init()
self.textNodeContainer.addSubnode(self.textNode)
self.addSubnode(self.textNodeContainer)
self.addSubnode(self.topSeparator)
}
func update(width: CGFloat, safeInset: CGFloat) -> CGFloat {
return 44.0
func update(width: CGFloat, safeInset: CGFloat, hasPrevious: Bool, placeholder: String, isEnabled: Bool, presentationData: PresentationData, updateText: String?) -> CGFloat {
self.currentParams = (width, safeInset)
if self.theme !== presentationData.theme {
self.theme = presentationData.theme
let textColor = presentationData.theme.list.itemPrimaryTextColor
self.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: textColor]
self.textNode.clipsToBounds = true
self.textNode.delegate = self
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
}
self.topSeparator.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
self.topSeparator.frame = CGRect(origin: CGPoint(x: safeInset + (hasPrevious ? 16.0 : 0.0), y: 0.0), size: CGSize(width: width, height: UIScreenPixel))
let attributedPlaceholderText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: presentationData.theme.list.itemPlaceholderTextColor)
if self.textNode.attributedPlaceholderText == nil || !self.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) {
self.textNode.attributedPlaceholderText = attributedPlaceholderText
}
if let updateText = updateText {
let attributedText = NSAttributedString(string: updateText, font: Font.regular(17.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
self.textNode.attributedText = attributedText
}
var measureText = self.textNode.attributedText?.string ?? ""
if measureText.hasSuffix("\n") || measureText.isEmpty {
measureText += "|"
}
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(17.0), textColor: .black)
self.measureTextNode.attributedText = attributedMeasureText
let measureTextSize = self.measureTextNode.updateLayout(CGSize(width: width - safeInset * 2.0 - 16 * 2.0, height: .greatestFiniteMagnitude))
self.currentMeasuredHeight = measureTextSize.height
let height = measureTextSize.height + 22.0
let textNodeFrame = CGRect(origin: CGPoint(x: safeInset + 16.0, y: 10.0), size: CGSize(width: width - safeInset * 2.0 - 16.0 * 2.0, height: max(height, 1000.0)))
self.textNodeContainer.frame = textNodeFrame
self.textNode.frame = CGRect(origin: CGPoint(), size: textNodeFrame.size)
return height
}
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
guard let theme = self.theme else {
return true
}
let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
if updatedText.count > 255 {
let attributedText = NSAttributedString(string: String(updatedText[updatedText.startIndex..<updatedText.index(updatedText.startIndex, offsetBy: 255)]), font: Font.regular(17.0), textColor: theme.list.itemPrimaryTextColor)
self.textNode.attributedText = attributedText
self.requestUpdateHeight()
return false
} else {
return true
}
}
func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
if let (width, safeInset) = self.currentParams {
var measureText = self.textNode.attributedText?.string ?? ""
if measureText.hasSuffix("\n") || measureText.isEmpty {
measureText += "|"
}
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(17.0), textColor: .black)
self.measureTextNode.attributedText = attributedMeasureText
let measureTextSize = self.measureTextNode.updateLayout(CGSize(width: width - safeInset * 2.0 - 16 * 2.0, height: .greatestFiniteMagnitude))
if let currentMeasuredHeight = self.currentMeasuredHeight, abs(measureTextSize.height - currentMeasuredHeight) > 0.1 {
self.requestUpdateHeight()
}
}
}
func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool {
let text: String? = UIPasteboard.general.string
if let _ = text {
return true
} else {
return false
}
}
}
}*/
final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
private let context: AccountContext
private let requestUpdateLayout: () -> Void
let avatarNode: PeerInfoEditingAvatarNode
var itemNodes: [PeerInfoHeaderTextFieldNodeKey: PeerInfoHeaderTextFieldNode] = [:]
init(context: AccountContext) {
init(context: AccountContext, requestUpdateLayout: @escaping () -> Void) {
self.context = context
self.requestUpdateLayout = requestUpdateLayout
self.avatarNode = PeerInfoEditingAvatarNode(context: context)
super.init()
@@ -1167,6 +1272,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
if let current = self.itemNodes[key] {
itemNode = current
} else {
var isMultiline = false
switch key {
case .firstName:
updateText = (peer as? TelegramUser)?.firstName ?? ""
@@ -1175,6 +1281,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
case .title:
updateText = peer?.debugDisplayTitle ?? ""
case .description:
isMultiline = true
if let cachedData = cachedData as? CachedChannelData {
updateText = cachedData.about ?? ""
} else if let cachedData = cachedData as? CachedGroupData {
@@ -1183,7 +1290,13 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
updateText = ""
}
}
if isMultiline {
itemNode = PeerInfoHeaderMultiLineTextFieldNode(requestUpdateHeight: { [weak self] in
self?.requestUpdateLayout()
})
} else {
itemNode = PeerInfoHeaderSingleLineTextFieldNode()
}
self.itemNodes[key] = itemNode
self.addSubnode(itemNode)
}
@@ -1256,6 +1369,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var performButtonAction: ((PeerInfoHeaderButtonKey) -> Void)?
var requestAvatarExpansion: (([AvatarGalleryEntry], (ASDisplayNode, CGRect, () -> (UIView?, UIView?))) -> Void)?
var requestOpenAvatarForEditing: (() -> Void)?
var requestUpdateLayout: (() -> Void)?
var navigationTransition: PeerInfoHeaderNavigationTransition?
@@ -1282,7 +1396,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.subtitleNode.displaysAsynchronously = false
self.regularContentNode = PeerInfoHeaderRegularContentNode()
self.editingContentNode = PeerInfoHeaderEditingContentNode(context: context)
var requestUpdateLayoutImpl: (() -> Void)?
self.editingContentNode = PeerInfoHeaderEditingContentNode(context: context, requestUpdateLayout: {
requestUpdateLayoutImpl?()
})
self.editingContentNode.alpha = 0.0
self.navigationButtonContainer = PeerInfoHeaderNavigationButtonContainerNode()
@@ -1297,6 +1414,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
super.init()
requestUpdateLayoutImpl = { [weak self] in
self?.requestUpdateLayout?()
}
self.addSubnode(self.backgroundNode)
self.addSubnode(self.expandedBackgroundNode)
self.addSubnode(self.separatorNode)
@@ -1650,7 +1771,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let buttonsAlpha: CGFloat
let apparentButtonSize: CGFloat
let buttonsVerticalOffset: CGFloat
var buttonsAlphaTransition = transition
if self.navigationTransition != nil {
if case let .animated(duration, curve) = transition, transitionFraction >= 1.0 - CGFloat.ulpOfOne {
buttonsAlphaTransition = .animated(duration: duration * 0.6, curve: curve)
}
if self.isAvatarExpanded {
apparentButtonSize = expandedButtonSize
} else {
@@ -1736,7 +1864,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if wasAdded {
buttonNode.alpha = 0.0
}
transition.updateAlpha(node: buttonNode, alpha: buttonsAlpha)
buttonsAlphaTransition.updateAlpha(node: buttonNode, alpha: buttonsAlpha)
let hiddenWhileExpanded: Bool
switch self.keepExpandedButtons {

View File

@@ -601,6 +601,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
interaction.openAddContact()
}))
}
}
if let cachedData = data.cachedData as? CachedUserData {
if cachedData.isBlocked {
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 4, text: user.botInfo != nil ? presentationData.strings.Bot_Unblock : presentationData.strings.Conversation_Unblock, action: {
@@ -615,12 +616,12 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}
}
}
if user.botInfo != nil, !user.isVerified {
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 5, text: presentationData.strings.ReportPeer_Report, action: {
interaction.openReport()
}))
}
}
} else if let channel = data.peer as? TelegramChannel {
let ItemUsername = 1
let ItemAbout = 2
@@ -1170,7 +1171,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
}))
}
if strongSelf.searchDisplayController == nil {
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuMore, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
@@ -1178,6 +1179,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
strongSelf.expandTabs()
}
}))
}
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
@@ -1575,6 +1577,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.openAvatarForEditing()
}
self.headerNode.requestUpdateLayout = { [weak self] in
guard let strongSelf = self else {
return
}
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
}
self.headerNode.navigationButtonContainer.performAction = { [weak self] key in
guard let strongSelf = self else {
return
@@ -1646,7 +1657,36 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
} else if let group = data.peer as? TelegramGroup, canEditPeerInfo(peer: group) {
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? ""
var updateDataSignals: [Signal<Never, Void>] = []
if title != group.title {
updateDataSignals.append(
updatePeerTitle(account: strongSelf.context.account, peerId: group.id, title: title)
|> ignoreValues
|> mapError { _ in return Void() }
)
}
if description != (data.cachedData as? CachedGroupData)?.about {
updateDataSignals.append(
updatePeerDescription(account: strongSelf.context.account, peerId: group.id, description: description.isEmpty ? nil : description)
|> ignoreValues
|> mapError { _ in return Void() }
)
}
strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals)
|> deliverOnMainQueue).start(error: { _ in
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
}, completed: {
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
}))
} else if let channel = data.peer as? TelegramChannel, canEditPeerInfo(peer: channel) {
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? ""
@@ -3106,17 +3146,19 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} else {
mode = .privateLink
}
let visibilityController = channelVisibilityController(context: strongSelf.context, peerId: groupPeer.id, mode: mode, upgradedToSupergroup: { _, f in f() })
visibilityController.navigationPresentation = .modal
let visibilityController = channelVisibilityController(context: strongSelf.context, peerId: groupPeer.id, mode: mode, upgradedToSupergroup: { _, f in f() }, onDismissRemoveController: contactsController)
//visibilityController.navigationPresentation = .modal
if let navigationController = strongSelf.controller?.navigationController as? NavigationController {
contactsController?.push(visibilityController)
/*if let navigationController = strongSelf.controller?.navigationController as? NavigationController {
var controllers = navigationController.viewControllers
if let contactsController = contactsController {
controllers.removeAll(where: { $0 === contactsController })
}
controllers.append(visibilityController)
navigationController.setViewControllers(controllers, animated: true)
}
}*/
}
strongSelf.controller?.push(contactsController)
@@ -3312,6 +3354,16 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
return
}
if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .members = currentPaneKey {
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, contentNode: ChannelMembersSearchContainerNode(context: self.context, peerId: self.peerId, mode: .searchMembers, filters: [], searchContext: nil, openPeer: { [weak self] peer, participant in
self?.openPeer(peerId: peer.id, navigation: .info)
}, updateActivity: { _ in
}, pushController: { [weak self] c in
self?.controller?.push(c)
}), cancel: { [weak self] in
self?.deactivateSearch()
})
} else {
var tagMask: MessageTags = .file
if let currentPaneKey = self.paneContainerNode.currentPaneKey {
switch currentPaneKey {
@@ -3327,6 +3379,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, tagMask: tagMask, interfaceInteraction: self.chatInterfaceInteraction), cancel: { [weak self] in
self?.deactivateSearch()
})
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
if let navigationBar = self.controller?.navigationBar {
transition.updateAlpha(node: navigationBar, alpha: 0.0)
@@ -3653,7 +3707,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if self.state.selectedMessageIds == nil {
if let currentPaneKey = self.paneContainerNode.currentPaneKey {
switch currentPaneKey {
case .files, .music, .links:
case .files, .music, .links, .members:
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
default:
break