This commit is contained in:
Ali 2020-02-11 01:53:12 +01:00
parent fd9be238d2
commit 76893115f6
17 changed files with 1542 additions and 207 deletions

View File

@ -150,8 +150,14 @@ public final class AvatarEditOverlayNode: ASDisplayNode {
context.setBlendMode(.normal)
if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIcon"), color: .white) {
context.draw(editAvatarIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - editAvatarIcon.size.width) / 2.0) + 0.5, y: floor((bounds.size.height - editAvatarIcon.size.height) / 2.0) + 1.0), size: editAvatarIcon.size))
if bounds.width > 90.0 {
if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIconLarge"), color: .white) {
context.draw(editAvatarIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - editAvatarIcon.size.width) / 2.0) + 0.5, y: floor((bounds.size.height - editAvatarIcon.size.height) / 2.0) + 1.0), size: editAvatarIcon.size))
}
} else {
if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIcon"), color: .white) {
context.draw(editAvatarIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - editAvatarIcon.size.width) / 2.0) + 0.5, y: floor((bounds.size.height - editAvatarIcon.size.height) / 2.0) + 1.0), size: editAvatarIcon.size))
}
}
}
}
@ -501,7 +507,9 @@ public final class AvatarNode: ASDisplayNode {
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIcon"), color: theme.list.itemAccentColor) {
if bounds.width > 90.0, let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIconLarge"), color: theme.list.itemAccentColor) {
context.draw(editAvatarIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - editAvatarIcon.size.width) / 2.0) + 0.5, y: floor((bounds.size.height - editAvatarIcon.size.height) / 2.0) + 1.0), size: editAvatarIcon.size))
} else if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIcon"), color: theme.list.itemAccentColor) {
context.draw(editAvatarIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - editAvatarIcon.size.width) / 2.0) + 0.5, y: floor((bounds.size.height - editAvatarIcon.size.height) / 2.0) + 1.0), size: editAvatarIcon.size))
}
} else if case .archivedChatsIcon = parameters.icon {

View File

@ -238,6 +238,8 @@ public func generateStretchableFilledCircleImage(diameter: CGFloat, color: UICol
let cap: Int
if intDiameter == 3 {
cap = 1
} else if intDiameter == 2 {
cap = 3
} else if intRadius == 1 {
cap = 2
} else {
@ -266,6 +268,35 @@ public func generateVerticallyStretchableFilledCircleImage(radius: CGFloat, colo
})?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius))
}
public func generateSmallHorizontalStretchableFilledCircleImage(diameter: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? {
return generateImage(CGSize(width: diameter + 1.0, height: diameter), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if let subImage = generateImage(CGSize(width: diameter + 1.0, height: diameter), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.black.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: diameter, height: diameter)))
context.fill(CGRect(origin: CGPoint(x: diameter / 2.0, y: 0.0), size: CGSize(width: 1.0, height: diameter)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 1.0, y: 0.0), size: CGSize(width: diameter, height: diameter)))
}) {
if let backgroundColor = backgroundColor {
context.setFillColor(backgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
if let color = color {
context.setFillColor(color.cgColor)
} else {
context.setFillColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
}
context.clip(to: CGRect(origin: CGPoint(), size: size), mask: subImage.cgImage!)
context.fill(CGRect(origin: CGPoint(), size: size))
}
})?.stretchableImage(withLeftCapWidth: Int(diameter / 2), topCapHeight: Int(diameter / 2))
}
public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor: UIColor? = nil) -> UIImage? {
guard let image = image else {
return nil

View File

@ -18,13 +18,14 @@ public final class ItemListAddressItem: ListViewItem, ItemListItem {
let selected: Bool?
public let sectionId: ItemListSectionId
let style: ItemListStyle
let displayDecorations: Bool
let action: (() -> Void)?
let longTapAction: (() -> Void)?
let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)?
public let tag: Any?
public init(theme: PresentationTheme, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
public init(theme: PresentationTheme, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, style: ItemListStyle, displayDecorations: Bool = true, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
self.theme = theme
self.label = label
self.text = text
@ -32,6 +33,7 @@ public final class ItemListAddressItem: ListViewItem, ItemListItem {
self.selected = selected
self.sectionId = sectionId
self.style = style
self.displayDecorations = displayDecorations
self.action = action
self.longTapAction = longTapAction
self.linkItemAction = linkItemAction
@ -157,7 +159,7 @@ public class ItemListAddressItemNode: ListViewItemNode {
updatedTheme = item.theme
}
let insets: UIEdgeInsets
var insets: UIEdgeInsets
let leftInset: CGFloat = 16.0 + params.leftInset
let rightInset: CGFloat = 8.0 + params.rightInset
let separatorHeight = UIScreenPixel
@ -175,6 +177,10 @@ public class ItemListAddressItemNode: ListViewItemNode {
insets = itemListNeighborsGroupedInsets(neighbors)
}
if !item.displayDecorations {
insets = UIEdgeInsets()
}
var leftOffset: CGFloat = 0.0
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
if let selected = item.selected {
@ -226,6 +232,11 @@ public class ItemListAddressItemNode: ListViewItemNode {
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
}
strongSelf.topStripeNode.isHidden = !item.displayDecorations
strongSelf.bottomStripeNode.isHidden = !item.displayDecorations
strongSelf.backgroundNode.isHidden = !item.displayDecorations
strongSelf.highlightedBackgroundNode.isHidden = !item.displayDecorations
let _ = labelApply()
let _ = textApply()
let _ = imageApply()
@ -293,7 +304,7 @@ public class ItemListAddressItemNode: ListViewItemNode {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
strongSelf.topStripeNode.isHidden = false
strongSelf.topStripeNode.isHidden = !item.displayDecorations
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat

View File

@ -334,8 +334,9 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
public let tag: ItemListItemTag?
let header: ListViewItemHeader?
let shimmering: ItemListPeerItemShimmering?
let displayDecorations: Bool
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, selectable: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil) {
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: PeerPresence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, selectable: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil, displayDecorations: Bool = true) {
self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
@ -365,6 +366,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
self.tag = tag
self.header = header
self.shimmering = shimmering
self.displayDecorations = displayDecorations
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
@ -932,7 +934,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners || !item.hasTopStripe
strongSelf.topStripeNode.isHidden = !item.displayDecorations || hasCorners || !item.hasTopStripe
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
@ -944,7 +946,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
strongSelf.bottomStripeNode.isHidden = hasCorners || !item.displayDecorations
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
@ -1087,6 +1089,9 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
shimmerNode.removeFromSupernode()
}
strongSelf.backgroundNode.isHidden = !item.displayDecorations
strongSelf.highlightedBackgroundNode.isHidden = !item.displayDecorations
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
strongSelf.setRevealOptions((left: [], right: peerRevealOptions))

View File

@ -335,7 +335,7 @@ func compactStringForGroupPermission(strings: PresentationStrings, right: Telegr
}
}
let allGroupPermissionList: [(TelegramChatBannedRightsFlags, TelegramChannelPermission)] = [
public let allGroupPermissionList: [(TelegramChatBannedRightsFlags, TelegramChannelPermission)] = [
(.banSendMessages, .sendMessages),
(.banSendMedia, .sendMessages),
(.banSendGifs, .sendMessages),

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_camera.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -365,8 +365,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
self.navigationBar?.customBackButtonText = ""
self.blocksBackgroundWhenInOverlay = true
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
@ -5354,6 +5352,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if peer.id == strongSelf.context.account.peerId {
strongSelf.effectiveNavigationController?.pushViewController(PeerMediaCollectionController(context: strongSelf.context, peerId: strongSelf.context.account.peerId))
} else {
var expandAvatar = expandAvatar
if peer.smallProfileImage == nil {
expandAvatar = false
}
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: expandAvatar) {
strongSelf.effectiveNavigationController?.pushViewController(infoController)
}

View File

@ -0,0 +1,122 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import ItemListAddressItem
import SwiftSignalKit
import AccountContext
final class PeerInfoScreenAddressItem: PeerInfoScreenItem {
let id: AnyHashable
let label: String
let text: String
let imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
let action: (() -> Void)?
let longTapAction: (() -> Void)?
let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)?
init(
id: AnyHashable,
label: String,
text: String,
imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?,
action: (() -> Void)?,
longTapAction: (() -> Void)? = nil,
linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil
) {
self.id = id
self.label = label
self.text = text
self.imageSignal = imageSignal
self.action = action
self.longTapAction = longTapAction
self.linkItemAction = linkItemAction
}
func node() -> PeerInfoScreenItemNode {
return PeerInfoScreenAddressItemNode()
}
}
private final class PeerInfoScreenAddressItemNode: PeerInfoScreenItemNode {
private let selectionNode: PeerInfoScreenSelectableBackgroundNode
private let bottomSeparatorNode: ASDisplayNode
private var item: PeerInfoScreenAddressItem?
private var itemNode: ItemListAddressItemNode?
override init() {
var bringToFrontForHighlightImpl: (() -> Void)?
self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() })
self.bottomSeparatorNode = ASDisplayNode()
self.bottomSeparatorNode.isLayerBacked = true
super.init()
bringToFrontForHighlightImpl = { [weak self] in
self?.bringToFrontForHighlight?()
}
self.addSubnode(self.bottomSeparatorNode)
self.addSubnode(self.selectionNode)
}
override func update(width: CGFloat, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat {
guard let item = item as? PeerInfoScreenAddressItem else {
return 10.0
}
self.item = item
self.selectionNode.pressed = item.action
let sideInset: CGFloat = 16.0
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let addressItem = ItemListAddressItem(theme: presentationData.theme, label: item.label, text: item.text, imageSignal: item.imageSignal, sectionId: 0, style: .blocks, displayDecorations: false, action: nil, longTapAction: item.longTapAction, linkItemAction: item.linkItemAction)
let params = ListViewItemLayoutParams(width: width, leftInset: 0.0, rightInset: 0.0, availableHeight: 1000.0)
let itemNode: ItemListAddressItemNode
if let current = self.itemNode {
itemNode = current
addressItem.updateNode(async: { $0() }, node: {
return itemNode
}, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in
let nodeFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: layout.size.height))
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
itemNode.frame = nodeFrame
apply(ListViewItemApply(isOnScreen: true))
})
} else {
var itemNodeValue: ListViewItemNode?
addressItem.nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in
itemNodeValue = node
apply().1(ListViewItemApply(isOnScreen: true))
})
itemNode = itemNodeValue as! ItemListAddressItemNode
itemNode.isUserInteractionEnabled = false
self.itemNode = itemNode
self.addSubnode(itemNode)
}
let height = itemNode.contentSize.height
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(), size: itemNode.bounds.size))
let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel
self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition)
transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset)))
transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel)))
transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0)
return height
}
}

