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,
|
||||
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
|
||||
activeLineWidth: 2.0,
|
||||
inactiveLineWidth: 1.0 + UIScreenPixel
|
||||
inactiveLineWidth: 1.0 + UIScreenPixel,
|
||||
counters: nil
|
||||
)),
|
||||
environment: {},
|
||||
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 {
|
||||
storyStats = (storyData.count, storyData.unseenCount)
|
||||
|
||||
let text: String
|
||||
//TODO:localize
|
||||
if storyData.unseenCount != 0 {
|
||||
@ -219,12 +221,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
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
|
||||
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 {
|
||||
interaction.openStories(peerValue, sourceNode)
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
let arrowAction: (() -> Void)?
|
||||
let animationCache: AnimationCache?
|
||||
let animationRenderer: MultiAnimationRenderer?
|
||||
let hasUnseenStories: Bool?
|
||||
let storyStats: (total: Int, unseen: Int)?
|
||||
let openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)?
|
||||
|
||||
public let selectable: Bool
|
||||
@ -217,7 +217,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, arrowAction: (() -> Void)? = nil,
|
||||
animationCache: AnimationCache? = nil,
|
||||
animationRenderer: MultiAnimationRenderer? = nil,
|
||||
hasUnseenStories: Bool? = nil,
|
||||
storyStats: (total: Int, unseen: Int)? = nil,
|
||||
openStories: ((ContactsPeerItemPeer, ASDisplayNode) -> Void)? = nil
|
||||
) {
|
||||
self.presentationData = presentationData
|
||||
@ -248,7 +248,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
|
||||
self.arrowAction = arrowAction
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.hasUnseenStories = hasUnseenStories
|
||||
self.storyStats = storyStats
|
||||
self.openStories = openStories
|
||||
|
||||
if let index = index {
|
||||
@ -1088,7 +1088,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
var avatarScale: CGFloat = 1.0
|
||||
|
||||
if item.hasUnseenStories != nil {
|
||||
if item.storyStats != nil {
|
||||
avatarScale *= (avatarFrame.width - 2.0 * 2.0) / avatarFrame.width
|
||||
}
|
||||
|
||||
@ -1096,7 +1096,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let storyIndicatorScale: CGFloat = 1.0
|
||||
|
||||
if let displayStoryIndicator = item.hasUnseenStories {
|
||||
if let storyStats = item.storyStats {
|
||||
var indicatorTransition = Transition(transition)
|
||||
let avatarStoryIndicator: ComponentView<Empty>
|
||||
if let current = strongSelf.avatarStoryIndicator {
|
||||
@ -1113,10 +1113,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
let _ = avatarStoryIndicator.update(
|
||||
transition: indicatorTransition,
|
||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||
hasUnseen: displayStoryIndicator,
|
||||
hasUnseen: storyStats.unseen != 0,
|
||||
isDarkTheme: item.presentationData.theme.overallDarkAppearance,
|
||||
activeLineWidth: 1.0 + UIScreenPixel,
|
||||
inactiveLineWidth: 1.0 + UIScreenPixel
|
||||
inactiveLineWidth: 1.0 + UIScreenPixel,
|
||||
counters: AvatarStoryIndicatorComponent.Counters(totalCount: storyStats.total, unseenCount: storyStats.unseen)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: indicatorFrame.size
|
||||
|
||||
@ -215,7 +215,8 @@ public final class ChatAvatarNavigationNode: ASDisplayNode {
|
||||
hasUnseen: hasUnseenStories,
|
||||
isDarkTheme: theme.overallDarkAppearance,
|
||||
activeLineWidth: 1.0,
|
||||
inactiveLineWidth: 1.0
|
||||
inactiveLineWidth: 1.0,
|
||||
counters: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: self.avatarNode.bounds.insetBy(dx: 2.0, dy: 2.0).size
|
||||
|
||||
@ -5,21 +5,34 @@ import ComponentFlow
|
||||
import TelegramPresentationData
|
||||
|
||||
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 isDarkTheme: Bool
|
||||
public let activeLineWidth: CGFloat
|
||||
public let inactiveLineWidth: CGFloat
|
||||
public let counters: Counters?
|
||||
|
||||
public init(
|
||||
hasUnseen: Bool,
|
||||
isDarkTheme: Bool,
|
||||
activeLineWidth: CGFloat,
|
||||
inactiveLineWidth: CGFloat
|
||||
inactiveLineWidth: CGFloat,
|
||||
counters: Counters?
|
||||
) {
|
||||
self.hasUnseen = hasUnseen
|
||||
self.isDarkTheme = isDarkTheme
|
||||
self.activeLineWidth = activeLineWidth
|
||||
self.inactiveLineWidth = inactiveLineWidth
|
||||
self.counters = counters
|
||||
}
|
||||
|
||||
public static func ==(lhs: AvatarStoryIndicatorComponent, rhs: AvatarStoryIndicatorComponent) -> Bool {
|
||||
@ -35,6 +48,9 @@ public final class AvatarStoryIndicatorComponent: Component {
|
||||
if lhs.inactiveLineWidth != rhs.inactiveLineWidth {
|
||||
return false
|
||||
}
|
||||
if lhs.counters != rhs.counters {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -76,26 +92,79 @@ public final class AvatarStoryIndicatorComponent: Component {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
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]
|
||||
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]
|
||||
if let counters = component.counters, counters.totalCount > 1 {
|
||||
let center = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
|
||||
let radius = (diameter - lineWidth) * 0.5
|
||||
let spacing: CGFloat = 2.0
|
||||
let angularSpacing: CGFloat = spacing / radius
|
||||
let circleLength = CGFloat.pi * 2.0 * radius
|
||||
let segmentLength = (circleLength - spacing * CGFloat(counters.totalCount)) / CGFloat(counters.totalCount)
|
||||
let segmentAngle = segmentLength / radius
|
||||
|
||||
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)))
|
||||
|
||||
|
||||
@ -470,7 +470,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
hasUnseen: hasUnseenStories,
|
||||
isDarkTheme: theme.overallDarkAppearance,
|
||||
activeLineWidth: 3.0,
|
||||
inactiveLineWidth: 2.0
|
||||
inactiveLineWidth: 2.0,
|
||||
counters: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: self.avatarNode.bounds.size
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user