mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Temp
This commit is contained in:
parent
fd9be238d2
commit
76893115f6
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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),
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Avatar/EditAvatarIconLarge.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Avatar/EditAvatarIconLarge.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_camera.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Avatar/EditAvatarIconLarge.imageset/ic_camera.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Avatar/EditAvatarIconLarge.imageset/ic_camera.pdf
vendored
Normal file
Binary file not shown.
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
153
submodules/TelegramUI/TelegramUI/PeerInfo/PeerInfoMembers.swift
Normal file
153
submodules/TelegramUI/TelegramUI/PeerInfo/PeerInfoMembers.swift
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user