View File

@ -0,0 +1,57 @@
import AsyncDisplayKit
import Display
import TelegramPresentationData
final class PeerInfoScreenHeaderItem: PeerInfoScreenItem {
let id: AnyHashable
let text: String
init(id: AnyHashable, text: String) {
self.id = id
self.text = text
}
func node() -> PeerInfoScreenItemNode {
return PeerInfoScreenHeaderItemNode()
}
}
private final class PeerInfoScreenHeaderItemNode: PeerInfoScreenItemNode {
private let textNode: ImmediateTextNode
private var item: PeerInfoScreenHeaderItem?
override init() {
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.textNode)
}
override func update(width: CGFloat, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat {
guard let item = item as? PeerInfoScreenHeaderItem else {
return 10.0
}
self.item = item
let sideInset: CGFloat = 16.0
let verticalInset: CGFloat = 7.0
self.textNode.maximumNumberOfLines = 0
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(13.0), textColor: presentationData.theme.list.freeTextColor)
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: textSize)
let height = textSize.height + verticalInset * 2.0
transition.updateFrame(node: self.textNode, frame: textFrame)
return height
}
}

View File

@ -0,0 +1,124 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import ItemListPeerItem
import SwiftSignalKit
import AccountContext
import Postbox
import SyncCore
import TelegramCore
import ItemListUI
final class PeerInfoScreenMemberItem: PeerInfoScreenItem {
let id: AnyHashable
let context: AccountContext
let peer: Peer
let presence: TelegramUserPresence?
let action: (() -> Void)?
init(
id: AnyHashable,
context: AccountContext,
peer: Peer,
presence: TelegramUserPresence?,
action: (() -> Void)?
) {
self.id = id
self.context = context
self.peer = peer
self.presence = presence
self.action = action
}
func node() -> PeerInfoScreenItemNode {
return PeerInfoScreenMemberItemNode()
}
}
private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
private let selectionNode: PeerInfoScreenSelectableBackgroundNode
private let bottomSeparatorNode: ASDisplayNode
private var item: PeerInfoScreenMemberItem?
private var itemNode: ItemListPeerItemNode?
override init() {
var bringToFrontForHighlightImpl: (() -> Void)?
self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() })
self.bottomSeparatorNode = ASDisplayNode()
self.bottomSeparatorNode.isLayerBacked = true
super.init()
bringToFrontForHighlightImpl = { [weak self] in
self?.bringToFrontForHighlight?()
}
self.addSubnode(self.bottomSeparatorNode)
self.addSubnode(self.selectionNode)
}
override func update(width: CGFloat, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat {
guard let item = item as? PeerInfoScreenMemberItem else {
return 10.0
}
self.item = item
self.selectionNode.pressed = item.action
let sideInset: CGFloat = 16.0
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let peerItem = ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: item.context, peer: item.peer, height: .peerList, presence: item.presence, text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
}, removePeer: { _ in
}, contextAction: nil, hasTopStripe: false, hasTopGroupInset: false, noInsets: true, displayDecorations: false)
let params = ListViewItemLayoutParams(width: width, leftInset: 0.0, rightInset: 0.0, availableHeight: 1000.0)
let itemNode: ItemListPeerItemNode
if let current = self.itemNode {
itemNode = current
peerItem.updateNode(async: { $0() }, node: {
return itemNode
}, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in
let nodeFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: layout.size.height))
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
itemNode.frame = nodeFrame
apply(ListViewItemApply(isOnScreen: true))
})
} else {
var itemNodeValue: ListViewItemNode?
peerItem.nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in
itemNodeValue = node
apply().1(ListViewItemApply(isOnScreen: true))
})
itemNode = itemNodeValue as! ItemListPeerItemNode
itemNode.isUserInteractionEnabled = false
self.itemNode = itemNode
self.addSubnode(itemNode)
}
let height = itemNode.contentSize.height
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(), size: itemNode.bounds.size))
let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel
self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition)
transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset)))
transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel)))
transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0)
return height
}
}

