mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
2575b0e2f1
commit
008b52a250
@ -665,8 +665,8 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public let contentNode: ContentNode
|
public let contentNode: ContentNode
|
||||||
private var storyIndicatorTheme: PresentationTheme?
|
|
||||||
private var storyIndicator: ComponentView<Empty>?
|
private var storyIndicator: ComponentView<Empty>?
|
||||||
|
public private(set) var storyPresentationParams: StoryPresentationParams?
|
||||||
|
|
||||||
public struct StoryStats: Equatable {
|
public struct StoryStats: Equatable {
|
||||||
public var totalCount: Int
|
public var totalCount: Int
|
||||||
@ -735,9 +735,7 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let storyIndicatorTheme = self.storyIndicatorTheme {
|
self.updateStoryIndicator(transition: .immediate)
|
||||||
self.updateStoryIndicator(theme: storyIndicatorTheme, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addSubnode(self.contentNode)
|
self.addSubnode(self.contentNode)
|
||||||
@ -766,9 +764,7 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.contentNode.updateSize(size: size)
|
self.contentNode.updateSize(size: size)
|
||||||
|
|
||||||
if let storyIndicatorTheme = self.storyIndicatorTheme {
|
self.updateStoryIndicator(transition: .immediate)
|
||||||
self.updateStoryIndicator(theme: storyIndicatorTheme, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func playArchiveAnimation() {
|
public func playArchiveAnimation() {
|
||||||
@ -807,51 +803,71 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
self.contentNode.setCustomLetters(letters, explicitColor: explicitColor, icon: icon)
|
self.contentNode.setCustomLetters(letters, explicitColor: explicitColor, icon: icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setStoryStats(storyStats: StoryStats?, theme: PresentationTheme, transition: Transition) {
|
public func setStoryStats(storyStats: StoryStats?, presentationParams: StoryPresentationParams, transition: Transition) {
|
||||||
if self.storyStats != storyStats || self.storyIndicatorTheme !== theme {
|
if self.storyStats != storyStats || self.storyPresentationParams != presentationParams {
|
||||||
self.storyStats = storyStats
|
self.storyStats = storyStats
|
||||||
self.storyIndicatorTheme = theme
|
self.storyPresentationParams = presentationParams
|
||||||
|
|
||||||
self.updateStoryIndicator(theme: theme, transition: transition)
|
self.updateStoryIndicator(transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct StoryIndicatorParams {
|
public struct Colors: Equatable {
|
||||||
let lineWidth: CGFloat
|
public var unseenColors: [UIColor]
|
||||||
let indicatorSize: CGSize
|
public var unseenCloseFriendsColors: [UIColor]
|
||||||
let avatarScale: CGFloat
|
public var seenColors: [UIColor]
|
||||||
|
|
||||||
init(lineWidth: CGFloat, indicatorSize: CGSize, avatarScale: CGFloat) {
|
public init(
|
||||||
|
unseenColors: [UIColor],
|
||||||
|
unseenCloseFriendsColors: [UIColor],
|
||||||
|
seenColors: [UIColor]
|
||||||
|
) {
|
||||||
|
self.unseenColors = unseenColors
|
||||||
|
self.unseenCloseFriendsColors = unseenCloseFriendsColors
|
||||||
|
self.seenColors = seenColors
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(theme: PresentationTheme) {
|
||||||
|
self.unseenColors = [theme.chatList.storyUnseenColors.topColor, theme.chatList.storyUnseenColors.bottomColor]
|
||||||
|
self.unseenCloseFriendsColors = [theme.chatList.storyUnseenPrivateColors.topColor, theme.chatList.storyUnseenPrivateColors.bottomColor]
|
||||||
|
self.seenColors = [theme.chatList.storySeenColors.topColor, theme.chatList.storySeenColors.bottomColor]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct StoryPresentationParams: Equatable {
|
||||||
|
public var colors: Colors
|
||||||
|
public var lineWidth: CGFloat
|
||||||
|
public var inactiveLineWidth: CGFloat
|
||||||
|
|
||||||
|
public init(
|
||||||
|
colors: Colors,
|
||||||
|
lineWidth: CGFloat,
|
||||||
|
inactiveLineWidth: CGFloat
|
||||||
|
) {
|
||||||
|
self.colors = colors
|
||||||
self.lineWidth = lineWidth
|
self.lineWidth = lineWidth
|
||||||
self.indicatorSize = indicatorSize
|
self.inactiveLineWidth = inactiveLineWidth
|
||||||
self.avatarScale = avatarScale
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func storyIndicatorParams(size: CGSize) -> StoryIndicatorParams {
|
private func updateStoryIndicator(transition: Transition) {
|
||||||
let lineWidth: CGFloat = 2.0
|
|
||||||
|
|
||||||
return StoryIndicatorParams(
|
|
||||||
lineWidth: lineWidth,
|
|
||||||
indicatorSize: CGSize(width: size.width - lineWidth * 4.0, height: size.height - lineWidth * 4.0),
|
|
||||||
avatarScale: (size.width - lineWidth * 4.0) / size.width
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateStoryIndicator(theme: PresentationTheme, transition: Transition) {
|
|
||||||
if !self.isNodeLoaded {
|
if !self.isNodeLoaded {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.bounds.isEmpty {
|
if self.bounds.isEmpty {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
guard let storyPresentationParams = self.storyPresentationParams else {
|
||||||
self.storyIndicatorTheme = theme
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let size = self.bounds.size
|
let size = self.bounds.size
|
||||||
|
|
||||||
if let storyStats = self.storyStats {
|
if let storyStats = self.storyStats {
|
||||||
let indicatorParams = self.storyIndicatorParams(size: size)
|
let activeLineWidth = storyPresentationParams.lineWidth
|
||||||
|
let inactiveLineWidth = storyPresentationParams.inactiveLineWidth
|
||||||
|
let indicatorSize = CGSize(width: size.width - activeLineWidth * 4.0, height: size.height - activeLineWidth * 4.0)
|
||||||
|
let avatarScale = (size.width - activeLineWidth * 4.0) / size.width
|
||||||
|
|
||||||
let storyIndicator: ComponentView<Empty>
|
let storyIndicator: ComponentView<Empty>
|
||||||
var indicatorTransition = transition
|
var indicatorTransition = transition
|
||||||
@ -867,25 +883,28 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||||
hasUnseen: storyStats.unseenCount != 0,
|
hasUnseen: storyStats.unseenCount != 0,
|
||||||
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriendsItems,
|
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriendsItems,
|
||||||
theme: theme,
|
colors: AvatarStoryIndicatorComponent.Colors(
|
||||||
activeLineWidth: indicatorParams.lineWidth,
|
unseenColors: storyPresentationParams.colors.unseenColors,
|
||||||
inactiveLineWidth: indicatorParams.lineWidth,
|
unseenCloseFriendsColors: storyPresentationParams.colors.unseenCloseFriendsColors,
|
||||||
isGlassBackground: false,
|
seenColors: storyPresentationParams.colors.seenColors
|
||||||
|
),
|
||||||
|
activeLineWidth: activeLineWidth,
|
||||||
|
inactiveLineWidth: inactiveLineWidth,
|
||||||
counters: AvatarStoryIndicatorComponent.Counters(
|
counters: AvatarStoryIndicatorComponent.Counters(
|
||||||
totalCount: storyStats.totalCount,
|
totalCount: storyStats.totalCount,
|
||||||
unseenCount: storyStats.unseenCount
|
unseenCount: storyStats.unseenCount
|
||||||
)
|
)
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: indicatorParams.indicatorSize
|
containerSize: indicatorSize
|
||||||
)
|
)
|
||||||
if let storyIndicatorView = storyIndicator.view {
|
if let storyIndicatorView = storyIndicator.view {
|
||||||
if storyIndicatorView.superview == nil {
|
if storyIndicatorView.superview == nil {
|
||||||
self.view.addSubview(storyIndicatorView)
|
self.view.addSubview(storyIndicatorView)
|
||||||
}
|
}
|
||||||
indicatorTransition.setFrame(view: storyIndicatorView, frame: CGRect(origin: CGPoint(x: (size.width - indicatorParams.indicatorSize.width) * 0.5, y: (size.height - indicatorParams.indicatorSize.height) * 0.5), size: indicatorParams.indicatorSize))
|
indicatorTransition.setFrame(view: storyIndicatorView, frame: CGRect(origin: CGPoint(x: (size.width - indicatorSize.width) * 0.5, y: (size.height - indicatorSize.height) * 0.5), size: indicatorSize))
|
||||||
}
|
}
|
||||||
transition.setScale(view: self.contentNode.view, scale: indicatorParams.avatarScale)
|
transition.setScale(view: self.contentNode.view, scale: avatarScale)
|
||||||
} else {
|
} else {
|
||||||
transition.setScale(view: self.contentNode.view, scale: 1.0)
|
transition.setScale(view: self.contentNode.view, scale: 1.0)
|
||||||
if let storyIndicator = self.storyIndicator {
|
if let storyIndicator = self.storyIndicator {
|
||||||
|
@ -2841,7 +2841,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||||
hasUnseen: storyState.stats.unseenCount != 0,
|
hasUnseen: storyState.stats.unseenCount != 0,
|
||||||
hasUnseenCloseFriendsItems: storyState.hasUnseenCloseFriends,
|
hasUnseenCloseFriendsItems: storyState.hasUnseenCloseFriends,
|
||||||
theme: item.presentationData.theme,
|
colors: AvatarStoryIndicatorComponent.Colors(theme: item.presentationData.theme),
|
||||||
activeLineWidth: 2.33,
|
activeLineWidth: 2.33,
|
||||||
inactiveLineWidth: 1.33,
|
inactiveLineWidth: 1.33,
|
||||||
counters: AvatarStoryIndicatorComponent.Counters(
|
counters: AvatarStoryIndicatorComponent.Counters(
|
||||||
|
@ -1115,7 +1115,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||||
hasUnseen: storyStats.unseen != 0,
|
hasUnseen: storyStats.unseen != 0,
|
||||||
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends,
|
hasUnseenCloseFriendsItems: storyStats.hasUnseenCloseFriends,
|
||||||
theme: item.presentationData.theme,
|
colors: AvatarStoryIndicatorComponent.Colors(theme: item.presentationData.theme),
|
||||||
activeLineWidth: 1.0 + UIScreenPixel,
|
activeLineWidth: 1.0 + UIScreenPixel,
|
||||||
inactiveLineWidth: 1.0 + UIScreenPixel,
|
inactiveLineWidth: 1.0 + UIScreenPixel,
|
||||||
counters: AvatarStoryIndicatorComponent.Counters(totalCount: storyStats.total, unseenCount: storyStats.unseen)
|
counters: AvatarStoryIndicatorComponent.Counters(totalCount: storyStats.total, unseenCount: storyStats.unseen)
|
||||||
|
@ -355,8 +355,10 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
|||||||
let shimmering: ItemListPeerItemShimmering?
|
let shimmering: ItemListPeerItemShimmering?
|
||||||
let displayDecorations: Bool
|
let displayDecorations: Bool
|
||||||
let disableInteractiveTransitionIfNecessary: Bool
|
let disableInteractiveTransitionIfNecessary: Bool
|
||||||
|
let storyStats: PeerStoryStats?
|
||||||
|
let openStories: ((UIView) -> Void)?
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, threadInfo: EngineMessageHistoryThread.Info? = nil, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, highlighted: Bool = false, selectable: Bool, highlightable: Bool = true, animateFirstAvatarTransition: Bool = true, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, noCorners: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil, displayDecorations: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false) {
|
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, threadInfo: EngineMessageHistoryThread.Info? = nil, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, highlighted: Bool = false, selectable: Bool, highlightable: Bool = true, animateFirstAvatarTransition: Bool = true, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, noCorners: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil, displayDecorations: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false, storyStats: PeerStoryStats? = nil, openStories: ((UIView) -> Void)? = nil) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
@ -393,6 +395,8 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
|||||||
self.shimmering = shimmering
|
self.shimmering = shimmering
|
||||||
self.displayDecorations = displayDecorations
|
self.displayDecorations = displayDecorations
|
||||||
self.disableInteractiveTransitionIfNecessary = disableInteractiveTransitionIfNecessary
|
self.disableInteractiveTransitionIfNecessary = disableInteractiveTransitionIfNecessary
|
||||||
|
self.storyStats = storyStats
|
||||||
|
self.openStories = openStories
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
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) {
|
||||||
@ -471,6 +475,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
private var avatarIconComponent: EmojiStatusComponent?
|
private var avatarIconComponent: EmojiStatusComponent?
|
||||||
private var avatarIconView: ComponentView<Empty>?
|
private var avatarIconView: ComponentView<Empty>?
|
||||||
|
|
||||||
|
private var avatarButton: HighlightTrackingButton?
|
||||||
|
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let labelNode: TextNode
|
private let labelNode: TextNode
|
||||||
private let labelBadgeNode: ASImageNode
|
private let labelBadgeNode: ASImageNode
|
||||||
@ -1250,6 +1256,22 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
let avatarFrame = CGRect(origin: CGPoint(x: params.leftInset + additionalLeftInset + revealOffset + editingOffset + 15.0, y: floorToScreenPixels((layout.contentSize.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
|
let avatarFrame = CGRect(origin: CGPoint(x: params.leftInset + additionalLeftInset + revealOffset + editingOffset + 15.0, y: floorToScreenPixels((layout.contentSize.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
|
||||||
transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame)
|
transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame)
|
||||||
|
|
||||||
|
if item.storyStats != nil {
|
||||||
|
let avatarButton: HighlightTrackingButton
|
||||||
|
if let current = strongSelf.avatarButton {
|
||||||
|
avatarButton = current
|
||||||
|
} else {
|
||||||
|
avatarButton = HighlightTrackingButton()
|
||||||
|
strongSelf.avatarButton = avatarButton
|
||||||
|
strongSelf.containerNode.view.addSubview(avatarButton)
|
||||||
|
avatarButton.addTarget(strongSelf, action: #selector(strongSelf.avatarButtonPressed), for: .touchUpInside)
|
||||||
|
}
|
||||||
|
avatarButton.frame = avatarFrame
|
||||||
|
} else if let avatarButton = strongSelf.avatarButton {
|
||||||
|
strongSelf.avatarButton = nil
|
||||||
|
avatarButton.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
if let switchValue = item.switchValue, case .leftCheck = switchValue.style {
|
if let switchValue = item.switchValue, case .leftCheck = switchValue.style {
|
||||||
let leftCheckNode: CheckNode
|
let leftCheckNode: CheckNode
|
||||||
if let current = strongSelf.leftCheckNode {
|
if let current = strongSelf.leftCheckNode {
|
||||||
@ -1332,6 +1354,17 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
clipStyle = .roundedRect
|
clipStyle = .roundedRect
|
||||||
}
|
}
|
||||||
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: clipStyle, synchronousLoad: synchronousLoad)
|
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: clipStyle, synchronousLoad: synchronousLoad)
|
||||||
|
strongSelf.avatarNode.setStoryStats(storyStats: item.storyStats.flatMap { storyStats in
|
||||||
|
return AvatarNode.StoryStats(
|
||||||
|
totalCount: storyStats.totalCount,
|
||||||
|
unseenCount: storyStats.unseenCount,
|
||||||
|
hasUnseenCloseFriendsItems: false
|
||||||
|
)
|
||||||
|
}, presentationParams: AvatarNode.StoryPresentationParams(
|
||||||
|
colors: AvatarNode.Colors(theme: item.presentationData.theme),
|
||||||
|
lineWidth: 1.33,
|
||||||
|
inactiveLineWidth: 1.33
|
||||||
|
), transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1444,10 +1477,14 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
||||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||||
|
|
||||||
|
if let avatarButton = self.avatarButton, avatarButton.bounds.contains(self.view.convert(point, to: avatarButton)) {
|
||||||
|
self.isHighlighted = false
|
||||||
|
} else {
|
||||||
self.isHighlighted = highlighted
|
self.isHighlighted = highlighted
|
||||||
|
|
||||||
self.updateIsHighlighted(transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
|
self.updateIsHighlighted(transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
@ -1513,7 +1550,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
|
|
||||||
transition.updateFrame(node: self.labelBadgeNode, frame: CGRect(origin: CGPoint(x: offset + params.width - rightLabelInset - badgeWidth, y: self.labelBadgeNode.frame.minY), size: CGSize(width: badgeWidth, height: badgeDiameter)))
|
transition.updateFrame(node: self.labelBadgeNode, frame: CGRect(origin: CGPoint(x: offset + params.width - rightLabelInset - badgeWidth, y: self.labelBadgeNode.frame.minY), size: CGSize(width: badgeWidth, height: badgeDiameter)))
|
||||||
|
|
||||||
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + editingOffset + params.leftInset + 15.0, y: self.avatarNode.frame.minY), size: self.avatarNode.bounds.size))
|
let avatarFrame = CGRect(origin: CGPoint(x: revealOffset + editingOffset + params.leftInset + 15.0, y: self.avatarNode.frame.minY), size: self.avatarNode.bounds.size)
|
||||||
|
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||||
|
if let avatarButton = self.avatarButton {
|
||||||
|
avatarButton.frame = avatarFrame
|
||||||
|
}
|
||||||
|
|
||||||
if let avatarIconComponentView = self.avatarIconView?.view {
|
if let avatarIconComponentView = self.avatarIconView?.view {
|
||||||
let avatarFrame = self.avatarNode.frame
|
let avatarFrame = self.avatarNode.frame
|
||||||
@ -1580,6 +1621,13 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func avatarButtonPressed() {
|
||||||
|
guard let item = self.layoutParams?.0 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.openStories?(self.avatarNode.view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ItemListPeerItemHeader: ListViewItemHeader {
|
public final class ItemListPeerItemHeader: ListViewItemHeader {
|
||||||
|
76
submodules/Postbox/Sources/PeerStoryStatsView.swift
Normal file
76
submodules/Postbox/Sources/PeerStoryStatsView.swift
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class MutablePeerStoryStatsView: MutablePostboxView {
|
||||||
|
let peerIds: Set<PeerId>
|
||||||
|
var storyStats: [PeerId: PeerStoryStats] = [:]
|
||||||
|
|
||||||
|
init(postbox: PostboxImpl, peerIds: Set<PeerId>) {
|
||||||
|
self.peerIds = peerIds
|
||||||
|
for id in self.peerIds {
|
||||||
|
if let value = fetchPeerStoryStats(postbox: postbox, peerId: id) {
|
||||||
|
self.storyStats[id] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool {
|
||||||
|
var updated = false
|
||||||
|
var updatedPeerIds = Set<PeerId>()
|
||||||
|
for event in transaction.currentStoryTopItemEvents {
|
||||||
|
if case let .replace(peerId) = event {
|
||||||
|
if self.peerIds.contains(peerId) {
|
||||||
|
updatedPeerIds.insert(peerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for event in transaction.storyPeerStatesEvents {
|
||||||
|
if case let .set(key) = event, case let .peer(peerId) = key {
|
||||||
|
if self.peerIds.contains(peerId) {
|
||||||
|
updatedPeerIds.insert(peerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in updatedPeerIds {
|
||||||
|
let value = fetchPeerStoryStats(postbox: postbox, peerId: id)
|
||||||
|
if self.storyStats[id] != value {
|
||||||
|
updated = true
|
||||||
|
|
||||||
|
if let value = value {
|
||||||
|
self.storyStats[id] = value
|
||||||
|
} else {
|
||||||
|
self.storyStats.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool {
|
||||||
|
var storyStats: [PeerId: PeerStoryStats] = [:]
|
||||||
|
for id in self.peerIds {
|
||||||
|
if let value = fetchPeerStoryStats(postbox: postbox, peerId: id) {
|
||||||
|
storyStats[id] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.storyStats != storyStats {
|
||||||
|
self.storyStats = storyStats
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func immutableView() -> PostboxView {
|
||||||
|
return PeerStoryStatsView(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class PeerStoryStatsView: PostboxView {
|
||||||
|
public let storyStats: [PeerId: PeerStoryStats]
|
||||||
|
|
||||||
|
init(_ view: MutablePeerStoryStatsView) {
|
||||||
|
self.storyStats = view.storyStats
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,9 @@ public struct PeerViewComponents: OptionSet {
|
|||||||
public static let subPeers = PeerViewComponents(rawValue: 1 << 1)
|
public static let subPeers = PeerViewComponents(rawValue: 1 << 1)
|
||||||
public static let messages = PeerViewComponents(rawValue: 1 << 2)
|
public static let messages = PeerViewComponents(rawValue: 1 << 2)
|
||||||
public static let groupId = PeerViewComponents(rawValue: 1 << 3)
|
public static let groupId = PeerViewComponents(rawValue: 1 << 3)
|
||||||
|
public static let storyStats = PeerViewComponents(rawValue: 1 << 4)
|
||||||
|
|
||||||
public static let all: PeerViewComponents = [.cachedData, .subPeers, .messages, .groupId]
|
public static let all: PeerViewComponents = [.cachedData, .subPeers, .messages, .groupId, .storyStats]
|
||||||
}
|
}
|
||||||
|
|
||||||
final class MutablePeerView: MutablePostboxView {
|
final class MutablePeerView: MutablePostboxView {
|
||||||
@ -27,6 +28,8 @@ final class MutablePeerView: MutablePostboxView {
|
|||||||
var media: [MediaId: Media] = [:]
|
var media: [MediaId: Media] = [:]
|
||||||
var peerIsContact: Bool
|
var peerIsContact: Bool
|
||||||
var groupId: PeerGroupId?
|
var groupId: PeerGroupId?
|
||||||
|
var storyStats: PeerStoryStats?
|
||||||
|
var memberStoryStats: [PeerId: PeerStoryStats] = [:]
|
||||||
|
|
||||||
init(postbox: PostboxImpl, peerId: PeerId, components: PeerViewComponents) {
|
init(postbox: PostboxImpl, peerId: PeerId, components: PeerViewComponents) {
|
||||||
self.components = components
|
self.components = components
|
||||||
@ -54,8 +57,10 @@ final class MutablePeerView: MutablePostboxView {
|
|||||||
}
|
}
|
||||||
self.cachedData = postbox.cachedPeerDataTable.get(contactPeerId)
|
self.cachedData = postbox.cachedPeerDataTable.get(contactPeerId)
|
||||||
self.peerIsContact = postbox.contactsTable.isContact(peerId: self.contactPeerId)
|
self.peerIsContact = postbox.contactsTable.isContact(peerId: self.contactPeerId)
|
||||||
|
var cachedDataPeerIds = Set<PeerId>()
|
||||||
if let cachedData = self.cachedData {
|
if let cachedData = self.cachedData {
|
||||||
peerIds.formUnion(cachedData.peerIds)
|
cachedDataPeerIds = cachedData.peerIds
|
||||||
|
peerIds.formUnion(cachedDataPeerIds)
|
||||||
messageIds.formUnion(cachedData.messageIds)
|
messageIds.formUnion(cachedData.messageIds)
|
||||||
}
|
}
|
||||||
for id in peerIds {
|
for id in peerIds {
|
||||||
@ -66,6 +71,11 @@ final class MutablePeerView: MutablePostboxView {
|
|||||||
self.peerPresences[id] = presence
|
self.peerPresences[id] = presence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for id in cachedDataPeerIds {
|
||||||
|
if let value = fetchPeerStoryStats(postbox: postbox, peerId: id) {
|
||||||
|
self.memberStoryStats[id] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
if let peer = self.peers[peerId], let associatedPeerId = peer.associatedPeerId {
|
if let peer = self.peers[peerId], let associatedPeerId = peer.associatedPeerId {
|
||||||
if let peer = getPeer(associatedPeerId) {
|
if let peer = getPeer(associatedPeerId) {
|
||||||
self.peers[associatedPeerId] = peer
|
self.peers[associatedPeerId] = peer
|
||||||
@ -83,6 +93,10 @@ final class MutablePeerView: MutablePostboxView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.media = renderAssociatedMediaForPeers(postbox: postbox, peers: self.peers)
|
self.media = renderAssociatedMediaForPeers(postbox: postbox, peers: self.peers)
|
||||||
|
|
||||||
|
if components.contains(.storyStats) {
|
||||||
|
self.storyStats = fetchPeerStoryStats(postbox: postbox, peerId: self.peerId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset(postbox: PostboxImpl) -> Bool {
|
func reset(postbox: PostboxImpl) -> Bool {
|
||||||
@ -260,6 +274,53 @@ final class MutablePeerView: MutablePostboxView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.components.contains(.storyStats) {
|
||||||
|
var refreshStoryStats = false
|
||||||
|
for event in transaction.currentStoryTopItemEvents {
|
||||||
|
if case .replace(peerId: self.peerId) = event {
|
||||||
|
refreshStoryStats = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !refreshStoryStats {
|
||||||
|
for event in transaction.storyPeerStatesEvents {
|
||||||
|
if case .set(.peer(self.peerId)) = event {
|
||||||
|
refreshStoryStats = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if refreshStoryStats {
|
||||||
|
self.storyStats = fetchPeerStoryStats(postbox: postbox, peerId: self.peerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !transaction.storyPeerStatesEvents.isEmpty || !transaction.currentStoryTopItemEvents.isEmpty {
|
||||||
|
if let cachedData = self.cachedData {
|
||||||
|
var updatedPeerIds = Set<PeerId>()
|
||||||
|
let cachedDataPeerIds = cachedData.peerIds
|
||||||
|
for event in transaction.currentStoryTopItemEvents {
|
||||||
|
if case let .replace(id) = event, cachedDataPeerIds.contains(id) {
|
||||||
|
updatedPeerIds.insert(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for event in transaction.storyPeerStatesEvents {
|
||||||
|
if case let .set(key) = event, case let .peer(id) = key, cachedDataPeerIds.contains(id) {
|
||||||
|
updatedPeerIds.insert(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in updatedPeerIds {
|
||||||
|
let value = fetchPeerStoryStats(postbox: postbox, peerId: id)
|
||||||
|
if self.memberStoryStats[id] != value {
|
||||||
|
updated = true
|
||||||
|
if let value = value {
|
||||||
|
self.memberStoryStats[id] = value
|
||||||
|
} else {
|
||||||
|
self.memberStoryStats.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return updated
|
return updated
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,6 +343,8 @@ public final class PeerView: PostboxView {
|
|||||||
public let media: [MediaId: Media]
|
public let media: [MediaId: Media]
|
||||||
public let peerIsContact: Bool
|
public let peerIsContact: Bool
|
||||||
public let groupId: PeerGroupId?
|
public let groupId: PeerGroupId?
|
||||||
|
public let storyStats: PeerStoryStats?
|
||||||
|
public let memberStoryStats: [PeerId: PeerStoryStats]
|
||||||
|
|
||||||
init(_ mutableView: MutablePeerView) {
|
init(_ mutableView: MutablePeerView) {
|
||||||
self.peerId = mutableView.peerId
|
self.peerId = mutableView.peerId
|
||||||
@ -293,5 +356,7 @@ public final class PeerView: PostboxView {
|
|||||||
self.media = mutableView.media
|
self.media = mutableView.media
|
||||||
self.peerIsContact = mutableView.peerIsContact
|
self.peerIsContact = mutableView.peerIsContact
|
||||||
self.groupId = mutableView.groupId
|
self.groupId = mutableView.groupId
|
||||||
|
self.storyStats = mutableView.storyStats
|
||||||
|
self.memberStoryStats = mutableView.memberStoryStats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1327,6 +1327,10 @@ public final class Transaction {
|
|||||||
public func getExpiredStoryIds(belowTimestamp: Int32) -> [StoryId] {
|
public func getExpiredStoryIds(belowTimestamp: Int32) -> [StoryId] {
|
||||||
return self.postbox!.storyItemsTable.getExpiredIds(belowTimestamp: belowTimestamp)
|
return self.postbox!.storyItemsTable.getExpiredIds(belowTimestamp: belowTimestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getPeerStoryStats(peerId: PeerId) -> PeerStoryStats? {
|
||||||
|
return fetchPeerStoryStats(postbox: self.postbox!, peerId: peerId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PostboxResult {
|
public enum PostboxResult {
|
||||||
|
@ -44,6 +44,7 @@ public enum PostboxViewKey: Hashable {
|
|||||||
case storiesState(key: PostboxStoryStatesKey)
|
case storiesState(key: PostboxStoryStatesKey)
|
||||||
case storyItems(peerId: PeerId)
|
case storyItems(peerId: PeerId)
|
||||||
case storyExpirationTimeItems
|
case storyExpirationTimeItems
|
||||||
|
case peerStoryStats(peerIds: Set<PeerId>)
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -147,6 +148,8 @@ public enum PostboxViewKey: Hashable {
|
|||||||
hasher.combine(peerId)
|
hasher.combine(peerId)
|
||||||
case .storyExpirationTimeItems:
|
case .storyExpirationTimeItems:
|
||||||
hasher.combine(19)
|
hasher.combine(19)
|
||||||
|
case let .peerStoryStats(peerIds):
|
||||||
|
hasher.combine(peerIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,6 +413,12 @@ public enum PostboxViewKey: Hashable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case let .peerStoryStats(peerIds):
|
||||||
|
if case .peerStoryStats(peerIds) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,5 +511,7 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost
|
|||||||
return MutableStoryItemsView(postbox: postbox, peerId: peerId)
|
return MutableStoryItemsView(postbox: postbox, peerId: peerId)
|
||||||
case .storyExpirationTimeItems:
|
case .storyExpirationTimeItems:
|
||||||
return MutableStoryExpirationTimeItemsView(postbox: postbox)
|
return MutableStoryExpirationTimeItemsView(postbox: postbox)
|
||||||
|
case let .peerStoryStats(peerIds):
|
||||||
|
return MutablePeerStoryStatsView(postbox: postbox, peerIds: peerIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1490,13 +1490,16 @@ public final class EngineStoryViewListContext {
|
|||||||
public final class Item: Equatable {
|
public final class Item: Equatable {
|
||||||
public let peer: EnginePeer
|
public let peer: EnginePeer
|
||||||
public let timestamp: Int32
|
public let timestamp: Int32
|
||||||
|
public let storyStats: PeerStoryStats?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
timestamp: Int32
|
timestamp: Int32,
|
||||||
|
storyStats: PeerStoryStats?
|
||||||
) {
|
) {
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
|
self.storyStats = storyStats
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||||
@ -1506,6 +1509,9 @@ public final class EngineStoryViewListContext {
|
|||||||
if lhs.timestamp != rhs.timestamp {
|
if lhs.timestamp != rhs.timestamp {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.storyStats != rhs.storyStats {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1545,6 +1551,7 @@ public final class EngineStoryViewListContext {
|
|||||||
let storyId: Int32
|
let storyId: Int32
|
||||||
|
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
let storyStatsDisposable = MetaDisposable()
|
||||||
|
|
||||||
var state: InternalState
|
var state: InternalState
|
||||||
let statePromise = Promise<InternalState>()
|
let statePromise = Promise<InternalState>()
|
||||||
@ -1569,6 +1576,7 @@ public final class EngineStoryViewListContext {
|
|||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
self.disposable.dispose()
|
self.disposable.dispose()
|
||||||
|
self.storyStatsDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadMore() {
|
func loadMore() {
|
||||||
@ -1604,8 +1612,9 @@ public final class EngineStoryViewListContext {
|
|||||||
for view in views {
|
for view in views {
|
||||||
switch view {
|
switch view {
|
||||||
case let .storyView(userId, date):
|
case let .storyView(userId, date):
|
||||||
if let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) {
|
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||||
items.append(Item(peer: EnginePeer(peer), timestamp: date))
|
if let peer = transaction.getPeer(peerId) {
|
||||||
|
items.append(Item(peer: EnginePeer(peer), timestamp: date, storyStats: transaction.getPeerStoryStats(peerId: peerId)))
|
||||||
|
|
||||||
nextOffset = NextOffset(id: userId, timestamp: date)
|
nextOffset = NextOffset(id: userId, timestamp: date)
|
||||||
}
|
}
|
||||||
@ -1702,6 +1711,35 @@ public final class EngineStoryViewListContext {
|
|||||||
|
|
||||||
strongSelf.isLoadingMore = false
|
strongSelf.isLoadingMore = false
|
||||||
strongSelf.statePromise.set(.single(strongSelf.state))
|
strongSelf.statePromise.set(.single(strongSelf.state))
|
||||||
|
|
||||||
|
let statsKey: PostboxViewKey = .peerStoryStats(peerIds: Set(strongSelf.state.items.map(\.peer.id)))
|
||||||
|
strongSelf.storyStatsDisposable.set((strongSelf.account.postbox.combinedView(keys: [statsKey])
|
||||||
|
|> deliverOn(strongSelf.queue)).start(next: { views in
|
||||||
|
guard let `self` = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let view = views.views[statsKey] as? PeerStoryStatsView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var updated = false
|
||||||
|
var items = self.state.items
|
||||||
|
for i in 0 ..< strongSelf.state.items.count {
|
||||||
|
let item = items[i]
|
||||||
|
let value = view.storyStats[item.peer.id]
|
||||||
|
if item.storyStats != value {
|
||||||
|
updated = true
|
||||||
|
items[i] = Item(
|
||||||
|
peer: item.peer,
|
||||||
|
timestamp: item.timestamp,
|
||||||
|
storyStats: value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if updated {
|
||||||
|
self.state.items = items
|
||||||
|
self.statePromise.set(.single(self.state))
|
||||||
|
}
|
||||||
|
}))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ public final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||||
hasUnseen: storyData.hasUnseen,
|
hasUnseen: storyData.hasUnseen,
|
||||||
hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends,
|
hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends,
|
||||||
theme: theme,
|
colors: AvatarStoryIndicatorComponent.Colors(theme: theme),
|
||||||
activeLineWidth: 1.0,
|
activeLineWidth: 1.0,
|
||||||
inactiveLineWidth: 1.0,
|
inactiveLineWidth: 1.0,
|
||||||
counters: nil
|
counters: nil
|
||||||
|
@ -5,6 +5,28 @@ import ComponentFlow
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
public final class AvatarStoryIndicatorComponent: Component {
|
public final class AvatarStoryIndicatorComponent: Component {
|
||||||
|
public struct Colors: Equatable {
|
||||||
|
public var unseenColors: [UIColor]
|
||||||
|
public var unseenCloseFriendsColors: [UIColor]
|
||||||
|
public var seenColors: [UIColor]
|
||||||
|
|
||||||
|
public init(
|
||||||
|
unseenColors: [UIColor],
|
||||||
|
unseenCloseFriendsColors: [UIColor],
|
||||||
|
seenColors: [UIColor]
|
||||||
|
) {
|
||||||
|
self.unseenColors = unseenColors
|
||||||
|
self.unseenCloseFriendsColors = unseenCloseFriendsColors
|
||||||
|
self.seenColors = seenColors
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(theme: PresentationTheme) {
|
||||||
|
self.unseenColors = [theme.chatList.storyUnseenColors.topColor, theme.chatList.storyUnseenColors.bottomColor]
|
||||||
|
self.unseenCloseFriendsColors = [theme.chatList.storyUnseenPrivateColors.topColor, theme.chatList.storyUnseenPrivateColors.bottomColor]
|
||||||
|
self.seenColors = [theme.chatList.storySeenColors.topColor, theme.chatList.storySeenColors.bottomColor]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct Counters: Equatable {
|
public struct Counters: Equatable {
|
||||||
public var totalCount: Int
|
public var totalCount: Int
|
||||||
public var unseenCount: Int
|
public var unseenCount: Int
|
||||||
@ -17,27 +39,24 @@ public final class AvatarStoryIndicatorComponent: Component {
|
|||||||
|
|
||||||
public let hasUnseen: Bool
|
public let hasUnseen: Bool
|
||||||
public let hasUnseenCloseFriendsItems: Bool
|
public let hasUnseenCloseFriendsItems: Bool
|
||||||
public let theme: PresentationTheme
|
public let colors: Colors
|
||||||
public let activeLineWidth: CGFloat
|
public let activeLineWidth: CGFloat
|
||||||
public let inactiveLineWidth: CGFloat
|
public let inactiveLineWidth: CGFloat
|
||||||
public let isGlassBackground: Bool
|
|
||||||
public let counters: Counters?
|
public let counters: Counters?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
hasUnseen: Bool,
|
hasUnseen: Bool,
|
||||||
hasUnseenCloseFriendsItems: Bool,
|
hasUnseenCloseFriendsItems: Bool,
|
||||||
theme: PresentationTheme,
|
colors: Colors,
|
||||||
activeLineWidth: CGFloat,
|
activeLineWidth: CGFloat,
|
||||||
inactiveLineWidth: CGFloat,
|
inactiveLineWidth: CGFloat,
|
||||||
isGlassBackground: Bool = false,
|
|
||||||
counters: Counters?
|
counters: Counters?
|
||||||
) {
|
) {
|
||||||
self.hasUnseen = hasUnseen
|
self.hasUnseen = hasUnseen
|
||||||
self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems
|
self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems
|
||||||
self.theme = theme
|
self.colors = colors
|
||||||
self.activeLineWidth = activeLineWidth
|
self.activeLineWidth = activeLineWidth
|
||||||
self.inactiveLineWidth = inactiveLineWidth
|
self.inactiveLineWidth = inactiveLineWidth
|
||||||
self.isGlassBackground = isGlassBackground
|
|
||||||
self.counters = counters
|
self.counters = counters
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +67,7 @@ public final class AvatarStoryIndicatorComponent: Component {
|
|||||||
if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems {
|
if lhs.hasUnseenCloseFriendsItems != rhs.hasUnseenCloseFriendsItems {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.theme !== rhs.theme {
|
if lhs.colors != rhs.colors {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.activeLineWidth != rhs.activeLineWidth {
|
if lhs.activeLineWidth != rhs.activeLineWidth {
|
||||||
@ -57,9 +76,6 @@ public final class AvatarStoryIndicatorComponent: Component {
|
|||||||
if lhs.inactiveLineWidth != rhs.inactiveLineWidth {
|
if lhs.inactiveLineWidth != rhs.inactiveLineWidth {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.isGlassBackground != rhs.isGlassBackground {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.counters != rhs.counters {
|
if lhs.counters != rhs.counters {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -101,16 +117,12 @@ public final class AvatarStoryIndicatorComponent: Component {
|
|||||||
let inactiveColors: [CGColor]
|
let inactiveColors: [CGColor]
|
||||||
|
|
||||||
if component.hasUnseenCloseFriendsItems {
|
if component.hasUnseenCloseFriendsItems {
|
||||||
activeColors = [component.theme.chatList.storyUnseenPrivateColors.topColor.cgColor, component.theme.chatList.storyUnseenPrivateColors.bottomColor.cgColor]
|
activeColors = component.colors.unseenCloseFriendsColors.map(\.cgColor)
|
||||||
} else {
|
} else {
|
||||||
activeColors = [component.theme.chatList.storyUnseenColors.topColor.cgColor, component.theme.chatList.storyUnseenColors.bottomColor.cgColor]
|
activeColors = component.colors.unseenColors.map(\.cgColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if component.isGlassBackground {
|
inactiveColors = component.colors.seenColors.map(\.cgColor)
|
||||||
inactiveColors = [UIColor(white: 1.0, alpha: 0.2).cgColor, UIColor(white: 1.0, alpha: 0.2).cgColor]
|
|
||||||
} else {
|
|
||||||
inactiveColors = [component.theme.chatList.storySeenColors.topColor.cgColor, component.theme.chatList.storySeenColors.bottomColor.cgColor]
|
|
||||||
}
|
|
||||||
|
|
||||||
var locations: [CGFloat] = [0.0, 1.0]
|
var locations: [CGFloat] = [0.0, 1.0]
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ swift_library(
|
|||||||
"//submodules/SSignalKit/SwiftSignalKit",
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
"//submodules/AccountContext",
|
"//submodules/AccountContext",
|
||||||
"//submodules/TelegramCore",
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/Postbox",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
"//submodules/AvatarNode",
|
"//submodules/AvatarNode",
|
||||||
"//submodules/CheckNode",
|
"//submodules/CheckNode",
|
||||||
|
@ -6,6 +6,7 @@ import ComponentFlow
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
import Postbox
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
@ -49,12 +50,14 @@ public final class PeerListItemComponent: Component {
|
|||||||
let sideInset: CGFloat
|
let sideInset: CGFloat
|
||||||
let title: String
|
let title: String
|
||||||
let peer: EnginePeer?
|
let peer: EnginePeer?
|
||||||
|
let storyStats: PeerStoryStats?
|
||||||
let subtitle: String?
|
let subtitle: String?
|
||||||
let subtitleAccessory: SubtitleAccessory
|
let subtitleAccessory: SubtitleAccessory
|
||||||
let presence: EnginePeer.Presence?
|
let presence: EnginePeer.Presence?
|
||||||
let selectionState: SelectionState
|
let selectionState: SelectionState
|
||||||
let hasNext: Bool
|
let hasNext: Bool
|
||||||
let action: (EnginePeer) -> Void
|
let action: (EnginePeer) -> Void
|
||||||
|
let openStories: ((EnginePeer, UIView) -> Void)?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -64,12 +67,14 @@ public final class PeerListItemComponent: Component {
|
|||||||
sideInset: CGFloat,
|
sideInset: CGFloat,
|
||||||
title: String,
|
title: String,
|
||||||
peer: EnginePeer?,
|
peer: EnginePeer?,
|
||||||
|
storyStats: PeerStoryStats? = nil,
|
||||||
subtitle: String?,
|
subtitle: String?,
|
||||||
subtitleAccessory: SubtitleAccessory,
|
subtitleAccessory: SubtitleAccessory,
|
||||||
presence: EnginePeer.Presence?,
|
presence: EnginePeer.Presence?,
|
||||||
selectionState: SelectionState,
|
selectionState: SelectionState,
|
||||||
hasNext: Bool,
|
hasNext: Bool,
|
||||||
action: @escaping (EnginePeer) -> Void
|
action: @escaping (EnginePeer) -> Void,
|
||||||
|
openStories: ((EnginePeer, UIView) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
@ -78,12 +83,14 @@ public final class PeerListItemComponent: Component {
|
|||||||
self.sideInset = sideInset
|
self.sideInset = sideInset
|
||||||
self.title = title
|
self.title = title
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
self.storyStats = storyStats
|
||||||
self.subtitle = subtitle
|
self.subtitle = subtitle
|
||||||
self.subtitleAccessory = subtitleAccessory
|
self.subtitleAccessory = subtitleAccessory
|
||||||
self.presence = presence
|
self.presence = presence
|
||||||
self.selectionState = selectionState
|
self.selectionState = selectionState
|
||||||
self.hasNext = hasNext
|
self.hasNext = hasNext
|
||||||
self.action = action
|
self.action = action
|
||||||
|
self.openStories = openStories
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool {
|
public static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool {
|
||||||
@ -108,6 +115,9 @@ public final class PeerListItemComponent: Component {
|
|||||||
if lhs.peer != rhs.peer {
|
if lhs.peer != rhs.peer {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.storyStats != rhs.storyStats {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.subtitle != rhs.subtitle {
|
if lhs.subtitle != rhs.subtitle {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -133,6 +143,7 @@ public final class PeerListItemComponent: Component {
|
|||||||
private let label = ComponentView<Empty>()
|
private let label = ComponentView<Empty>()
|
||||||
private let separatorLayer: SimpleLayer
|
private let separatorLayer: SimpleLayer
|
||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
|
private let avatarButtonView: HighlightTrackingButton
|
||||||
private var avatarIcon: ComponentView<Empty>?
|
private var avatarIcon: ComponentView<Empty>?
|
||||||
|
|
||||||
private var iconView: UIImageView?
|
private var iconView: UIImageView?
|
||||||
@ -168,7 +179,9 @@ public final class PeerListItemComponent: Component {
|
|||||||
self.containerButton = HighlightTrackingButton()
|
self.containerButton = HighlightTrackingButton()
|
||||||
|
|
||||||
self.avatarNode = AvatarNode(font: avatarFont)
|
self.avatarNode = AvatarNode(font: avatarFont)
|
||||||
self.avatarNode.isLayerBacked = true
|
self.avatarNode.isLayerBacked = false
|
||||||
|
|
||||||
|
self.avatarButtonView = HighlightTrackingButton()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
@ -177,6 +190,9 @@ public final class PeerListItemComponent: Component {
|
|||||||
self.containerButton.layer.addSublayer(self.avatarNode.layer)
|
self.containerButton.layer.addSublayer(self.avatarNode.layer)
|
||||||
|
|
||||||
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
|
|
||||||
|
self.addSubview(self.avatarButtonView)
|
||||||
|
self.avatarButtonView.addTarget(self, action: #selector(self.avatarButtonPressed), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -190,6 +206,13 @@ public final class PeerListItemComponent: Component {
|
|||||||
component.action(peer)
|
component.action(peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func avatarButtonPressed() {
|
||||||
|
guard let component = self.component, let peer = component.peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.openStories?(peer, self.avatarNode.view)
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: PeerListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(component: PeerListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
var synchronousLoad = false
|
var synchronousLoad = false
|
||||||
if let hint = transition.userData(TransitionHint.self) {
|
if let hint = transition.userData(TransitionHint.self) {
|
||||||
@ -234,6 +257,8 @@ public final class PeerListItemComponent: Component {
|
|||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
|
self.avatarButtonView.isUserInteractionEnabled = component.storyStats != nil
|
||||||
|
|
||||||
let labelData: (String, Bool)
|
let labelData: (String, Bool)
|
||||||
if let presence = component.presence {
|
if let presence = component.presence {
|
||||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
@ -319,6 +344,8 @@ public final class PeerListItemComponent: Component {
|
|||||||
transition.setFrame(layer: self.avatarNode.layer, frame: avatarFrame)
|
transition.setFrame(layer: self.avatarNode.layer, frame: avatarFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: self.avatarButtonView, frame: avatarFrame)
|
||||||
|
|
||||||
var statusIcon: EmojiStatusComponent.Content?
|
var statusIcon: EmojiStatusComponent.Content?
|
||||||
if let peer = component.peer {
|
if let peer = component.peer {
|
||||||
let clipStyle: AvatarNodeClipStyle
|
let clipStyle: AvatarNodeClipStyle
|
||||||
@ -328,6 +355,17 @@ public final class PeerListItemComponent: Component {
|
|||||||
clipStyle = .round
|
clipStyle = .round
|
||||||
}
|
}
|
||||||
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
|
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
|
||||||
|
self.avatarNode.setStoryStats(storyStats: component.storyStats.flatMap { storyStats -> AvatarNode.StoryStats in
|
||||||
|
return AvatarNode.StoryStats(
|
||||||
|
totalCount: storyStats.totalCount == 0 ? 0 : 1,
|
||||||
|
unseenCount: storyStats.unseenCount == 0 ? 0 : 1,
|
||||||
|
hasUnseenCloseFriendsItems: false
|
||||||
|
)
|
||||||
|
}, presentationParams: AvatarNode.StoryPresentationParams(
|
||||||
|
colors: AvatarNode.Colors(theme: component.theme),
|
||||||
|
lineWidth: 1.33,
|
||||||
|
inactiveLineWidth: 1.33
|
||||||
|
), transition: transition)
|
||||||
|
|
||||||
if peer.isScam {
|
if peer.isScam {
|
||||||
statusIcon = .text(color: component.theme.chat.message.incoming.scamColor, string: component.strings.Message_ScamAccount.uppercased())
|
statusIcon = .text(color: component.theme.chat.message.incoming.scamColor, string: component.strings.Message_ScamAccount.uppercased())
|
||||||
|
@ -2039,6 +2039,12 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.navigateToPeer(peer: peer, chat: false)
|
self.navigateToPeer(peer: peer, chat: false)
|
||||||
|
},
|
||||||
|
openPeerStories: { [weak self] peer, sourceView in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.openPeerStories(peer: peer, sourceView: sourceView)
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -3163,6 +3169,77 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openPeerStories(peer: EnginePeer, sourceView: UIView) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let storyContent = StoryContentContextImpl(context: component.context, isHidden: false, focusedPeerId: peer.id, singlePeer: true)
|
||||||
|
let _ = (storyContent.state
|
||||||
|
|> filter { $0.slice != nil }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak sourceView] _ in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||||
|
if let sourceView {
|
||||||
|
transitionIn = StoryContainerScreen.TransitionIn(
|
||||||
|
sourceView: sourceView,
|
||||||
|
sourceRect: sourceView.bounds,
|
||||||
|
sourceCornerRadius: sourceView.bounds.width * 0.5,
|
||||||
|
sourceIsAvatar: false
|
||||||
|
)
|
||||||
|
sourceView.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let storyContainerScreen = StoryContainerScreen(
|
||||||
|
context: component.context,
|
||||||
|
content: storyContent,
|
||||||
|
transitionIn: transitionIn,
|
||||||
|
transitionOut: { peerId, _ in
|
||||||
|
if let sourceView {
|
||||||
|
let destinationView = sourceView
|
||||||
|
return StoryContainerScreen.TransitionOut(
|
||||||
|
destinationView: destinationView,
|
||||||
|
transitionView: StoryContainerScreen.TransitionView(
|
||||||
|
makeView: { [weak destinationView] in
|
||||||
|
let parentView = UIView()
|
||||||
|
if let copyView = destinationView?.snapshotContentTree(unhide: true) {
|
||||||
|
parentView.addSubview(copyView)
|
||||||
|
}
|
||||||
|
return parentView
|
||||||
|
},
|
||||||
|
updateView: { copyView, state, transition in
|
||||||
|
guard let view = copyView.subviews.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress)
|
||||||
|
transition.setPosition(view: view, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
|
||||||
|
transition.setScale(view: view, scale: size.width / state.destinationSize.width)
|
||||||
|
},
|
||||||
|
insertCloneTransitionView: nil
|
||||||
|
),
|
||||||
|
destinationRect: destinationView.bounds,
|
||||||
|
destinationCornerRadius: destinationView.bounds.width * 0.5,
|
||||||
|
destinationIsAvatar: false,
|
||||||
|
completed: { [weak sourceView] in
|
||||||
|
guard let sourceView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sourceView.isHidden = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
component.controller()?.push(storyContainerScreen)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private func openStoryEditing() {
|
private func openStoryEditing() {
|
||||||
guard let component = self.component, let peerReference = PeerReference(component.slice.peer._asPeer()) else {
|
guard let component = self.component, let peerReference = PeerReference(component.slice.peer._asPeer()) else {
|
||||||
return
|
return
|
||||||
|
@ -56,6 +56,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
let deleteAction: () -> Void
|
let deleteAction: () -> Void
|
||||||
let moreAction: (UIView, ContextGesture?) -> Void
|
let moreAction: (UIView, ContextGesture?) -> Void
|
||||||
let openPeer: (EnginePeer) -> Void
|
let openPeer: (EnginePeer) -> Void
|
||||||
|
let openPeerStories: (EnginePeer, UIView) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
externalState: ExternalState,
|
externalState: ExternalState,
|
||||||
@ -73,7 +74,8 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
expandViewStats: @escaping () -> Void,
|
expandViewStats: @escaping () -> Void,
|
||||||
deleteAction: @escaping () -> Void,
|
deleteAction: @escaping () -> Void,
|
||||||
moreAction: @escaping (UIView, ContextGesture?) -> Void,
|
moreAction: @escaping (UIView, ContextGesture?) -> Void,
|
||||||
openPeer: @escaping (EnginePeer) -> Void
|
openPeer: @escaping (EnginePeer) -> Void,
|
||||||
|
openPeerStories: @escaping (EnginePeer, UIView) -> Void
|
||||||
) {
|
) {
|
||||||
self.externalState = externalState
|
self.externalState = externalState
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -91,6 +93,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
self.deleteAction = deleteAction
|
self.deleteAction = deleteAction
|
||||||
self.moreAction = moreAction
|
self.moreAction = moreAction
|
||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
|
self.openPeerStories = openPeerStories
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: StoryItemSetViewListComponent, rhs: StoryItemSetViewListComponent) -> Bool {
|
static func ==(lhs: StoryItemSetViewListComponent, rhs: StoryItemSetViewListComponent) -> Bool {
|
||||||
@ -484,6 +487,7 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
sideInset: 0.0,
|
sideInset: 0.0,
|
||||||
title: item.peer.displayTitle(strings: component.strings, displayOrder: .firstLast),
|
title: item.peer.displayTitle(strings: component.strings, displayOrder: .firstLast),
|
||||||
peer: item.peer,
|
peer: item.peer,
|
||||||
|
storyStats: item.storyStats,
|
||||||
subtitle: dateText,
|
subtitle: dateText,
|
||||||
subtitleAccessory: .checks,
|
subtitleAccessory: .checks,
|
||||||
presence: nil,
|
presence: nil,
|
||||||
@ -494,6 +498,12 @@ final class StoryItemSetViewListComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.openPeer(peer)
|
component.openPeer(peer)
|
||||||
|
},
|
||||||
|
openStories: { [weak self] peer, sourceView in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.openPeerStories(peer, sourceView)
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
|
@ -563,6 +563,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
private var powerSavingMonitoringDisposable: Disposable?
|
private var powerSavingMonitoringDisposable: Disposable?
|
||||||
|
|
||||||
private var avatarNode: ChatAvatarNavigationNode?
|
private var avatarNode: ChatAvatarNavigationNode?
|
||||||
|
private var storyStats: PeerStoryStats?
|
||||||
|
|
||||||
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = []) {
|
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = []) {
|
||||||
let _ = ChatControllerCount.modify { value in
|
let _ = ChatControllerCount.modify { value in
|
||||||
@ -1165,7 +1166,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
var expandAvatar = false
|
var expandAvatar = false
|
||||||
if case let .groupParticipant(storyStats, avatarHeaderNode) = source {
|
if case let .groupParticipant(storyStats, avatarHeaderNode) = source {
|
||||||
if let storyStats, storyStats.totalCount != 0, let avatarHeaderNode = avatarHeaderNode as? ChatMessageAvatarHeaderNode {
|
if let storyStats, storyStats.totalCount != 0, let avatarHeaderNode = avatarHeaderNode as? ChatMessageAvatarHeaderNode {
|
||||||
self?.openStories(peerId: peer.id, avatarHeaderNode: avatarHeaderNode)
|
self?.openStories(peerId: peer.id, avatarHeaderNode: avatarHeaderNode, avatarNode: nil)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
expandAvatar = true
|
expandAvatar = true
|
||||||
@ -4708,8 +4709,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
|
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
|
||||||
self.avatarNode = avatarNode
|
self.avatarNode = avatarNode
|
||||||
|
|
||||||
//avatarNode.updateStoryView(transition: .immediate, theme: self.presentationData.theme)
|
|
||||||
case .feed:
|
case .feed:
|
||||||
chatInfoButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
|
chatInfoButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
|
||||||
}
|
}
|
||||||
@ -4998,6 +4997,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: EnginePeer(peer), overrideImage: imageOverride)
|
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: EnginePeer(peer), overrideImage: imageOverride)
|
||||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = strongSelf.chatLocation.threadId == nil && peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil
|
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = strongSelf.chatLocation.threadId == nil && peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil
|
||||||
strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = presentationInterfaceState.strings.Conversation_ContextMenuOpenProfile
|
strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = presentationInterfaceState.strings.Conversation_ContextMenuOpenProfile
|
||||||
|
|
||||||
|
strongSelf.storyStats = peerView.storyStats
|
||||||
|
if let avatarNode = strongSelf.avatarNode {
|
||||||
|
avatarNode.avatarNode.setStoryStats(storyStats: peerView.storyStats.flatMap { storyStats -> AvatarNode.StoryStats? in
|
||||||
|
if storyStats.totalCount == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return AvatarNode.StoryStats(
|
||||||
|
totalCount: storyStats.totalCount == 0 ? 0 : 1,
|
||||||
|
unseenCount: storyStats.unseenCount == 0 ? 0 : 1,
|
||||||
|
hasUnseenCloseFriendsItems: false
|
||||||
|
)
|
||||||
|
}, presentationParams: AvatarNode.StoryPresentationParams(
|
||||||
|
colors: AvatarNode.Colors(theme: strongSelf.presentationData.theme),
|
||||||
|
lineWidth: 1.5,
|
||||||
|
inactiveLineWidth: 1.5
|
||||||
|
), transition: .immediate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -12230,9 +12247,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
@objc func rightNavigationButtonAction() {
|
@objc func rightNavigationButtonAction() {
|
||||||
if let button = self.rightNavigationButton {
|
if let button = self.rightNavigationButton {
|
||||||
|
if case let .peer(peerId) = self.chatLocation, case .openChatInfo(expandAvatar: true) = button.action, let storyStats = self.storyStats, storyStats.totalCount != 0, let avatarNode = self.avatarNode {
|
||||||
|
self.openStories(peerId: peerId, avatarHeaderNode: nil, avatarNode: avatarNode.avatarNode)
|
||||||
|
} else {
|
||||||
self.navigationButtonAction(button.action)
|
self.navigationButtonAction(button.action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func moreButtonPressed() {
|
@objc private func moreButtonPressed() {
|
||||||
self.moreBarButton.play()
|
self.moreBarButton.play()
|
||||||
@ -17011,12 +17032,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openStories(peerId: EnginePeer.Id, avatarHeaderNode: ChatMessageAvatarHeaderNode) {
|
private func openStories(peerId: EnginePeer.Id, avatarHeaderNode: ChatMessageAvatarHeaderNode?, avatarNode: AvatarNode?) {
|
||||||
let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: peerId, singlePeer: true)
|
let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: peerId, singlePeer: true)
|
||||||
let _ = (storyContent.state
|
let _ = (storyContent.state
|
||||||
|> filter { $0.slice != nil }
|
|> filter { $0.slice != nil }
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self, weak avatarHeaderNode] _ in
|
|> deliverOnMainQueue).start(next: { [weak self, weak avatarHeaderNode, weak avatarNode] _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -17030,6 +17051,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
sourceIsAvatar: false
|
sourceIsAvatar: false
|
||||||
)
|
)
|
||||||
avatarHeaderNode.avatarNode.isHidden = true
|
avatarHeaderNode.avatarNode.isHidden = true
|
||||||
|
} else if let avatarNode {
|
||||||
|
transitionIn = StoryContainerScreen.TransitionIn(
|
||||||
|
sourceView: avatarNode.view,
|
||||||
|
sourceRect: avatarNode.view.bounds,
|
||||||
|
sourceCornerRadius: avatarNode.view.bounds.width * 0.5,
|
||||||
|
sourceIsAvatar: false
|
||||||
|
)
|
||||||
|
avatarNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let storyContainerScreen = StoryContainerScreen(
|
let storyContainerScreen = StoryContainerScreen(
|
||||||
@ -17037,9 +17066,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
content: storyContent,
|
content: storyContent,
|
||||||
transitionIn: transitionIn,
|
transitionIn: transitionIn,
|
||||||
transitionOut: { peerId, _ in
|
transitionOut: { peerId, _ in
|
||||||
guard let avatarHeaderNode else {
|
if let avatarHeaderNode {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
let destinationView = avatarHeaderNode.avatarNode.view
|
let destinationView = avatarHeaderNode.avatarNode.view
|
||||||
return StoryContainerScreen.TransitionOut(
|
return StoryContainerScreen.TransitionOut(
|
||||||
destinationView: destinationView,
|
destinationView: destinationView,
|
||||||
@ -17071,6 +17098,41 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
avatarHeaderNode.avatarNode.isHidden = false
|
avatarHeaderNode.avatarNode.isHidden = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
} else if let avatarNode {
|
||||||
|
let destinationView = avatarNode.view
|
||||||
|
return StoryContainerScreen.TransitionOut(
|
||||||
|
destinationView: destinationView,
|
||||||
|
transitionView: StoryContainerScreen.TransitionView(
|
||||||
|
makeView: { [weak destinationView] in
|
||||||
|
let parentView = UIView()
|
||||||
|
if let copyView = destinationView?.snapshotContentTree(unhide: true) {
|
||||||
|
parentView.addSubview(copyView)
|
||||||
|
}
|
||||||
|
return parentView
|
||||||
|
},
|
||||||
|
updateView: { copyView, state, transition in
|
||||||
|
guard let view = copyView.subviews.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress)
|
||||||
|
transition.setPosition(view: view, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
|
||||||
|
transition.setScale(view: view, scale: size.width / state.destinationSize.width)
|
||||||
|
},
|
||||||
|
insertCloneTransitionView: nil
|
||||||
|
),
|
||||||
|
destinationRect: destinationView.bounds,
|
||||||
|
destinationCornerRadius: destinationView.bounds.width * 0.5,
|
||||||
|
destinationIsAvatar: false,
|
||||||
|
completed: { [weak avatarNode] in
|
||||||
|
guard let avatarNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
avatarNode.isHidden = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.push(storyContainerScreen)
|
self.push(storyContainerScreen)
|
||||||
|
@ -613,7 +613,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
|||||||
unseenCount: storyStats.unseenCount,
|
unseenCount: storyStats.unseenCount,
|
||||||
hasUnseenCloseFriendsItems: false
|
hasUnseenCloseFriendsItems: false
|
||||||
)
|
)
|
||||||
}, theme: theme, transition: .immediate)
|
}, presentationParams: AvatarNode.StoryPresentationParams(
|
||||||
|
colors: AvatarNode.Colors(theme: theme),
|
||||||
|
lineWidth: 2.0,
|
||||||
|
inactiveLineWidth: 2.0
|
||||||
|
), transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,15 +278,16 @@ class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let indicatorFrame = imageFrame
|
let indicatorFrame = imageFrame
|
||||||
|
var storyColors = AvatarStoryIndicatorComponent.Colors(theme: item.presentationData.theme.theme)
|
||||||
|
storyColors.seenColors = [UIColor(white: 1.0, alpha: 0.2), UIColor(white: 1.0, alpha: 0.2)]
|
||||||
let _ = strongSelf.storyIndicator.update(
|
let _ = strongSelf.storyIndicator.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||||
hasUnseen: hasUnseen,
|
hasUnseen: hasUnseen,
|
||||||
hasUnseenCloseFriendsItems: hasUnseen && (story?.isCloseFriends ?? false),
|
hasUnseenCloseFriendsItems: hasUnseen && (story?.isCloseFriends ?? false),
|
||||||
theme: item.presentationData.theme.theme,
|
colors: storyColors,
|
||||||
activeLineWidth: 3.0,
|
activeLineWidth: 3.0,
|
||||||
inactiveLineWidth: 1.0 + UIScreenPixel,
|
inactiveLineWidth: 1.0 + UIScreenPixel,
|
||||||
isGlassBackground: true,
|
|
||||||
counters: nil
|
counters: nil
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
|
@ -26,6 +26,7 @@ final class PeerInfoScreenMemberItem: PeerInfoScreenItem {
|
|||||||
let isAccount: Bool
|
let isAccount: Bool
|
||||||
let action: ((PeerInfoScreenMemberItemAction) -> Void)?
|
let action: ((PeerInfoScreenMemberItemAction) -> Void)?
|
||||||
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
|
let openStories: ((UIView) -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
id: AnyHashable,
|
id: AnyHashable,
|
||||||
@ -35,7 +36,8 @@ final class PeerInfoScreenMemberItem: PeerInfoScreenItem {
|
|||||||
badge: String? = nil,
|
badge: String? = nil,
|
||||||
isAccount: Bool,
|
isAccount: Bool,
|
||||||
action: ((PeerInfoScreenMemberItemAction) -> Void)?,
|
action: ((PeerInfoScreenMemberItemAction) -> Void)?,
|
||||||
contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil
|
contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil,
|
||||||
|
openStories: ((UIView) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -45,6 +47,7 @@ final class PeerInfoScreenMemberItem: PeerInfoScreenItem {
|
|||||||
self.isAccount = isAccount
|
self.isAccount = isAccount
|
||||||
self.action = action
|
self.action = action
|
||||||
self.contextAction = contextAction
|
self.contextAction = contextAction
|
||||||
|
self.openStories = openStories
|
||||||
}
|
}
|
||||||
|
|
||||||
func node() -> PeerInfoScreenItemNode {
|
func node() -> PeerInfoScreenItemNode {
|
||||||
@ -195,7 +198,12 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
|
|||||||
|
|
||||||
}, removePeer: { _ in
|
}, removePeer: { _ in
|
||||||
|
|
||||||
}, contextAction: item.contextAction, hasTopStripe: false, hasTopGroupInset: false, noInsets: true, noCorners: true, displayDecorations: false)
|
}, contextAction: item.contextAction, hasTopStripe: false, hasTopGroupInset: false, noInsets: true, noCorners: true, displayDecorations: false, storyStats: item.member.storyStats, openStories: { [weak self] sourceView in
|
||||||
|
guard let self, let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item.openStories?(sourceView)
|
||||||
|
})
|
||||||
|
|
||||||
let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0)
|
let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0)
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ enum PeerMembersListAction {
|
|||||||
case promote
|
case promote
|
||||||
case restrict
|
case restrict
|
||||||
case remove
|
case remove
|
||||||
|
case openStories(sourceView: UIView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum PeerMembersListEntryStableId: Hashable {
|
private enum PeerMembersListEntryStableId: Hashable {
|
||||||
@ -35,7 +36,7 @@ private enum PeerMembersListEntryStableId: Hashable {
|
|||||||
|
|
||||||
private enum PeerMembersListEntry: Comparable, Identifiable {
|
private enum PeerMembersListEntry: Comparable, Identifiable {
|
||||||
case addMember(PresentationTheme, String)
|
case addMember(PresentationTheme, String)
|
||||||
case member(PresentationTheme, Int, PeerInfoMember)
|
case member(theme: PresentationTheme, index: Int, member: PeerInfoMember)
|
||||||
|
|
||||||
var stableId: PeerMembersListEntryStableId {
|
var stableId: PeerMembersListEntryStableId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -126,7 +127,9 @@ private enum PeerMembersListEntry: Comparable, Identifiable {
|
|||||||
action(member, .open)
|
action(member, .open)
|
||||||
}, setPeerIdWithRevealedOptions: { _, _ in
|
}, setPeerIdWithRevealedOptions: { _, _ in
|
||||||
}, removePeer: { _ in
|
}, removePeer: { _ in
|
||||||
}, contextAction: nil, hasTopStripe: false, noInsets: true, noCorners: true, disableInteractiveTransitionIfNecessary: true)
|
}, contextAction: nil, hasTopStripe: false, noInsets: true, noCorners: true, disableInteractiveTransitionIfNecessary: true, storyStats: member.storyStats, openStories: { sourceView in
|
||||||
|
action(member, .openStories(sourceView: sourceView))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,7 +268,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||||||
entries.append(.addMember(presentationData.theme, presentationData.strings.GroupInfo_AddParticipant))
|
entries.append(.addMember(presentationData.theme, presentationData.strings.GroupInfo_AddParticipant))
|
||||||
}
|
}
|
||||||
for member in state.members {
|
for member in state.members {
|
||||||
entries.append(.member(presentationData.theme, entries.count, member))
|
entries.append(.member(theme: presentationData.theme, index: entries.count, member: member))
|
||||||
}
|
}
|
||||||
|
|
||||||
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, enclosingPeer: enclosingPeer, addMemberAction: { [weak self] in
|
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, enclosingPeer: enclosingPeer, addMemberAction: { [weak self] in
|
||||||
|
@ -1106,7 +1106,7 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member:
|
|||||||
result.insert(.promote)
|
result.insert(.promote)
|
||||||
} else {
|
} else {
|
||||||
switch member {
|
switch member {
|
||||||
case let .channelMember(channelMember):
|
case let .channelMember(channelMember, _):
|
||||||
switch channelMember.participant {
|
switch channelMember.participant {
|
||||||
case .creator:
|
case .creator:
|
||||||
break
|
break
|
||||||
@ -1142,7 +1142,7 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member:
|
|||||||
result.insert(.promote)
|
result.insert(.promote)
|
||||||
case .admin:
|
case .admin:
|
||||||
switch member {
|
switch member {
|
||||||
case let .legacyGroupMember(_, _, invitedBy, _):
|
case let .legacyGroupMember(_, _, invitedBy, _, _):
|
||||||
result.insert(.restrict)
|
result.insert(.restrict)
|
||||||
if invitedBy == accountPeerId {
|
if invitedBy == accountPeerId {
|
||||||
result.insert(.promote)
|
result.insert(.promote)
|
||||||
@ -1154,7 +1154,7 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member:
|
|||||||
}
|
}
|
||||||
case .member:
|
case .member:
|
||||||
switch member {
|
switch member {
|
||||||
case let .legacyGroupMember(_, _, invitedBy, _):
|
case let .legacyGroupMember(_, _, invitedBy, _, _):
|
||||||
if invitedBy == accountPeerId {
|
if invitedBy == accountPeerId {
|
||||||
result.insert(.restrict)
|
result.insert(.restrict)
|
||||||
}
|
}
|
||||||
|
@ -379,13 +379,15 @@ final class PeerInfoHeaderNavigationTransition {
|
|||||||
let sourceTitleView: ChatTitleView
|
let sourceTitleView: ChatTitleView
|
||||||
let sourceTitleFrame: CGRect
|
let sourceTitleFrame: CGRect
|
||||||
let sourceSubtitleFrame: CGRect
|
let sourceSubtitleFrame: CGRect
|
||||||
|
let previousAvatarView: UIView?
|
||||||
let fraction: CGFloat
|
let fraction: CGFloat
|
||||||
|
|
||||||
init(sourceNavigationBar: NavigationBar, sourceTitleView: ChatTitleView, sourceTitleFrame: CGRect, sourceSubtitleFrame: CGRect, fraction: CGFloat) {
|
init(sourceNavigationBar: NavigationBar, sourceTitleView: ChatTitleView, sourceTitleFrame: CGRect, sourceSubtitleFrame: CGRect, previousAvatarView: UIView?, fraction: CGFloat) {
|
||||||
self.sourceNavigationBar = sourceNavigationBar
|
self.sourceNavigationBar = sourceNavigationBar
|
||||||
self.sourceTitleView = sourceTitleView
|
self.sourceTitleView = sourceTitleView
|
||||||
self.sourceTitleFrame = sourceTitleFrame
|
self.sourceTitleFrame = sourceTitleFrame
|
||||||
self.sourceSubtitleFrame = sourceSubtitleFrame
|
self.sourceSubtitleFrame = sourceSubtitleFrame
|
||||||
|
self.previousAvatarView = previousAvatarView
|
||||||
self.fraction = fraction
|
self.fraction = fraction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -455,40 +457,22 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateStoryView(transition: ContainedViewLayoutTransition, theme: PresentationTheme) {
|
func updateStoryView(transition: ContainedViewLayoutTransition, theme: PresentationTheme) {
|
||||||
if let storyData = self.storyData {
|
var colors = AvatarNode.Colors(theme: theme)
|
||||||
let avatarStoryView: ComponentView<Empty>
|
colors.seenColors = [
|
||||||
if let current = self.avatarStoryView {
|
theme.list.controlSecondaryColor,
|
||||||
avatarStoryView = current
|
theme.list.controlSecondaryColor
|
||||||
} else {
|
]
|
||||||
avatarStoryView = ComponentView()
|
self.avatarNode.setStoryStats(storyStats: self.storyData.flatMap { storyData in
|
||||||
self.avatarStoryView = avatarStoryView
|
return AvatarNode.StoryStats(
|
||||||
}
|
totalCount: 1,
|
||||||
|
unseenCount: storyData.hasUnseen ? 1 : 0,
|
||||||
let _ = avatarStoryView.update(
|
hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends
|
||||||
transition: Transition(transition),
|
|
||||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
|
||||||
hasUnseen: storyData.hasUnseen,
|
|
||||||
hasUnseenCloseFriendsItems: storyData.hasUnseenCloseFriends,
|
|
||||||
theme: theme,
|
|
||||||
activeLineWidth: 3.0,
|
|
||||||
inactiveLineWidth: 2.0,
|
|
||||||
counters: nil
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: self.avatarNode.bounds.size
|
|
||||||
)
|
)
|
||||||
if let avatarStoryComponentView = avatarStoryView.view {
|
}, presentationParams: AvatarNode.StoryPresentationParams(
|
||||||
if avatarStoryComponentView.superview == nil {
|
colors: colors,
|
||||||
self.containerNode.view.insertSubview(avatarStoryComponentView, at: 0)
|
lineWidth: 3.0,
|
||||||
}
|
inactiveLineWidth: 1.5
|
||||||
avatarStoryComponentView.frame = self.avatarNode.frame
|
), transition: Transition(transition))
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let avatarStoryView = self.avatarStoryView {
|
|
||||||
self.avatarStoryView = nil
|
|
||||||
avatarStoryView.view?.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
@ -610,11 +594,11 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
avatarCornerRadius = avatarSize / 2.0
|
avatarCornerRadius = avatarSize / 2.0
|
||||||
}
|
}
|
||||||
if self.avatarNode.layer.cornerRadius != 0.0 {
|
if self.avatarNode.layer.cornerRadius != 0.0 {
|
||||||
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut).updateCornerRadius(layer: self.avatarNode.layer, cornerRadius: avatarCornerRadius)
|
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut).updateCornerRadius(layer: self.avatarNode.contentNode.layer, cornerRadius: avatarCornerRadius)
|
||||||
} else {
|
} else {
|
||||||
self.avatarNode.layer.cornerRadius = avatarCornerRadius
|
self.avatarNode.contentNode.layer.cornerRadius = avatarCornerRadius
|
||||||
}
|
}
|
||||||
self.avatarNode.layer.masksToBounds = true
|
self.avatarNode.contentNode.layer.masksToBounds = true
|
||||||
|
|
||||||
self.isFirstAvatarLoading = false
|
self.isFirstAvatarLoading = false
|
||||||
|
|
||||||
@ -1175,7 +1159,6 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
|
|||||||
self.containerNode = ASDisplayNode()
|
self.containerNode = ASDisplayNode()
|
||||||
|
|
||||||
self.bottomCoverNode = ASDisplayNode()
|
self.bottomCoverNode = ASDisplayNode()
|
||||||
self.bottomCoverNode.backgroundColor = .black
|
|
||||||
|
|
||||||
self.maskNode = DynamicIslandMaskNode()
|
self.maskNode = DynamicIslandMaskNode()
|
||||||
self.pinchSourceNode = PinchSourceContainerNode()
|
self.pinchSourceNode = PinchSourceContainerNode()
|
||||||
@ -2918,6 +2901,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
transitionSourceAvatarFrame = avatarNavigationNode.avatarNode.view.convert(avatarNavigationNode.avatarNode.view.bounds, to: navigationTransition.sourceNavigationBar.view)
|
transitionSourceAvatarFrame = avatarNavigationNode.avatarNode.view.convert(avatarNavigationNode.avatarNode.view.bounds, to: navigationTransition.sourceNavigationBar.view)
|
||||||
}
|
}
|
||||||
|
transition.updateAlpha(node: self.avatarListNode.avatarContainerNode.avatarNode, alpha: 1.0 - transitionFraction)
|
||||||
} else {
|
} else {
|
||||||
if deviceMetrics.hasDynamicIsland && !isLandscape {
|
if deviceMetrics.hasDynamicIsland && !isLandscape {
|
||||||
transitionSourceAvatarFrame = CGRect(origin: CGPoint(x: avatarFrame.minX, y: -20.0), size: avatarFrame.size).insetBy(dx: avatarSize * 0.4, dy: avatarSize * 0.4)
|
transitionSourceAvatarFrame = CGRect(origin: CGPoint(x: avatarFrame.minX, y: -20.0), size: avatarFrame.size).insetBy(dx: avatarSize * 0.4, dy: avatarSize * 0.4)
|
||||||
@ -3370,7 +3354,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
let avatarOffset: CGFloat
|
let avatarOffset: CGFloat
|
||||||
if self.navigationTransition != nil {
|
if self.navigationTransition != nil {
|
||||||
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
||||||
avatarScale = ((1.0 - transitionFraction) * avatarFrame.width + transitionFraction * transitionSourceAvatarFrame.width) / avatarFrame.width
|
var trueAvatarSize = transitionSourceAvatarFrame.size
|
||||||
|
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||||
|
trueAvatarSize.width -= 1.33 * 4.0
|
||||||
|
trueAvatarSize.height -= 1.33 * 4.0
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarScale = ((1.0 - transitionFraction) * avatarFrame.width + transitionFraction * trueAvatarSize.width) / avatarFrame.width
|
||||||
} else {
|
} else {
|
||||||
avatarScale = 1.0
|
avatarScale = 1.0
|
||||||
}
|
}
|
||||||
@ -3380,6 +3370,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
avatarOffset = apparentTitleLockOffset + 0.0 * (1.0 - titleCollapseFraction) + 10.0 * titleCollapseFraction
|
avatarOffset = apparentTitleLockOffset + 0.0 * (1.0 - titleCollapseFraction) + 10.0 * titleCollapseFraction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let previousAvatarView = self.navigationTransition?.previousAvatarView, let transitionSourceAvatarFrame {
|
||||||
|
let previousScale = ((1.0 - transitionFraction) * avatarFrame.width + transitionFraction * transitionSourceAvatarFrame.width) / transitionSourceAvatarFrame.width
|
||||||
|
|
||||||
|
transition.updateAlpha(layer: previousAvatarView.layer, alpha: transitionFraction)
|
||||||
|
transition.updateTransformScale(layer: previousAvatarView.layer, scale: previousScale)
|
||||||
|
transition.updatePosition(layer: previousAvatarView.layer, position: self.view.convert(CGPoint(x: avatarCenter.x - (27.0 * (1.0 - transitionFraction) + 10 * transitionFraction), y: avatarCenter.y - (2.66 * (1.0 - transitionFraction) + 1.0 * transitionFraction)), to: previousAvatarView.superview))
|
||||||
|
}
|
||||||
|
|
||||||
if subtitleIsButton {
|
if subtitleIsButton {
|
||||||
subtitleFrame.origin.y += 11.0 * (1.0 - titleCollapseFraction)
|
subtitleFrame.origin.y += 11.0 * (1.0 - titleCollapseFraction)
|
||||||
if let subtitleBackgroundButton = self.subtitleBackgroundButton {
|
if let subtitleBackgroundButton = self.subtitleBackgroundButton {
|
||||||
@ -3398,8 +3396,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
if self.isAvatarExpanded {
|
if self.isAvatarExpanded {
|
||||||
self.avatarListNode.listContainerNode.isHidden = false
|
self.avatarListNode.listContainerNode.isHidden = false
|
||||||
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
||||||
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: transitionFraction * transitionSourceAvatarFrame.width / 2.0)
|
var trueAvatarSize = transitionSourceAvatarFrame.size
|
||||||
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: transitionFraction * transitionSourceAvatarFrame.width / 2.0)
|
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||||
|
trueAvatarSize.width -= 1.33 * 4.0
|
||||||
|
trueAvatarSize.height -= 1.33 * 4.0
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: transitionFraction * trueAvatarSize.width / 2.0)
|
||||||
|
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: transitionFraction * trueAvatarSize.width / 2.0)
|
||||||
} else {
|
} else {
|
||||||
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: 0.0)
|
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: 0.0)
|
||||||
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: 0.0)
|
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: 0.0)
|
||||||
@ -3435,19 +3439,31 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
transition.updateAlpha(layer: avatarStoryView.layer, alpha: 1.0 - transitionFraction)
|
transition.updateAlpha(layer: avatarStoryView.layer, alpha: 1.0 - transitionFraction)
|
||||||
}
|
}
|
||||||
|
|
||||||
let apparentAvatarFrame: CGRect
|
var apparentAvatarFrame: CGRect
|
||||||
let controlsClippingFrame: CGRect
|
let controlsClippingFrame: CGRect
|
||||||
if self.isAvatarExpanded {
|
if self.isAvatarExpanded {
|
||||||
let expandedAvatarCenter = CGPoint(x: expandedAvatarListSize.width / 2.0, y: expandedAvatarListSize.height / 2.0 - contentOffset / 2.0)
|
let expandedAvatarCenter = CGPoint(x: expandedAvatarListSize.width / 2.0, y: expandedAvatarListSize.height / 2.0 - contentOffset / 2.0)
|
||||||
apparentAvatarFrame = CGRect(origin: CGPoint(x: expandedAvatarCenter.x * (1.0 - transitionFraction) + transitionFraction * avatarCenter.x, y: expandedAvatarCenter.y * (1.0 - transitionFraction) + transitionFraction * avatarCenter.y), size: CGSize())
|
apparentAvatarFrame = CGRect(origin: CGPoint(x: expandedAvatarCenter.x * (1.0 - transitionFraction) + transitionFraction * avatarCenter.x, y: expandedAvatarCenter.y * (1.0 - transitionFraction) + transitionFraction * avatarCenter.y), size: CGSize())
|
||||||
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
||||||
|
var trueAvatarSize = transitionSourceAvatarFrame.size
|
||||||
|
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||||
|
trueAvatarSize.width -= 1.33 * 4.0
|
||||||
|
trueAvatarSize.height -= 1.33 * 4.0
|
||||||
|
}
|
||||||
|
let trueAvatarFrame = trueAvatarSize.centered(around: transitionSourceAvatarFrame.center)
|
||||||
|
|
||||||
let expandedFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedAvatarListSize)
|
let expandedFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedAvatarListSize)
|
||||||
controlsClippingFrame = CGRect(origin: CGPoint(x: transitionFraction * transitionSourceAvatarFrame.minX + (1.0 - transitionFraction) * expandedFrame.minX, y: transitionFraction * transitionSourceAvatarFrame.minY + (1.0 - transitionFraction) * expandedFrame.minY), size: CGSize(width: transitionFraction * transitionSourceAvatarFrame.width + (1.0 - transitionFraction) * expandedFrame.width, height: transitionFraction * transitionSourceAvatarFrame.height + (1.0 - transitionFraction) * expandedFrame.height))
|
controlsClippingFrame = CGRect(origin: CGPoint(x: transitionFraction * trueAvatarFrame.minX + (1.0 - transitionFraction) * expandedFrame.minX, y: transitionFraction * trueAvatarFrame.minY + (1.0 - transitionFraction) * expandedFrame.minY), size: CGSize(width: transitionFraction * trueAvatarFrame.width + (1.0 - transitionFraction) * expandedFrame.width, height: transitionFraction * trueAvatarFrame.height + (1.0 - transitionFraction) * expandedFrame.height))
|
||||||
} else {
|
} else {
|
||||||
controlsClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedAvatarListSize)
|
controlsClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedAvatarListSize)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
apparentAvatarFrame = CGRect(origin: CGPoint(x: avatarCenter.x - avatarFrame.width / 2.0, y: -contentOffset + avatarOffset + avatarCenter.y - avatarFrame.height / 2.0), size: avatarFrame.size)
|
var trueAvatarSize = avatarFrame.size
|
||||||
|
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||||
|
trueAvatarSize.width -= 3.0 * 4.0
|
||||||
|
trueAvatarSize.height -= 3.0 * 4.0
|
||||||
|
}
|
||||||
|
apparentAvatarFrame = CGRect(origin: CGPoint(x: avatarCenter.x - trueAvatarSize.width / 2.0, y: -contentOffset + avatarOffset + avatarCenter.y - trueAvatarSize.height / 2.0), size: trueAvatarSize)
|
||||||
controlsClippingFrame = apparentAvatarFrame
|
controlsClippingFrame = apparentAvatarFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3466,7 +3482,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
if self.isAvatarExpanded {
|
if self.isAvatarExpanded {
|
||||||
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
||||||
let neutralAvatarListContainerSize = expandedAvatarListSize
|
let neutralAvatarListContainerSize = expandedAvatarListSize
|
||||||
let avatarListContainerSize = CGSize(width: neutralAvatarListContainerSize.width * (1.0 - transitionFraction) + transitionSourceAvatarFrame.width * transitionFraction, height: neutralAvatarListContainerSize.height * (1.0 - transitionFraction) + transitionSourceAvatarFrame.height * transitionFraction)
|
var avatarListContainerSize = CGSize(width: neutralAvatarListContainerSize.width * (1.0 - transitionFraction) + transitionSourceAvatarFrame.width * transitionFraction, height: neutralAvatarListContainerSize.height * (1.0 - transitionFraction) + transitionSourceAvatarFrame.height * transitionFraction)
|
||||||
|
|
||||||
|
if self.avatarListNode.avatarContainerNode.avatarNode.storyStats != nil {
|
||||||
|
avatarListContainerSize.width -= 1.33 * 5.0
|
||||||
|
avatarListContainerSize.height -= 1.33 * 5.0
|
||||||
|
}
|
||||||
|
|
||||||
avatarListContainerFrame = CGRect(origin: CGPoint(x: -avatarListContainerSize.width / 2.0, y: -avatarListContainerSize.height / 2.0), size: avatarListContainerSize)
|
avatarListContainerFrame = CGRect(origin: CGPoint(x: -avatarListContainerSize.width / 2.0, y: -avatarListContainerSize.height / 2.0), size: avatarListContainerSize)
|
||||||
} else {
|
} else {
|
||||||
avatarListContainerFrame = CGRect(origin: CGPoint(x: -expandedAvatarListSize.width / 2.0, y: -expandedAvatarListSize.height / 2.0), size: expandedAvatarListSize)
|
avatarListContainerFrame = CGRect(origin: CGPoint(x: -expandedAvatarListSize.width / 2.0, y: -expandedAvatarListSize.height / 2.0), size: expandedAvatarListSize)
|
||||||
@ -3512,6 +3534,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
self.avatarListNode.topCoverNode.update(maskValue)
|
self.avatarListNode.topCoverNode.update(maskValue)
|
||||||
self.avatarListNode.maskNode.update(maskValue)
|
self.avatarListNode.maskNode.update(maskValue)
|
||||||
|
self.avatarListNode.bottomCoverNode.backgroundColor = UIColor(white: 0.0, alpha: maskValue)
|
||||||
|
|
||||||
self.avatarListNode.listContainerNode.topShadowNode.isHidden = !self.isAvatarExpanded
|
self.avatarListNode.listContainerNode.topShadowNode.isHidden = !self.isAvatarExpanded
|
||||||
|
|
||||||
@ -3520,7 +3543,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
avatarMaskOffset -= contentOffset
|
avatarMaskOffset -= contentOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
self.avatarListNode.maskNode.position = CGPoint(x: 0.0, y: -self.avatarListNode.frame.minY + 48.0 + 85.5 + avatarMaskOffset)
|
self.avatarListNode.maskNode.position = CGPoint(x: 0.0, y: -self.avatarListNode.frame.minY + 48.0 + 85.0 + avatarMaskOffset)
|
||||||
self.avatarListNode.maskNode.bounds = CGRect(origin: .zero, size: CGSize(width: 171.0, height: 171.0))
|
self.avatarListNode.maskNode.bounds = CGRect(origin: .zero, size: CGSize(width: 171.0, height: 171.0))
|
||||||
|
|
||||||
self.avatarListNode.bottomCoverNode.position = self.avatarListNode.maskNode.position
|
self.avatarListNode.bottomCoverNode.position = self.avatarListNode.maskNode.position
|
||||||
|
@ -12,15 +12,15 @@ enum PeerInfoMemberRole {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum PeerInfoMember: Equatable {
|
enum PeerInfoMember: Equatable {
|
||||||
case channelMember(RenderedChannelParticipant)
|
case channelMember(participant: RenderedChannelParticipant, storyStats: PeerStoryStats?)
|
||||||
case legacyGroupMember(peer: RenderedPeer, role: PeerInfoMemberRole, invitedBy: PeerId?, presence: TelegramUserPresence?)
|
case legacyGroupMember(peer: RenderedPeer, role: PeerInfoMemberRole, invitedBy: PeerId?, presence: TelegramUserPresence?, storyStats: PeerStoryStats?)
|
||||||
case account(peer: RenderedPeer)
|
case account(peer: RenderedPeer)
|
||||||
|
|
||||||
var id: PeerId {
|
var id: PeerId {
|
||||||
switch self {
|
switch self {
|
||||||
case let .channelMember(channelMember):
|
case let .channelMember(participant, _):
|
||||||
return channelMember.peer.id
|
return participant.peer.id
|
||||||
case let .legacyGroupMember(peer, _, _, _):
|
case let .legacyGroupMember(peer, _, _, _, _):
|
||||||
return peer.peerId
|
return peer.peerId
|
||||||
case let .account(peer):
|
case let .account(peer):
|
||||||
return peer.peerId
|
return peer.peerId
|
||||||
@ -29,9 +29,9 @@ enum PeerInfoMember: Equatable {
|
|||||||
|
|
||||||
var peer: Peer {
|
var peer: Peer {
|
||||||
switch self {
|
switch self {
|
||||||
case let .channelMember(channelMember):
|
case let .channelMember(participant, _):
|
||||||
return channelMember.peer
|
return participant.peer
|
||||||
case let .legacyGroupMember(peer, _, _, _):
|
case let .legacyGroupMember(peer, _, _, _, _):
|
||||||
return peer.peers[peer.peerId]!
|
return peer.peers[peer.peerId]!
|
||||||
case let .account(peer):
|
case let .account(peer):
|
||||||
return peer.peers[peer.peerId]!
|
return peer.peers[peer.peerId]!
|
||||||
@ -40,9 +40,9 @@ enum PeerInfoMember: Equatable {
|
|||||||
|
|
||||||
var presence: TelegramUserPresence? {
|
var presence: TelegramUserPresence? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .channelMember(channelMember):
|
case let .channelMember(participant, _):
|
||||||
return channelMember.presences[channelMember.peer.id] as? TelegramUserPresence
|
return participant.presences[participant.peer.id] as? TelegramUserPresence
|
||||||
case let .legacyGroupMember(_, _, _, presence):
|
case let .legacyGroupMember(_, _, _, presence, _):
|
||||||
return presence
|
return presence
|
||||||
case .account:
|
case .account:
|
||||||
return nil
|
return nil
|
||||||
@ -51,8 +51,8 @@ enum PeerInfoMember: Equatable {
|
|||||||
|
|
||||||
var role: PeerInfoMemberRole {
|
var role: PeerInfoMemberRole {
|
||||||
switch self {
|
switch self {
|
||||||
case let .channelMember(channelMember):
|
case let .channelMember(participant, _):
|
||||||
switch channelMember.participant {
|
switch participant.participant {
|
||||||
case .creator:
|
case .creator:
|
||||||
return .creator
|
return .creator
|
||||||
case let .member(_, _, adminInfo, _, _):
|
case let .member(_, _, adminInfo, _, _):
|
||||||
@ -62,7 +62,7 @@ enum PeerInfoMember: Equatable {
|
|||||||
return .member
|
return .member
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .legacyGroupMember(_, role, _, _):
|
case let .legacyGroupMember(_, role, _, _, _):
|
||||||
return role
|
return role
|
||||||
case .account:
|
case .account:
|
||||||
return .member
|
return .member
|
||||||
@ -71,8 +71,8 @@ enum PeerInfoMember: Equatable {
|
|||||||
|
|
||||||
var rank: String? {
|
var rank: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .channelMember(channelMember):
|
case let .channelMember(participant, _):
|
||||||
switch channelMember.participant {
|
switch participant.participant {
|
||||||
case let .creator(_, _, rank):
|
case let .creator(_, _, rank):
|
||||||
return rank
|
return rank
|
||||||
case let .member(_, _, _, _, rank):
|
case let .member(_, _, _, _, rank):
|
||||||
@ -84,6 +84,17 @@ enum PeerInfoMember: Equatable {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var storyStats: PeerStoryStats? {
|
||||||
|
switch self {
|
||||||
|
case let .channelMember(_, value):
|
||||||
|
return value
|
||||||
|
case let .legacyGroupMember(_, _, _, _, value):
|
||||||
|
return value
|
||||||
|
case .account:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PeerInfoMembersDataState: Equatable {
|
enum PeerInfoMembersDataState: Equatable {
|
||||||
@ -154,7 +165,9 @@ private final class PeerInfoMembersContextImpl {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let unsortedMembers = state.list.map(PeerInfoMember.channelMember)
|
let unsortedMembers = state.list.map { item -> PeerInfoMember in
|
||||||
|
return .channelMember(participant: item, storyStats: state.peerStoryStats[item.peer.id])
|
||||||
|
}
|
||||||
let members: [PeerInfoMember]
|
let members: [PeerInfoMember]
|
||||||
if unsortedMembers.count <= 50 {
|
if unsortedMembers.count <= 50 {
|
||||||
members = membersSortedByPresence(unsortedMembers, accountPeerId: strongSelf.context.account.peerId)
|
members = membersSortedByPresence(unsortedMembers, accountPeerId: strongSelf.context.account.peerId)
|
||||||
@ -230,7 +243,7 @@ private final class PeerInfoMembersContextImpl {
|
|||||||
role = .member
|
role = .member
|
||||||
invitedBy = invitedByValue
|
invitedBy = invitedByValue
|
||||||
}
|
}
|
||||||
unsortedMembers.append(.legacyGroupMember(peer: RenderedPeer(peer: peer), role: role, invitedBy: invitedBy, presence: view.peerPresences[participant.peerId] as? TelegramUserPresence))
|
unsortedMembers.append(.legacyGroupMember(peer: RenderedPeer(peer: peer), role: role, invitedBy: invitedBy, presence: view.peerPresences[participant.peerId] as? TelegramUserPresence, storyStats: view.memberStoryStats[participant.peerId]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,6 +448,7 @@ private enum PeerInfoMemberAction {
|
|||||||
case promote
|
case promote
|
||||||
case restrict
|
case restrict
|
||||||
case remove
|
case remove
|
||||||
|
case openStories(sourceView: UIView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum PeerInfoContextSubject {
|
private enum PeerInfoContextSubject {
|
||||||
@ -1389,6 +1390,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
case .remove:
|
case .remove:
|
||||||
interaction.performMemberAction(member, .remove)
|
interaction.performMemberAction(member, .remove)
|
||||||
}
|
}
|
||||||
|
}, openStories: { sourceView in
|
||||||
|
interaction.performMemberAction(member, .openStories(sourceView: sourceView))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3012,6 +3015,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
strongSelf.performMemberAction(member: member, action: .restrict)
|
strongSelf.performMemberAction(member: member, action: .restrict)
|
||||||
case .remove:
|
case .remove:
|
||||||
strongSelf.performMemberAction(member: member, action: .remove)
|
strongSelf.performMemberAction(member: member, action: .remove)
|
||||||
|
case let .openStories(sourceView):
|
||||||
|
strongSelf.performMemberAction(member: member, action: .openStories(sourceView: sourceView))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7099,7 +7104,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
switch action {
|
switch action {
|
||||||
case .promote:
|
case .promote:
|
||||||
if case let .channelMember(channelMember) = member {
|
if case let .channelMember(channelMember, _) = member {
|
||||||
var upgradedToSupergroupImpl: (() -> Void)?
|
var upgradedToSupergroupImpl: (() -> Void)?
|
||||||
let controller = channelAdminController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, adminId: member.id, initialParticipant: channelMember.participant, updated: { _ in
|
let controller = channelAdminController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, adminId: member.id, initialParticipant: channelMember.participant, updated: { _ in
|
||||||
}, upgradedToSupergroup: { _, f in
|
}, upgradedToSupergroup: { _, f in
|
||||||
@ -7116,7 +7121,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .restrict:
|
case .restrict:
|
||||||
if case let .channelMember(channelMember) = member {
|
if case let .channelMember(channelMember, _) = member {
|
||||||
var upgradedToSupergroupImpl: (() -> Void)?
|
var upgradedToSupergroupImpl: (() -> Void)?
|
||||||
|
|
||||||
let controller = channelBannedMemberController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, memberId: member.id, initialParticipant: channelMember.participant, updated: { _ in
|
let controller = channelBannedMemberController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, memberId: member.id, initialParticipant: channelMember.participant, updated: { _ in
|
||||||
@ -7135,6 +7140,71 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
case .remove:
|
case .remove:
|
||||||
data.members?.membersContext.removeMember(memberId: member.id)
|
data.members?.membersContext.removeMember(memberId: member.id)
|
||||||
|
case let .openStories(sourceView):
|
||||||
|
let storyContent = StoryContentContextImpl(context: self.context, isHidden: false, focusedPeerId: member.id, singlePeer: true)
|
||||||
|
let _ = (storyContent.state
|
||||||
|
|> filter { $0.slice != nil }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak sourceView] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||||
|
if let sourceView {
|
||||||
|
transitionIn = StoryContainerScreen.TransitionIn(
|
||||||
|
sourceView: sourceView,
|
||||||
|
sourceRect: sourceView.bounds,
|
||||||
|
sourceCornerRadius: sourceView.bounds.width * 0.5,
|
||||||
|
sourceIsAvatar: false
|
||||||
|
)
|
||||||
|
sourceView.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let storyContainerScreen = StoryContainerScreen(
|
||||||
|
context: self.context,
|
||||||
|
content: storyContent,
|
||||||
|
transitionIn: transitionIn,
|
||||||
|
transitionOut: { peerId, _ in
|
||||||
|
if let sourceView {
|
||||||
|
let destinationView = sourceView
|
||||||
|
return StoryContainerScreen.TransitionOut(
|
||||||
|
destinationView: destinationView,
|
||||||
|
transitionView: StoryContainerScreen.TransitionView(
|
||||||
|
makeView: { [weak destinationView] in
|
||||||
|
let parentView = UIView()
|
||||||
|
if let copyView = destinationView?.snapshotContentTree(unhide: true) {
|
||||||
|
parentView.addSubview(copyView)
|
||||||
|
}
|
||||||
|
return parentView
|
||||||
|
},
|
||||||
|
updateView: { copyView, state, transition in
|
||||||
|
guard let view = copyView.subviews.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let size = state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress)
|
||||||
|
transition.setPosition(view: view, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
|
||||||
|
transition.setScale(view: view, scale: size.width / state.destinationSize.width)
|
||||||
|
},
|
||||||
|
insertCloneTransitionView: nil
|
||||||
|
),
|
||||||
|
destinationRect: destinationView.bounds,
|
||||||
|
destinationCornerRadius: destinationView.bounds.width * 0.5,
|
||||||
|
destinationIsAvatar: false,
|
||||||
|
completed: { [weak sourceView] in
|
||||||
|
guard let sourceView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sourceView.isHidden = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.controller?.push(storyContainerScreen)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10503,6 +10573,8 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||||||
private var previousTitleNode: (ASDisplayNode, PortalView)?
|
private var previousTitleNode: (ASDisplayNode, PortalView)?
|
||||||
private var previousStatusNode: (ASDisplayNode, ASDisplayNode)?
|
private var previousStatusNode: (ASDisplayNode, ASDisplayNode)?
|
||||||
|
|
||||||
|
private var previousAvatarView: UIView?
|
||||||
|
|
||||||
private var didSetup: Bool = false
|
private var didSetup: Bool = false
|
||||||
|
|
||||||
init(screenNode: PeerInfoScreenNode, presentationData: PresentationData, headerNode: PeerInfoHeaderNode) {
|
init(screenNode: PeerInfoScreenNode, presentationData: PresentationData, headerNode: PeerInfoHeaderNode) {
|
||||||
@ -10576,7 +10648,9 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||||||
self.view.layer.addSublayer(previousRightButton)
|
self.view.layer.addSublayer(previousRightButton)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let _ = bottomNavigationBar.rightButtonNode.singleCustomNode as? ChatAvatarNavigationNode {
|
if let avatarNavigationNode = bottomNavigationBar.rightButtonNode.singleCustomNode as? ChatAvatarNavigationNode, let previousAvatarView = avatarNavigationNode.view.snapshotContentTree() {
|
||||||
|
self.previousAvatarView = previousAvatarView
|
||||||
|
self.view.addSubview(previousAvatarView)
|
||||||
} else if let previousRightButton = bottomNavigationBar.rightButtonNode.view.layer.snapshotContentTree() {
|
} else if let previousRightButton = bottomNavigationBar.rightButtonNode.view.layer.snapshotContentTree() {
|
||||||
self.previousRightButton = previousRightButton
|
self.previousRightButton = previousRightButton
|
||||||
self.view.layer.addSublayer(previousRightButton)
|
self.view.layer.addSublayer(previousRightButton)
|
||||||
@ -10706,7 +10780,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||||||
let previousTitleFrame = previousTitleView.titleContainerView.convert(previousTitleView.titleContainerView.bounds, to: bottomNavigationBar.view)
|
let previousTitleFrame = previousTitleView.titleContainerView.convert(previousTitleView.titleContainerView.bounds, to: bottomNavigationBar.view)
|
||||||
let previousStatusFrame = previousTitleView.activityNode.view.convert(previousTitleView.activityNode.bounds, to: bottomNavigationBar.view)
|
let previousStatusFrame = previousTitleView.activityNode.view.convert(previousTitleView.activityNode.bounds, to: bottomNavigationBar.view)
|
||||||
|
|
||||||
self.headerNode.navigationTransition = PeerInfoHeaderNavigationTransition(sourceNavigationBar: bottomNavigationBar, sourceTitleView: previousTitleView, sourceTitleFrame: previousTitleFrame, sourceSubtitleFrame: previousStatusFrame, fraction: fraction)
|
self.headerNode.navigationTransition = PeerInfoHeaderNavigationTransition(sourceNavigationBar: bottomNavigationBar, sourceTitleView: previousTitleView, sourceTitleFrame: previousTitleFrame, sourceSubtitleFrame: previousStatusFrame, previousAvatarView: self.previousAvatarView, fraction: fraction)
|
||||||
var topHeight = topNavigationBar.backgroundNode.bounds.height
|
var topHeight = topNavigationBar.backgroundNode.bounds.height
|
||||||
|
|
||||||
if let iconView = previousTitleView.titleCredibilityIconView.componentView {
|
if let iconView = previousTitleView.titleCredibilityIconView.componentView {
|
||||||
|
@ -48,16 +48,9 @@ public extension ChannelParticipant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct ChannelMemberListState {
|
public struct ChannelMemberListState {
|
||||||
public let list: [RenderedChannelParticipant]
|
public var list: [RenderedChannelParticipant]
|
||||||
public let loadingState: ChannelMemberListLoadingState
|
public var peerStoryStats: [EnginePeer.Id: PeerStoryStats]
|
||||||
|
public var loadingState: ChannelMemberListLoadingState
|
||||||
public func withUpdatedList(_ list: [RenderedChannelParticipant]) -> ChannelMemberListState {
|
|
||||||
return ChannelMemberListState(list: list, loadingState: self.loadingState)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func withUpdatedLoadingState(_ loadingState: ChannelMemberListLoadingState) -> ChannelMemberListState {
|
|
||||||
return ChannelMemberListState(list: self.list, loadingState: loadingState)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChannelMemberListCategory {
|
enum ChannelMemberListCategory {
|
||||||
@ -72,7 +65,7 @@ enum ChannelMemberListCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private protocol ChannelMemberCategoryListContext {
|
private protocol ChannelMemberCategoryListContext {
|
||||||
var listStateValue: ChannelMemberListState { get }
|
//var listStateValue: ChannelMemberListState { get }
|
||||||
var listState: Signal<ChannelMemberListState, NoError> { get }
|
var listState: Signal<ChannelMemberListState, NoError> { get }
|
||||||
func loadMore()
|
func loadMore()
|
||||||
func reset(_ force: Bool)
|
func reset(_ force: Bool)
|
||||||
@ -139,7 +132,20 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
}
|
}
|
||||||
private var listStatePromise: Promise<ChannelMemberListState>
|
private var listStatePromise: Promise<ChannelMemberListState>
|
||||||
var listState: Signal<ChannelMemberListState, NoError> {
|
var listState: Signal<ChannelMemberListState, NoError> {
|
||||||
|
let postbox = self.postbox
|
||||||
return self.listStatePromise.get()
|
return self.listStatePromise.get()
|
||||||
|
|> mapToSignal { state -> Signal<ChannelMemberListState, NoError> in
|
||||||
|
let key: PostboxViewKey = .peerStoryStats(peerIds: Set(state.list.map(\.peer.id)))
|
||||||
|
return postbox.combinedView(keys: [key])
|
||||||
|
|> map { views -> ChannelMemberListState in
|
||||||
|
var state = state
|
||||||
|
if let view = views.views[key] as? PeerStoryStatsView {
|
||||||
|
state.peerStoryStats = view.storyStats
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let loadingDisposable = MetaDisposable()
|
private let loadingDisposable = MetaDisposable()
|
||||||
@ -155,7 +161,7 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.category = category
|
self.category = category
|
||||||
|
|
||||||
self.listStateValue = ChannelMemberListState(list: [], loadingState: .ready(hasMore: true))
|
self.listStateValue = ChannelMemberListState(list: [], peerStoryStats: [:], loadingState: .ready(hasMore: true))
|
||||||
self.listStatePromise = Promise(self.listStateValue)
|
self.listStatePromise = Promise(self.listStateValue)
|
||||||
self.loadMoreInternal(initial: true)
|
self.loadMoreInternal(initial: true)
|
||||||
}
|
}
|
||||||
@ -182,7 +188,7 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
loadCount = requestBatchSize
|
loadCount = requestBatchSize
|
||||||
}
|
}
|
||||||
|
|
||||||
self.listStateValue = self.listStateValue.withUpdatedLoadingState(.loading(initial: initial))
|
self.listStateValue.loadingState = .loading(initial: initial)
|
||||||
|
|
||||||
self.loadingDisposable.set((self.loadMoreSignal(count: loadCount)
|
self.loadingDisposable.set((self.loadMoreSignal(count: loadCount)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] members in
|
|> deliverOnMainQueue).start(next: { [weak self] members in
|
||||||
@ -201,7 +207,10 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.loadingDisposable.set(nil)
|
self.loadingDisposable.set(nil)
|
||||||
self.listStateValue = self.listStateValue.withUpdatedLoadingState(loadingState).withUpdatedList(list)
|
var listState = self.listStateValue
|
||||||
|
listState.loadingState = loadingState
|
||||||
|
listState.list = list
|
||||||
|
self.listStateValue = listState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +273,11 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.loadingDisposable.set(nil)
|
self.loadingDisposable.set(nil)
|
||||||
self.listStateValue = self.listStateValue.withUpdatedList(list)
|
|
||||||
|
var listState = self.listStateValue
|
||||||
|
listState.list = list
|
||||||
|
self.listStateValue = listState
|
||||||
|
|
||||||
if case .loading = self.listStateValue.loadingState {
|
if case .loading = self.listStateValue.loadingState {
|
||||||
self.loadMore()
|
self.loadMore()
|
||||||
}
|
}
|
||||||
@ -290,7 +303,12 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
list.append(member)
|
list.append(member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.listStateValue = self.listStateValue.withUpdatedList(list).withUpdatedLoadingState(.ready(hasMore: members.count >= requestBatchSize))
|
|
||||||
|
var listState = self.listStateValue
|
||||||
|
listState.loadingState = .ready(hasMore: members.count >= requestBatchSize)
|
||||||
|
listState.list = list
|
||||||
|
self.listStateValue = listState
|
||||||
|
|
||||||
if firstLoad {
|
if firstLoad {
|
||||||
self.checkUpdateHead()
|
self.checkUpdateHead()
|
||||||
}
|
}
|
||||||
@ -534,7 +552,9 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if updatedList {
|
if updatedList {
|
||||||
self.listStateValue = self.listStateValue.withUpdatedList(list)
|
var listState = self.listStateValue
|
||||||
|
listState.list = list
|
||||||
|
self.listStateValue = listState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -542,9 +562,9 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
|
|||||||
private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategoryListContext {
|
private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategoryListContext {
|
||||||
private var contexts: [ChannelMemberSingleCategoryListContext] = []
|
private var contexts: [ChannelMemberSingleCategoryListContext] = []
|
||||||
|
|
||||||
var listStateValue: ChannelMemberListState {
|
/*var listStateValue: ChannelMemberListState {
|
||||||
return ChannelMemberMultiCategoryListContext.reduceListStates(self.contexts.map { $0.listStateValue })
|
return ChannelMemberMultiCategoryListContext.reduceListStates(self.contexts.map { $0.listStateValue })
|
||||||
}
|
}*/
|
||||||
|
|
||||||
private static func reduceListStates(_ listStates: [ChannelMemberListState]) -> ChannelMemberListState {
|
private static func reduceListStates(_ listStates: [ChannelMemberListState]) -> ChannelMemberListState {
|
||||||
var allReady = true
|
var allReady = true
|
||||||
@ -555,12 +575,13 @@ private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !allReady {
|
if !allReady {
|
||||||
return ChannelMemberListState(list: [], loadingState: .loading(initial: true))
|
return ChannelMemberListState(list: [], peerStoryStats: [:], loadingState: .loading(initial: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
var list: [RenderedChannelParticipant] = []
|
var list: [RenderedChannelParticipant] = []
|
||||||
var existingIds = Set<PeerId>()
|
var existingIds = Set<PeerId>()
|
||||||
var loadingState: ChannelMemberListLoadingState = .ready(hasMore: false)
|
var loadingState: ChannelMemberListLoadingState = .ready(hasMore: false)
|
||||||
|
var peerStoryStats: [PeerId: PeerStoryStats] = [:]
|
||||||
loop: for i in 0 ..< listStates.count {
|
loop: for i in 0 ..< listStates.count {
|
||||||
for item in listStates[i].list {
|
for item in listStates[i].list {
|
||||||
if !existingIds.contains(item.peer.id) {
|
if !existingIds.contains(item.peer.id) {
|
||||||
@ -568,6 +589,9 @@ private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategory
|
|||||||
list.append(item)
|
list.append(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (id, value) in listStates[i].peerStoryStats {
|
||||||
|
peerStoryStats[id] = value
|
||||||
|
}
|
||||||
switch listStates[i].loadingState {
|
switch listStates[i].loadingState {
|
||||||
case let .loading(initial):
|
case let .loading(initial):
|
||||||
loadingState = .loading(initial: initial)
|
loadingState = .loading(initial: initial)
|
||||||
@ -579,7 +603,7 @@ private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ChannelMemberListState(list: list, loadingState: loadingState)
|
return ChannelMemberListState(list: list, peerStoryStats: peerStoryStats, loadingState: loadingState)
|
||||||
}
|
}
|
||||||
|
|
||||||
var listState: Signal<ChannelMemberListState, NoError> {
|
var listState: Signal<ChannelMemberListState, NoError> {
|
||||||
@ -639,6 +663,7 @@ private final class PeerChannelMemberContextWithSubscribers {
|
|||||||
private let subscribers = Bag<(ChannelMemberListState) -> Void>()
|
private let subscribers = Bag<(ChannelMemberListState) -> Void>()
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
private let becameEmpty: () -> Void
|
private let becameEmpty: () -> Void
|
||||||
|
private var currentValue: ChannelMemberListState?
|
||||||
|
|
||||||
private var emptyTimer: SwiftSignalKit.Timer?
|
private var emptyTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
@ -649,6 +674,7 @@ private final class PeerChannelMemberContextWithSubscribers {
|
|||||||
self.disposable.set((context.listState
|
self.disposable.set((context.listState
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
strongSelf.currentValue = value
|
||||||
for f in strongSelf.subscribers.copyItems() {
|
for f in strongSelf.subscribers.copyItems() {
|
||||||
f(value)
|
f(value)
|
||||||
}
|
}
|
||||||
@ -678,7 +704,9 @@ private final class PeerChannelMemberContextWithSubscribers {
|
|||||||
func subscribe(requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> Disposable {
|
func subscribe(requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> Disposable {
|
||||||
let wasEmpty = self.subscribers.isEmpty
|
let wasEmpty = self.subscribers.isEmpty
|
||||||
let index = self.subscribers.add(updated)
|
let index = self.subscribers.add(updated)
|
||||||
updated(self.context.listStateValue)
|
if let currentValue = self.currentValue {
|
||||||
|
updated(currentValue)
|
||||||
|
}
|
||||||
if wasEmpty {
|
if wasEmpty {
|
||||||
self.emptyTimer?.invalidate()
|
self.emptyTimer?.invalidate()
|
||||||
if requestUpdate {
|
if requestUpdate {
|
||||||
|
@ -707,7 +707,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||||
hasUnseen: true,
|
hasUnseen: true,
|
||||||
hasUnseenCloseFriendsItems: false,
|
hasUnseenCloseFriendsItems: false,
|
||||||
theme: defaultDarkPresentationTheme,
|
colors: AvatarStoryIndicatorComponent.Colors(theme: defaultDarkPresentationTheme),
|
||||||
activeLineWidth: 1.0 + UIScreenPixel,
|
activeLineWidth: 1.0 + UIScreenPixel,
|
||||||
inactiveLineWidth: 1.0 + UIScreenPixel,
|
inactiveLineWidth: 1.0 + UIScreenPixel,
|
||||||
counters: nil
|
counters: nil
|
||||||
|
Loading…
x
Reference in New Issue
Block a user