mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-28 10:55:40 +00:00
Avatar story indicator counters
This commit is contained in:
parent
94e6f28efe
commit
a0817a831b
@ -2795,7 +2795,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
hasUnseen: displayStoryIndicator,
|
hasUnseen: displayStoryIndicator,
|
||||||
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
|
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
|
||||||
activeLineWidth: 2.0,
|
activeLineWidth: 2.0,
|
||||||
inactiveLineWidth: 1.0 + UIScreenPixel
|
inactiveLineWidth: 1.0 + UIScreenPixel,
|
||||||
|
counters: nil
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: indicatorFrame.size
|
containerSize: indicatorFrame.size
|
||||||
|
|||||||
@ -201,8 +201,10 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||||||
})]
|
})]
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasUnseenStories: Bool?
|
var storyStats: (total: Int, unseen: Int)?
|
||||||
if let storyData = storyData {
|
if let storyData = storyData {
|
||||||
|
storyStats = (storyData.count, storyData.unseenCount)
|
||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
if storyData.unseenCount != 0 {
|
if storyData.unseenCount != 0 {
|
||||||
@ -219,12 +221,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
status = .custom(string: text, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: text, multiline: false, isActive: false, icon: nil)
|
||||||
hasUnseenStories = storyData.unseenCount != 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch : .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
|
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch : .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
|
||||||
interaction.openPeer(peer, .generic)
|
interaction.openPeer(peer, .generic)
|
||||||
}, itemHighlighting: interaction.itemHighlighting, contextAction: itemContextAction, hasUnseenStories: hasUnseenStories, openStories: { peer, sourceNode in
|
}, itemHighlighting: interaction.itemHighlighting, contextAction: itemContextAction, storyStats: storyStats, openStories: { peer, sourceNode in
|
||||||
if case let .peer(peerValue, _) = peer, let peerValue {
|
if case let .peer(peerValue, _) = peer, let peerValue {
|
||||||
interaction.openStories(peerValue, sourceNode)
|
interaction.openStories(peerValue, sourceNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -180,7 +180,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
|||||||
let arrowAction: (() -> Void)?
|
let arrowAction: (() -> Void)?
|
||||||
let animationCache: AnimationCache?
|
let animationCache: AnimationCache?
|
||||||
let animationRenderer: MultiAnimationRenderer?
|
let animationRenderer: MultiAnimationRenderer?
|
||||||
let hasUnseenStories: Bool?
|
let storyStats: (total: Int, unseen: Int)?
|
||||||
let openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)?
|
let openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)?
|
||||||
|
|
||||||
public let selectable: Bool
|
public let selectable: Bool
|
||||||
@ -217,7 +217,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
|||||||
contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil,
|
contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil,
|
||||||
animationCache: AnimationCache? = nil,
|
animationCache: AnimationCache? = nil,
|
||||||
animationRenderer: MultiAnimationRenderer? = nil,
|
animationRenderer: MultiAnimationRenderer? = nil,
|
||||||
hasUnseenStories: Bool? = nil,
|
storyStats: (total: Int, unseen: Int)? = nil,
|
||||||
openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? = nil
|
openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
@ -248,7 +248,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
|||||||
self.arrowAction = arrowAction
|
self.arrowAction = arrowAction
|
||||||
self.animationCache = animationCache
|
self.animationCache = animationCache
|
||||||
self.animationRenderer = animationRenderer
|
self.animationRenderer = animationRenderer
|
||||||
self.hasUnseenStories = hasUnseenStories
|
self.storyStats = storyStats
|
||||||
self.openStories = openStories
|
self.openStories = openStories
|
||||||
|
|
||||||
if let index = index {
|
if let index = index {
|
||||||
@ -1088,7 +1088,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
var avatarScale: CGFloat = 1.0
|
var avatarScale: CGFloat = 1.0
|
||||||
|
|
||||||
if item.hasUnseenStories != nil {
|
if item.storyStats != nil {
|
||||||
avatarScale *= (avatarFrame.width - 2.0 * 2.0) / avatarFrame.width
|
avatarScale *= (avatarFrame.width - 2.0 * 2.0) / avatarFrame.width
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1096,7 +1096,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let storyIndicatorScale: CGFloat = 1.0
|
let storyIndicatorScale: CGFloat = 1.0
|
||||||
|
|
||||||
if let displayStoryIndicator = item.hasUnseenStories {
|
if let storyStats = item.storyStats {
|
||||||
var indicatorTransition = Transition(transition)
|
var indicatorTransition = Transition(transition)
|
||||||
let avatarStoryIndicator: ComponentView<Empty>
|
let avatarStoryIndicator: ComponentView<Empty>
|
||||||
if let current = strongSelf.avatarStoryIndicator {
|
if let current = strongSelf.avatarStoryIndicator {
|
||||||
@ -1113,10 +1113,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let _ = avatarStoryIndicator.update(
|
let _ = avatarStoryIndicator.update(
|
||||||
transition: indicatorTransition,
|
transition: indicatorTransition,
|
||||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||||
hasUnseen: displayStoryIndicator,
|
hasUnseen: storyStats.unseen != 0,
|
||||||
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
|
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
|
||||||
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)
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: indicatorFrame.size
|
containerSize: indicatorFrame.size
|
||||||
|
|||||||
@ -215,7 +215,8 @@ public final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||||||
hasUnseen: hasUnseenStories,
|
hasUnseen: hasUnseenStories,
|
||||||
isDarkTheme: theme.overallDarkAppearance,
|
isDarkTheme: theme.overallDarkAppearance,
|
||||||
activeLineWidth: 1.0,
|
activeLineWidth: 1.0,
|
||||||
inactiveLineWidth: 1.0
|
inactiveLineWidth: 1.0,
|
||||||
|
counters: nil
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: self.avatarNode.bounds.insetBy(dx: 2.0, dy: 2.0).size
|
containerSize: self.avatarNode.bounds.insetBy(dx: 2.0, dy: 2.0).size
|
||||||
|
|||||||
@ -5,21 +5,34 @@ import ComponentFlow
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
public final class AvatarStoryIndicatorComponent: Component {
|
public final class AvatarStoryIndicatorComponent: Component {
|
||||||
|
public struct Counters: Equatable {
|
||||||
|
public var totalCount: Int
|
||||||
|
public var unseenCount: Int
|
||||||
|
|
||||||
|
public init(totalCount: Int, unseenCount: Int) {
|
||||||
|
self.totalCount = totalCount
|
||||||
|
self.unseenCount = unseenCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public let hasUnseen: Bool
|
public let hasUnseen: Bool
|
||||||
public let isDarkTheme: Bool
|
public let isDarkTheme: Bool
|
||||||
public let activeLineWidth: CGFloat
|
public let activeLineWidth: CGFloat
|
||||||
public let inactiveLineWidth: CGFloat
|
public let inactiveLineWidth: CGFloat
|
||||||
|
public let counters: Counters?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
hasUnseen: Bool,
|
hasUnseen: Bool,
|
||||||
isDarkTheme: Bool,
|
isDarkTheme: Bool,
|
||||||
activeLineWidth: CGFloat,
|
activeLineWidth: CGFloat,
|
||||||
inactiveLineWidth: CGFloat
|
inactiveLineWidth: CGFloat,
|
||||||
|
counters: Counters?
|
||||||
) {
|
) {
|
||||||
self.hasUnseen = hasUnseen
|
self.hasUnseen = hasUnseen
|
||||||
self.isDarkTheme = isDarkTheme
|
self.isDarkTheme = isDarkTheme
|
||||||
self.activeLineWidth = activeLineWidth
|
self.activeLineWidth = activeLineWidth
|
||||||
self.inactiveLineWidth = inactiveLineWidth
|
self.inactiveLineWidth = inactiveLineWidth
|
||||||
|
self.counters = counters
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: AvatarStoryIndicatorComponent, rhs: AvatarStoryIndicatorComponent) -> Bool {
|
public static func ==(lhs: AvatarStoryIndicatorComponent, rhs: AvatarStoryIndicatorComponent) -> Bool {
|
||||||
@ -35,6 +48,9 @@ public final class AvatarStoryIndicatorComponent: Component {
|
|||||||
if lhs.inactiveLineWidth != rhs.inactiveLineWidth {
|
if lhs.inactiveLineWidth != rhs.inactiveLineWidth {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.counters != rhs.counters {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,26 +92,79 @@ public final class AvatarStoryIndicatorComponent: Component {
|
|||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setLineWidth(lineWidth)
|
context.setLineWidth(lineWidth)
|
||||||
context.addEllipse(in: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
|
|
||||||
context.replacePathWithStrokedPath()
|
|
||||||
context.clip()
|
|
||||||
|
|
||||||
var locations: [CGFloat] = [1.0, 0.0]
|
if let counters = component.counters, counters.totalCount > 1 {
|
||||||
let colors: [CGColor]
|
let center = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
|
||||||
if component.hasUnseen {
|
let radius = (diameter - lineWidth) * 0.5
|
||||||
colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor]
|
let spacing: CGFloat = 2.0
|
||||||
} else {
|
let angularSpacing: CGFloat = spacing / radius
|
||||||
if component.isDarkTheme {
|
let circleLength = CGFloat.pi * 2.0 * radius
|
||||||
colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor]
|
let segmentLength = (circleLength - spacing * CGFloat(counters.totalCount)) / CGFloat(counters.totalCount)
|
||||||
} else {
|
let segmentAngle = segmentLength / radius
|
||||||
colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
|
||||||
|
for pass in 0 ..< 2 {
|
||||||
|
context.resetClip()
|
||||||
|
|
||||||
|
let startIndex: Int
|
||||||
|
let endIndex: Int
|
||||||
|
if pass == 0 {
|
||||||
|
startIndex = 0
|
||||||
|
endIndex = counters.totalCount - counters.unseenCount
|
||||||
|
} else {
|
||||||
|
startIndex = counters.totalCount - counters.unseenCount
|
||||||
|
endIndex = counters.totalCount
|
||||||
|
}
|
||||||
|
if startIndex < endIndex {
|
||||||
|
for i in startIndex ..< endIndex {
|
||||||
|
let startAngle = CGFloat(i) * (angularSpacing + segmentAngle) - CGFloat.pi * 0.5 + angularSpacing * 0.5
|
||||||
|
context.move(to: CGPoint(x: center.x + cos(startAngle) * radius, y: center.y + sin(startAngle) * radius))
|
||||||
|
context.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: startAngle + segmentAngle, clockwise: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.replacePathWithStrokedPath()
|
||||||
|
context.clip()
|
||||||
|
|
||||||
|
var locations: [CGFloat] = [1.0, 0.0]
|
||||||
|
let colors: [CGColor]
|
||||||
|
if pass == 1 {
|
||||||
|
colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor]
|
||||||
|
} else {
|
||||||
|
if component.isDarkTheme {
|
||||||
|
colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor]
|
||||||
|
} else {
|
||||||
|
colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
context.addEllipse(in: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
|
||||||
|
|
||||||
|
context.replacePathWithStrokedPath()
|
||||||
|
context.clip()
|
||||||
|
|
||||||
|
var locations: [CGFloat] = [1.0, 0.0]
|
||||||
|
let colors: [CGColor]
|
||||||
|
if component.hasUnseen {
|
||||||
|
colors = [UIColor(rgb: 0x34C76F).cgColor, UIColor(rgb: 0x3DA1FD).cgColor]
|
||||||
|
} else {
|
||||||
|
if component.isDarkTheme {
|
||||||
|
colors = [UIColor(rgb: 0x48484A).cgColor, UIColor(rgb: 0x48484A).cgColor]
|
||||||
|
} else {
|
||||||
|
colors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||||
}
|
}
|
||||||
|
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
|
||||||
|
|
||||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
|
||||||
})
|
})
|
||||||
transition.setFrame(view: self.indicatorView, frame: CGRect(origin: CGPoint(x: (availableSize.width - imageDiameter) * 0.5, y: (availableSize.height - imageDiameter) * 0.5), size: CGSize(width: imageDiameter, height: imageDiameter)))
|
transition.setFrame(view: self.indicatorView, frame: CGRect(origin: CGPoint(x: (availableSize.width - imageDiameter) * 0.5, y: (availableSize.height - imageDiameter) * 0.5), size: CGSize(width: imageDiameter, height: imageDiameter)))
|
||||||
|
|
||||||
|
|||||||
@ -470,7 +470,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
hasUnseen: hasUnseenStories,
|
hasUnseen: hasUnseenStories,
|
||||||
isDarkTheme: theme.overallDarkAppearance,
|
isDarkTheme: theme.overallDarkAppearance,
|
||||||
activeLineWidth: 3.0,
|
activeLineWidth: 3.0,
|
||||||
inactiveLineWidth: 2.0
|
inactiveLineWidth: 2.0,
|
||||||
|
counters: nil
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: self.avatarNode.bounds.size
|
containerSize: self.avatarNode.bounds.size
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user