View File

@ -0,0 +1,196 @@
import AsyncDisplayKit
import Display
import TelegramCore
import SyncCore
import SwiftSignalKit
import Postbox
import TelegramPresentationData
import AccountContext
import ContextUI
import PhotoResources
import TelegramUIPreferences
import ItemListPeerItem
import MergeLists
import ItemListUI
private struct PeerMembersListTransaction {
let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem]
}
private struct PeerMembersListEntry: Comparable, Identifiable {
var index: Int
var member: PeerInfoMember
var stableId: PeerId {
return self.member.id
}
static func ==(lhs: PeerMembersListEntry, rhs: PeerMembersListEntry) -> Bool {
return lhs.member == rhs.member
}
static func <(lhs: PeerMembersListEntry, rhs: PeerMembersListEntry) -> Bool {
return lhs.index < rhs.index
}
func item(context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void) -> ListViewItem {
let member = self.member
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: member.peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: {
openPeer(member.peer)
}, setPeerIdWithRevealedOptions: { _, _ in
}, removePeer: { _ in
}, contextAction: nil/*{ node, gesture in
openPeerContextAction(peer, node, gesture)
}*/, hasTopStripe: false, noInsets: true)
}
}
private func preparedTransition(from fromEntries: [PeerMembersListEntry], to toEntries: [PeerMembersListEntry], context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void) -> PeerMembersListTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, openPeer: openPeer), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, openPeer: openPeer), directionHint: nil) }
return PeerMembersListTransaction(deletions: deletions, insertions: insertions, updates: updates)
}
final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
private let context: AccountContext
private let membersContext: PeerInfoMembersContext
private let listNode: ListView
private var currentEntries: [PeerMembersListEntry] = []
private var currentState: PeerInfoMembersState?
private var canLoadMore: Bool = false
private var enqueuedTransactions: [PeerMembersListTransaction] = []
private var currentParams: (size: CGSize, isScrollingLockedAtTop: Bool, presentationData: PresentationData)?
private let ready = Promise<Bool>()
private var didSetReady: Bool = false
var isReady: Signal<Bool, NoError> {
return self.ready.get()
}
private var disposable: Disposable?
init(context: AccountContext, membersContext: PeerInfoMembersContext) {
self.context = context
self.membersContext = membersContext
self.listNode = ListView()
super.init()
self.listNode.preloadPages = true
self.addSubnode(self.listNode)
self.disposable = (membersContext.state
|> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else {
return
}
strongSelf.currentState = state
if let (_, _, presentationData) = strongSelf.currentParams {
strongSelf.updateState(state: state, presentationData: presentationData)
}
})
self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in
guard let strongSelf = self, let state = strongSelf.currentState, case .ready(true) = state.dataState else {
return
}
if case let .known(value) = offset, value < 100.0 {
strongSelf.membersContext.loadMore()
}
}
}
deinit {
}
func scrollToTop() -> Bool {
if !self.listNode.scrollToOffsetFromTop(0.0) {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
return true
} else {
return false
}
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
let isFirstLayout = self.currentParams == nil
self.currentParams = (size, isScrollingLockedAtTop, presentationData)
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.listNode.scrollEnabled = !isScrollingLockedAtTop
if isFirstLayout, let state = self.currentState {
self.updateState(state: state, presentationData: presentationData)
}
}
private func updateState(state: PeerInfoMembersState, presentationData: PresentationData) {
var entries: [PeerMembersListEntry] = []
for member in state.members {
entries.append(PeerMembersListEntry(index: entries.count, member: member))
}
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in
})
self.currentEntries = entries
self.enqueuedTransactions.append(transaction)
self.dequeueTransaction()
}
private func dequeueTransaction() {
guard let (layout, _, _) = self.currentParams, let transaction = self.enqueuedTransactions.first else {
return
}
self.enqueuedTransactions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions()
options.insert(.Synchronous)
self.listNode.transaction(deleteIndices: transaction.deletions, insertIndicesAndItems: transaction.insertions, updateIndicesAndItems: transaction.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}
if !strongSelf.didSetReady {
strongSelf.didSetReady = true
strongSelf.ready.set(.single(true))
}
})
}
func findLoadedMessage(id: MessageId) -> Message? {
return nil
}
func updateHiddenMedia() {
}
func transferVelocity(_ velocity: CGFloat) {
if velocity > 0.0 {
self.listNode.transferVelocity(velocity)
}
}
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
return nil
}
func addToTransitionSurface(view: UIView) {
}
func updateSelectedMessages(animated: Bool) {
}
}

