mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-08 09:49:51 +00:00
Channel recommendation improvements
This commit is contained in:
parent
c688b5ad5f
commit
17a203d6b5
@ -29,6 +29,9 @@ public final class AvatarBadgeView: UIImageView {
|
|||||||
private struct Parameters: Equatable {
|
private struct Parameters: Equatable {
|
||||||
var size: CGSize
|
var size: CGSize
|
||||||
var text: String
|
var text: String
|
||||||
|
var hasTimeoutIcon: Bool
|
||||||
|
var useSolidColor: Bool
|
||||||
|
var strokeColor: UIColor?
|
||||||
}
|
}
|
||||||
|
|
||||||
private var originalContent: OriginalContent?
|
private var originalContent: OriginalContent?
|
||||||
@ -50,8 +53,8 @@ public final class AvatarBadgeView: UIImageView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(size: CGSize, text: String) {
|
public func update(size: CGSize, text: String, hasTimeoutIcon: Bool = true, useSolidColor: Bool = false, strokeColor: UIColor? = nil) {
|
||||||
let parameters = Parameters(size: size, text: text)
|
let parameters = Parameters(size: size, text: text, hasTimeoutIcon: hasTimeoutIcon, useSolidColor: useSolidColor, strokeColor: strokeColor)
|
||||||
if self.parameters != parameters || !self.hasContent {
|
if self.parameters != parameters || !self.hasContent {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
self.update()
|
self.update()
|
||||||
@ -192,25 +195,90 @@ public final class AvatarBadgeView: UIImageView {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.image = generateImage(parameters.size, rotatedContext: { size, context in
|
var solidColor: UIColor?
|
||||||
|
if parameters.useSolidColor {
|
||||||
|
let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)!
|
||||||
|
context.withFlippedContext({ context in
|
||||||
|
if let cgImage = blurredImage.cgImage {
|
||||||
|
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
solidColor = context.colorAt(.zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
var badgeSize = parameters.size
|
||||||
|
|
||||||
|
let strokeWidth: CGFloat = 1.0 + UIScreenPixel
|
||||||
|
var size = parameters.size
|
||||||
|
var offset: CGPoint = .zero
|
||||||
|
if parameters.strokeColor != nil {
|
||||||
|
offset = CGPoint(x: strokeWidth / 2.0, y: strokeWidth / 2.0)
|
||||||
|
badgeSize.width += strokeWidth
|
||||||
|
badgeSize.height += strokeWidth
|
||||||
|
size.width += strokeWidth * 2.0
|
||||||
|
size.height += strokeWidth * 2.0
|
||||||
|
}
|
||||||
|
|
||||||
|
self.image = generateImage(size, rotatedContext: { size, context in
|
||||||
UIGraphicsPushContext(context)
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setBlendMode(.copy)
|
|
||||||
context.setFillColor(UIColor.black.cgColor)
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
||||||
|
|
||||||
blurredImage.draw(in: CGRect(origin: CGPoint(), size: size), blendMode: .sourceIn, alpha: 1.0)
|
|
||||||
|
|
||||||
context.setBlendMode(.normal)
|
|
||||||
|
|
||||||
let textColor: UIColor
|
let textColor: UIColor
|
||||||
|
if parameters.useSolidColor {
|
||||||
|
textColor = .white
|
||||||
|
} else {
|
||||||
if isLightImage {
|
if isLightImage {
|
||||||
textColor = UIColor(white: 0.7, alpha: 1.0)
|
textColor = UIColor(white: 0.7, alpha: 1.0)
|
||||||
} else {
|
} else {
|
||||||
textColor = .white
|
textColor = .white
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if var solidColor {
|
||||||
|
func adjustedBackgroundColor(backgroundColor: UIColor, textColor: UIColor) -> UIColor {
|
||||||
|
let minContrastRatio: CGFloat = 4.5
|
||||||
|
if backgroundColor.contrastRatio(with: textColor) < minContrastRatio {
|
||||||
|
var hue: CGFloat = 0
|
||||||
|
var saturation: CGFloat = 0
|
||||||
|
var brightness: CGFloat = 0
|
||||||
|
var alpha: CGFloat = 0
|
||||||
|
backgroundColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
|
||||||
|
return UIColor(hue: hue, saturation: saturation, brightness: brightness * 0.9, alpha: alpha)
|
||||||
|
} else {
|
||||||
|
return backgroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
solidColor = adjustedBackgroundColor(backgroundColor: solidColor, textColor: textColor)
|
||||||
|
|
||||||
|
if let strokeColor = parameters.strokeColor {
|
||||||
|
context.setStrokeColor(strokeColor.cgColor)
|
||||||
|
context.setLineWidth(strokeWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.setFillColor(solidColor.cgColor)
|
||||||
|
} else {
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.setFillColor(UIColor.black.cgColor)
|
||||||
|
}
|
||||||
|
if badgeSize.width != badgeSize.height {
|
||||||
|
let path = UIBezierPath(roundedRect: CGRect(origin: offset, size: badgeSize), cornerRadius: badgeSize.height / 2.0)
|
||||||
|
context.addPath(path.cgPath)
|
||||||
|
if let _ = parameters.strokeColor {
|
||||||
|
context.drawPath(using: .fillStroke)
|
||||||
|
} else {
|
||||||
|
context.fillPath()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.fillEllipse(in: CGRect(origin: offset, size: badgeSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = solidColor {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
blurredImage.draw(in: CGRect(origin: CGPoint(), size: badgeSize), blendMode: .sourceIn, alpha: 1.0)
|
||||||
|
context.setBlendMode(.normal)
|
||||||
|
}
|
||||||
|
|
||||||
var fontSize: CGFloat = floor(parameters.size.height * 0.48)
|
var fontSize: CGFloat = floor(parameters.size.height * 0.48)
|
||||||
while true {
|
while true {
|
||||||
@ -225,6 +293,7 @@ public final class AvatarBadgeView: UIImageView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parameters.hasTimeoutIcon {
|
||||||
let lineWidth: CGFloat = 1.5
|
let lineWidth: CGFloat = 1.5
|
||||||
let lineInset: CGFloat = 2.0
|
let lineInset: CGFloat = 2.0
|
||||||
let lineRadius: CGFloat = size.width * 0.5 - lineInset - lineWidth * 0.5
|
let lineRadius: CGFloat = size.width * 0.5 - lineInset - lineWidth * 0.5
|
||||||
@ -248,6 +317,7 @@ public final class AvatarBadgeView: UIImageView {
|
|||||||
context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
|
context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
|
||||||
context.strokePath()
|
context.strokePath()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*if isLightImage {
|
/*if isLightImage {
|
||||||
context.setLineWidth(UIScreenPixel)
|
context.setLineWidth(UIScreenPixel)
|
||||||
|
|||||||
@ -172,6 +172,12 @@ public extension UIColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contrastRatio(with other: UIColor) -> CGFloat {
|
||||||
|
let l1 = self.lightness
|
||||||
|
let l2 = other.lightness
|
||||||
|
return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05)
|
||||||
|
}
|
||||||
|
|
||||||
var brightness: CGFloat {
|
var brightness: CGFloat {
|
||||||
var hue: CGFloat = 0.0
|
var hue: CGFloat = 0.0
|
||||||
var saturation: CGFloat = 0.0
|
var saturation: CGFloat = 0.0
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import ChatMessageItemCommon
|
|||||||
import RoundedRectWithTailPath
|
import RoundedRectWithTailPath
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
|
import BundleIconComponent
|
||||||
import ChatMessageBackground
|
import ChatMessageBackground
|
||||||
|
|
||||||
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? {
|
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? {
|
||||||
@ -545,6 +546,8 @@ private final class ChannelItemComponent: Component {
|
|||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
private let subtitle = ComponentView<Empty>()
|
private let subtitle = ComponentView<Empty>()
|
||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
|
private let avatarBadge: AvatarBadgeView
|
||||||
|
private let subtitleIcon = ComponentView<Empty>()
|
||||||
|
|
||||||
private var component: ChannelItemComponent?
|
private var component: ChannelItemComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
@ -553,12 +556,17 @@ private final class ChannelItemComponent: Component {
|
|||||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||||
self.avatarNode.isUserInteractionEnabled = false
|
self.avatarNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.avatarBadge = AvatarBadgeView(frame: CGRect())
|
||||||
|
|
||||||
self.containerButton = HighlightTrackingButton()
|
self.containerButton = HighlightTrackingButton()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.addSubview(self.containerButton)
|
self.addSubview(self.containerButton)
|
||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
|
self.avatarNode.view.addSubview(self.avatarBadge)
|
||||||
|
|
||||||
|
self.avatarNode.badgeView = self.avatarBadge
|
||||||
|
|
||||||
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
@ -581,25 +589,38 @@ private final class ChannelItemComponent: Component {
|
|||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(string: component.peer.compactDisplayTitle, font: Font.regular(11.0), textColor: component.theme.chat.message.incoming.primaryTextColor))
|
text: .plain(NSAttributedString(string: component.peer.compactDisplayTitle, font: Font.regular(11.0), textColor: component.theme.chat.message.incoming.primaryTextColor)),
|
||||||
|
horizontalAlignment: .center,
|
||||||
|
maximumNumberOfLines: 2
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: itemSize.width - 20.0, height: 100.0)
|
containerSize: CGSize(width: itemSize.width - 16.0, height: 100.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let subtitleSize = self.subtitle.update(
|
let subtitleSize = self.subtitle.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(string: component.subtitle, font: Font.regular(10.0), textColor: component.theme.chat.message.incoming.secondaryTextColor))
|
text: .plain(NSAttributedString(string: component.subtitle, font: Font.with(size: 9.0, design: .round, weight: .bold), textColor: .white))
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: itemSize.width - 6.0, height: 100.0)
|
containerSize: CGSize(width: itemSize.width - 6.0, height: 100.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let subtitleIconSize = self.subtitleIcon.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(BundleIconComponent(name: "Chat/Message/Subscriber", tintColor: .white)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: itemSize.width - 6.0, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||||
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - avatarSize.width) / 2.0), y: 0.0), size: avatarSize)
|
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - avatarSize.width) / 2.0), y: 0.0), size: avatarSize)
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - titleSize.width) / 2.0), y: avatarFrame.maxY + 4.0), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - titleSize.width) / 2.0), y: avatarFrame.maxY + 4.0), size: titleSize)
|
||||||
let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize)
|
|
||||||
|
let subtitleSpacing: CGFloat = 1.0 + UIScreenPixel
|
||||||
|
let subtitleTotalWidth = subtitleIconSize.width + subtitleSize.width + subtitleSpacing
|
||||||
|
let subtitleIconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemSize.width - subtitleTotalWidth) / 2.0) + 1.0 - UIScreenPixel, y: avatarFrame.maxY - subtitleSize.height + 1.0 - UIScreenPixel), size: subtitleIconSize)
|
||||||
|
let subtitleFrame = CGRect(origin: CGPoint(x: subtitleIconFrame.maxX + subtitleSpacing, y: avatarFrame.maxY - subtitleSize.height - UIScreenPixel), size: subtitleSize)
|
||||||
|
|
||||||
self.avatarNode.frame = avatarFrame
|
self.avatarNode.frame = avatarFrame
|
||||||
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer)
|
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer)
|
||||||
@ -607,17 +628,31 @@ private final class ChannelItemComponent: Component {
|
|||||||
if let titleView = self.title.view {
|
if let titleView = self.title.view {
|
||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
titleView.isUserInteractionEnabled = false
|
titleView.isUserInteractionEnabled = false
|
||||||
self.containerButton.addSubview(titleView)
|
self.addSubview(titleView)
|
||||||
}
|
}
|
||||||
titleView.frame = titleFrame
|
titleView.frame = titleFrame
|
||||||
}
|
}
|
||||||
if let subtitleView = self.subtitle.view {
|
if let subtitleView = self.subtitle.view {
|
||||||
if subtitleView.superview == nil {
|
if subtitleView.superview == nil {
|
||||||
subtitleView.isUserInteractionEnabled = false
|
subtitleView.isUserInteractionEnabled = false
|
||||||
self.containerButton.addSubview(subtitleView)
|
self.addSubview(subtitleView)
|
||||||
}
|
}
|
||||||
subtitleView.frame = subtitleFrame
|
subtitleView.frame = subtitleFrame
|
||||||
}
|
}
|
||||||
|
if let subtitleIconView = self.subtitleIcon.view {
|
||||||
|
if subtitleIconView.superview == nil {
|
||||||
|
subtitleIconView.isUserInteractionEnabled = false
|
||||||
|
self.addSubview(subtitleIconView)
|
||||||
|
}
|
||||||
|
subtitleIconView.frame = subtitleIconFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
let strokeWidth: CGFloat = 1.0 + UIScreenPixel
|
||||||
|
let avatarBadgeSize = CGSize(width: subtitleSize.width + 4.0 + 4.0 + 6.0, height: 15.0)
|
||||||
|
self.avatarBadge.update(size: avatarBadgeSize, text: "", hasTimeoutIcon: false, useSolidColor: true, strokeColor: component.theme.chat.message.incoming.bubble.withoutWallpaper.fill.first!)
|
||||||
|
|
||||||
|
let avatarBadgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((avatarFrame.width - avatarBadgeSize.width) / 2.0), y: avatarFrame.height - avatarBadgeSize.height + 2.0), size: avatarBadgeSize).insetBy(dx: -strokeWidth, dy: -strokeWidth)
|
||||||
|
self.avatarBadge.frame = avatarBadgeFrame
|
||||||
|
|
||||||
self.containerButton.frame = CGRect(origin: .zero, size: itemSize)
|
self.containerButton.frame = CGRect(origin: .zero, size: itemSize)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user