View File

@ -64,6 +64,7 @@ final class PeerInfoScreenData {
let availablePanes: [PeerInfoPaneKey]
let groupsInCommon: [Peer]?
let linkedDiscussionPeer: Peer?
let members: PeerInfoMembersData?
init(
peer: Peer?,
@ -74,7 +75,8 @@ final class PeerInfoScreenData {
isContact: Bool,
availablePanes: [PeerInfoPaneKey],
groupsInCommon: [Peer]?,
linkedDiscussionPeer: Peer?
linkedDiscussionPeer: Peer?,
members: PeerInfoMembersData?
) {
self.peer = peer
self.cachedData = cachedData
@ -85,6 +87,7 @@ final class PeerInfoScreenData {
self.availablePanes = availablePanes
self.groupsInCommon = groupsInCommon
self.linkedDiscussionPeer = linkedDiscussionPeer
self.members = members
}
}
@ -98,7 +101,7 @@ enum PeerInfoScreenInputData: Equatable {
case none
case user(userId: PeerId, secretChatId: PeerId?, kind: PeerInfoScreenInputUserKind)
case channel
case group(isSupergroup: Bool)
case group(isSupergroup: Bool, membersContext: PeerInfoMembersContext)
}
func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<[PeerInfoPaneKey], NoError> {
@ -144,6 +147,11 @@ struct PeerInfoStatusData: Equatable {
var isActivity: Bool
}
enum PeerInfoMembersData: Equatable {
case shortList([PeerInfoMember])
case longList(PeerInfoMembersContext)
}
func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> Signal<PeerInfoScreenData, NoError> {
return context.account.postbox.combinedView(keys: [.basicPeer(peerId)])
|> map { view -> PeerInfoScreenInputData in
@ -162,12 +170,12 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
return .user(userId: user.id, secretChatId: nil, kind: kind)
} else if let channel = peer as? TelegramChannel {
if case .group = channel.info {
return .group(isSupergroup: true)
return .group(isSupergroup: true, membersContext: PeerInfoMembersContext(context: context, peerId: channel.id))
} else {
return .channel
}
} else if let _ = peer as? TelegramGroup {
return .group(isSupergroup: false)
} else if let group = peer as? TelegramGroup {
return .group(isSupergroup: false, membersContext: PeerInfoMembersContext(context: context, peerId: group.id))
} else {
return .none
}
@ -185,7 +193,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
isContact: false,
availablePanes: [],
groupsInCommon: nil,
linkedDiscussionPeer: nil
linkedDiscussionPeer: nil,
members: nil
))
case let .user(peerId, secretChatId, kind):
let groupsInCommonSignal: Signal<[Peer]?, NoError>
@ -301,8 +310,14 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
}
var availablePanes = availablePanes
if let groupsInCommon = groupsInCommon, !groupsInCommon.isEmpty {
availablePanes.append(.groupsInCommon)
if let groupsInCommon = groupsInCommon {
if !groupsInCommon.isEmpty {
availablePanes.append(.groupsInCommon)
}
} else if let cachedData = peerView.cachedData as? CachedUserData {
if cachedData.commonGroupCount != 0 {
availablePanes.append(.groupsInCommon)
}
}
return PeerInfoScreenData(
@ -314,7 +329,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
isContact: peerView.peerIsContact,
availablePanes: availablePanes,
groupsInCommon: groupsInCommon,
linkedDiscussionPeer: nil
linkedDiscussionPeer: nil,
members: nil
)
}
case .channel:
@ -362,10 +378,11 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
isContact: peerView.peerIsContact,
availablePanes: availablePanes,
groupsInCommon: [],
linkedDiscussionPeer: discussionPeer
linkedDiscussionPeer: discussionPeer,
members: nil
)
}
case .group:
case let .group(_, membersContext):
let status = context.account.viewTracker.peerView(peerId, updateData: false)
|> map { peerView -> PeerInfoStatusData? in
guard let channel = peerView.peers[peerId] as? TelegramChannel else {
@ -379,6 +396,16 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
}
|> distinctUntilChanged
let membersData: Signal<PeerInfoMembersData?, NoError> = membersContext.state
|> map { state -> PeerInfoMembersData? in
if state.members.count > 5 {
return .longList(membersContext)
} else {
return .shortList(state.members)
}
}
|> distinctUntilChanged
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
var combinedKeys: [PostboxViewKey] = []
combinedKeys.append(globalNotificationsKey)
@ -386,9 +413,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId),
context.account.postbox.combinedView(keys: combinedKeys),
status
status,
membersData
)
|> map { peerView, availablePanes, combinedView, status -> PeerInfoScreenData in
|> map { peerView, availablePanes, combinedView, status, membersData -> PeerInfoScreenData in
var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
@ -396,6 +424,16 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
}
}
var discussionPeer: Peer?
if let linkedDiscussionPeerId = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
discussionPeer = peer
}
var availablePanes = availablePanes
if let membersData = membersData, case .longList = membersData {
availablePanes.insert(.members, at: 0)
}
return PeerInfoScreenData(
peer: peerView.peers[peerId],
cachedData: peerView.cachedData,
@ -405,7 +443,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
isContact: peerView.peerIsContact,
availablePanes: availablePanes,
groupsInCommon: [],
linkedDiscussionPeer: nil
linkedDiscussionPeer: discussionPeer,
members: membersData
)
}
}
@ -443,31 +482,19 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?) -> [PeerInf
result.append(.call)
}
result.append(.mute)
if !user.isDeleted, user.botInfo == nil && !user.flags.contains(.isSupport) {
result.append(.more)
}
result.append(.more)
} else if let channel = peer as? TelegramChannel {
var canEditGroupInfo = false
var canEditMembers = false
var canAddMembers = false
var isPublic = false
var isCreator = false
isPublic = channel.username != nil
if !isPublic, let cachedChannelData = cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
isPublic = true
}
isCreator = channel.flags.contains(.isCreator)
if channel.hasPermission(.changeInfo) {
canEditGroupInfo = true
}
if channel.hasPermission(.banMembers) {
canEditMembers = true
}
if channel.hasPermission(.inviteMembers) {
canAddMembers = true
switch channel.info {
case .broadcast:
if let cachedData = cachedData as? CachedChannelData {
if cachedData.linkedDiscussionPeerId != nil {
result.append(.discussion)
}
}
case .group:
if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) {
result.append(.addMember)
}
}
result.append(.mute)

View File

@ -16,6 +16,7 @@ import ActivityIndicator
enum PeerInfoHeaderButtonKey: Hashable {
case message
case discussion
case call
case mute
case more
@ -252,7 +253,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.leftHighlightNode.image = generateImage(CGSize(width: 88.0, height: 1.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let topColor = UIColor(rgb: 0x000000, alpha: 0.5)
let topColor = UIColor(rgb: 0x000000, alpha: 0.1)
let bottomColor = UIColor(rgb: 0x000000, alpha: 0.0)
var locations: [CGFloat] = [0.0, 1.0]
@ -272,7 +273,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.rightHighlightNode.image = generateImage(CGSize(width: 88.0, height: 1.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let topColor = UIColor(rgb: 0x000000, alpha: 0.5)
let topColor = UIColor(rgb: 0x000000, alpha: 0.1)
let bottomColor = UIColor(rgb: 0x000000, alpha: 0.0)
var locations: [CGFloat] = [0.0, 1.0]
@ -287,8 +288,8 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.stripContainerNode = ASDisplayNode()
self.contentNode.addSubnode(self.stripContainerNode)
self.inactiveStripImage = generateStretchableFilledCircleImage(diameter: 3.0, color: UIColor(white: 1.0, alpha: 0.2))!
self.activeStripImage = generateStretchableFilledCircleImage(diameter: 3.0, color: .white)!
self.inactiveStripImage = generateSmallHorizontalStretchableFilledCircleImage(diameter: 2.0, color: UIColor(white: 1.0, alpha: 0.2))!
self.activeStripImage = generateSmallHorizontalStretchableFilledCircleImage(diameter: 2.0, color: .white)!
self.highlightContainerNode = ASDisplayNode()
self.highlightContainerNode.addSubnode(self.leftHighlightNode)
@ -370,11 +371,11 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
var highlightedSide: Bool?
if let point = point {
if point.x < size.width * 1.0 / 5.0 {
if strongSelf.currentIndex != 0 {
if strongSelf.items.count > 1 {
highlightedSide = false
}
} else if point.x > size.width * 4.0 / 5.0 {
if strongSelf.currentIndex < strongSelf.items.count - 1 || strongSelf.items.count > 1 {
if strongSelf.items.count > 1 {
highlightedSide = true
}
}
@ -417,6 +418,9 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
if self.currentIndex != 0 {
self.currentIndex -= 1
self.updateItems(size: size, transition: .immediate)
} else if self.items.count > 1 {
self.currentIndex = self.items.count - 1
self.updateItems(size: size, transition: .immediate)
}
} else if location.x > size.width * 4.0 / 5.0 {
if self.currentIndex < self.items.count - 1 {
@ -559,6 +563,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
}
}
let hadOneStripNode = self.stripNodes.count == 1
if self.stripNodes.count != self.items.count {
if self.stripNodes.count < self.items.count {
for _ in 0 ..< self.items.count - self.stripNodes.count {
@ -591,9 +596,12 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.stripNodes[self.currentIndex].image = self.activeStripImage
}
}
if hadOneStripNode && self.stripNodes.count > 1 {
self.stripContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
let stripInset: CGFloat = 5.0
let stripSpacing: CGFloat = 4.0
let stripWidth: CGFloat = floor((size.width - stripInset * 2.0 - stripSpacing * CGFloat(self.stripNodes.count - 1)) / CGFloat(self.stripNodes.count))
let stripWidth: CGFloat = max(5.0, floor((size.width - stripInset * 2.0 - stripSpacing * CGFloat(self.stripNodes.count - 1)) / CGFloat(self.stripNodes.count)))
var stripX: CGFloat = stripInset
for i in 0 ..< self.stripNodes.count {
if i == 0 && self.stripNodes.count == 1 {
@ -601,7 +609,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} else {
self.stripNodes[i].isHidden = false
}
self.stripNodes[i].frame = CGRect(origin: CGPoint(x: stripX, y: 0.0), size: CGSize(width: stripWidth, height: 3.0))
self.stripNodes[i].frame = CGRect(origin: CGPoint(x: stripX, y: 0.0), size: CGSize(width: stripWidth, height: 2.0))
stripX += stripWidth + stripSpacing
}
@ -1520,7 +1528,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.controlsContainerNode, frame: CGRect(origin: CGPoint(x: -controlsClippingFrame.minX, y: -controlsClippingFrame.minY), size: CGSize(width: expandedAvatarListSize.width, height: expandedAvatarListSize.height)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.shadowNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: expandedAvatarListSize.width, height: navigationHeight + 20.0)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.stripContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusBarHeight + 2.0), size: CGSize(width: expandedAvatarListSize.width, height: 3.0)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.stripContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusBarHeight + 2.0), size: CGSize(width: expandedAvatarListSize.width, height: 2.0)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.highlightContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: expandedAvatarListSize.width, height: expandedAvatarListSize.height)))
transition.updateAlpha(node: self.avatarListNode.listContainerNode.controlsContainerNode, alpha: self.isAvatarExpanded ? (1.0 - transitionFraction) : 0.0)
@ -1681,6 +1689,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
case .message:
buttonText = "Message"
buttonIcon = .message
case .discussion:
buttonText = "Discussion"
buttonIcon = .message
case .call:
buttonText = "Call"
buttonIcon = .call

View File

@ -0,0 +1,153 @@
import Foundation
import SwiftSignalKit
import Postbox
import SyncCore
import TelegramCore
import AccountContext
import TemporaryCachedPeerDataManager
enum PeerInfoMember: Equatable {
case channelMember(RenderedChannelParticipant)
var id: PeerId {
switch self {
case let .channelMember(channelMember):
return channelMember.peer.id
}
}
var peer: Peer {
switch self {
case let .channelMember(channelMember):
return channelMember.peer
}
}
var presence: TelegramUserPresence? {
switch self {
case let .channelMember(channelMember):
return channelMember.presences[channelMember.peer.id] as? TelegramUserPresence
}
}
}
enum PeerInfoMembersDataState: Equatable {
case loading(isInitial: Bool)
case ready(canLoadMore: Bool)
}
struct PeerInfoMembersState: Equatable {
var members: [PeerInfoMember]
var dataState: PeerInfoMembersDataState
}
private final class PeerInfoMembersContextImpl {
private let queue: Queue
private let context: AccountContext
private let peerId: PeerId
private var members: [PeerInfoMember] = []
private var dataState: PeerInfoMembersDataState = .loading(isInitial: true)
private let stateValue = Promise<PeerInfoMembersState>()
var state: Signal<PeerInfoMembersState, NoError> {
return self.stateValue.get()
}
private let disposable = MetaDisposable()
private var channelMembersControl: PeerChannelMemberCategoryControl?
init(queue: Queue, context: AccountContext, peerId: PeerId) {
self.queue = queue
self.context = context
self.peerId = peerId
self.pushState()
if peerId.namespace == Namespaces.Peer.CloudChannel {
let (disposable, control) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { [weak self] state in
queue.async {
guard let strongSelf = self else {
return
}
strongSelf.members = state.list.map(PeerInfoMember.channelMember)
switch state.loadingState {
case let .loading(initial):
strongSelf.dataState = .loading(isInitial: initial)
case let .ready(hasMore):
strongSelf.dataState = .ready(canLoadMore: hasMore)
}
strongSelf.pushState()
}
})
self.disposable.set(disposable)
self.channelMembersControl = control
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
disposable.set((context.account.postbox.peerView(id: peerId)
|> deliverOn(self.queue)).start(next: { [weak self] view in
guard let strongSelf = self, let cachedData = view.cachedData as? CachedGroupData, let participantsData = cachedData.participants else {
return
}
var members: [PeerInfoMember] = []
for participant in participantsData.participants {
if let peer = view.peers[participant.peerId] {
}
}
strongSelf.dataState = .ready(canLoadMore: false)
strongSelf.pushState()
}))
} else {
self.dataState = .ready(canLoadMore: false)
self.pushState()
}
}
deinit {
self.disposable.dispose()
}
private func pushState() {
self.stateValue.set(.single(PeerInfoMembersState(members: self.members, dataState: self.dataState)))
}
func loadMore() {
if case .ready(true) = self.dataState, let channelMembersControl = self.channelMembersControl {
self.context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: self.peerId, control: channelMembersControl)
}
}
}
final class PeerInfoMembersContext: Equatable {
private let queue = Queue.mainQueue()
private let impl: QueueLocalObject<PeerInfoMembersContextImpl>
var state: Signal<PeerInfoMembersState, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.state.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
init(context: AccountContext, peerId: PeerId) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return PeerInfoMembersContextImpl(queue: queue, context: context, peerId: peerId)
})
}
func loadMore() {
self.impl.with { impl in
impl.loadMore()
}
}
static func ==(lhs: PeerInfoMembersContext, rhs: PeerInfoMembersContext) -> Bool {
return lhs === rhs
}
}

View File

@ -51,6 +51,7 @@ enum PeerInfoPaneKey {
case voice
case music
case groupsInCommon
case members
}
final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
@ -449,6 +450,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode {
paneNode = PeerInfoListPaneNode(context: self.context, chatControllerInteraction: self.chatControllerInteraction!, peerId: self.peerId, tagMask: .music)
case .groupsInCommon:
paneNode = PeerInfoGroupsInCommonPaneNode(context: self.context, peerId: peerId, chatControllerInteraction: self.chatControllerInteraction!, openPeerContextAction: self.openPeerContextAction!, peers: data?.groupsInCommon ?? [])
case .members:
if case let .longList(membersContext) = data?.members {
paneNode = PeerInfoMembersPaneNode(context: self.context, membersContext: membersContext)
} else {
preconditionFailure()
}
}
let disposable = MetaDisposable()
@ -533,6 +540,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode {
title = "Audio"
case .groupsInCommon:
title = "Groups"
case .members:
title = "Members"
}
return PeerInfoPaneSpecifier(key: key, title: title)
}, selectedPane: self.currentPaneKey, transition: transition)