mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Folder improvements
This commit is contained in:
parent
0d5468a567
commit
42f43bf767
@ -127,6 +127,28 @@ public class CheckNode: ASDisplayNode {
|
||||
animation.timingFunction = CAMediaTimingFunction(name: selected ? CAMediaTimingFunctionName.easeOut : CAMediaTimingFunctionName.easeIn)
|
||||
animation.duration = selected ? 0.21 : 0.15
|
||||
self.pop_add(animation, forKey: "progress")
|
||||
|
||||
if selected {
|
||||
self.layer.animateScale(from: 1.0, to: 0.9, duration: 0.08, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.layer.animateScale(from: 0.9, to: 1.1, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.layer.animateScale(from: 1.1, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
self.layer.animateScale(from: 1.0, to: 0.9, duration: 0.08, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.layer.animateScale(from: 0.9, to: 1.0, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
self.pop_removeAllAnimations()
|
||||
self.animatingOut = false
|
||||
@ -152,108 +174,11 @@ public class CheckNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
if let parameters = parameters as? CheckNodeParameters {
|
||||
let center = CGPoint(x: bounds.width / 2.0, y: bounds.width / 2.0)
|
||||
|
||||
var borderWidth: CGFloat = 1.0 + UIScreenPixel
|
||||
if parameters.theme.hasInset {
|
||||
borderWidth = 1.5
|
||||
}
|
||||
if let customBorderWidth = parameters.theme.borderWidth {
|
||||
borderWidth = customBorderWidth
|
||||
}
|
||||
|
||||
let checkWidth: CGFloat = 1.5
|
||||
|
||||
let inset: CGFloat = parameters.theme.hasInset ? 2.0 - UIScreenPixel : 0.0
|
||||
|
||||
let checkProgress = parameters.animatingOut ? 1.0 : parameters.animationProgress
|
||||
let fillProgress = parameters.animatingOut ? 1.0 : min(1.0, parameters.animationProgress * 1.35)
|
||||
|
||||
context.setStrokeColor(parameters.theme.borderColor.cgColor)
|
||||
if parameters.theme.isDottedBorder {
|
||||
context.setLineDash(phase: 0.0, lengths: [4.0, 4.0])
|
||||
}
|
||||
context.setLineWidth(borderWidth)
|
||||
|
||||
let maybeScaleOut = {
|
||||
let animate: Bool
|
||||
if case .counter = parameters.content {
|
||||
animate = true
|
||||
} else if parameters.animatingOut {
|
||||
animate = true
|
||||
} else {
|
||||
animate = false
|
||||
}
|
||||
if animate {
|
||||
context.translateBy(x: bounds.width / 2.0, y: bounds.height / 2.0)
|
||||
context.scaleBy(x: parameters.animationProgress, y: parameters.animationProgress)
|
||||
context.translateBy(x: -bounds.width / 2.0, y: -bounds.height / 2.0)
|
||||
|
||||
context.setAlpha(parameters.animationProgress)
|
||||
}
|
||||
}
|
||||
|
||||
let borderInset = borderWidth / 2.0 + inset
|
||||
let borderProgress: CGFloat = parameters.theme.filledBorder ? fillProgress : 1.0
|
||||
let borderFrame = bounds.insetBy(dx: borderInset, dy: borderInset)
|
||||
|
||||
if parameters.theme.filledBorder {
|
||||
maybeScaleOut()
|
||||
}
|
||||
|
||||
context.saveGState()
|
||||
if parameters.theme.hasShadow {
|
||||
context.setShadow(offset: CGSize(), blur: 2.5, color: UIColor(rgb: 0x000000, alpha: 0.22).cgColor)
|
||||
}
|
||||
|
||||
context.strokeEllipse(in: borderFrame.insetBy(dx: borderFrame.width * (1.0 - borderProgress), dy: borderFrame.height * (1.0 - borderProgress)))
|
||||
context.restoreGState()
|
||||
|
||||
if !parameters.theme.filledBorder {
|
||||
maybeScaleOut()
|
||||
}
|
||||
|
||||
context.setFillColor(parameters.theme.backgroundColor.cgColor)
|
||||
|
||||
let fillInset = parameters.theme.overlayBorder ? borderWidth + inset : inset
|
||||
let fillFrame = bounds.insetBy(dx: fillInset, dy: fillInset)
|
||||
context.fillEllipse(in: fillFrame.insetBy(dx: fillFrame.width * (1.0 - fillProgress), dy: fillFrame.height * (1.0 - fillProgress)))
|
||||
|
||||
switch parameters.content {
|
||||
case .check:
|
||||
let scale = (bounds.width - inset) / 18.0
|
||||
let firstSegment: CGFloat = max(0.0, min(1.0, checkProgress * 3.0))
|
||||
let s = CGPoint(x: center.x - (4.0 - 0.3333) * scale, y: center.y + 0.5 * scale)
|
||||
let p1 = CGPoint(x: 2.5 * scale, y: 3.0 * scale)
|
||||
let p2 = CGPoint(x: 4.6667 * scale, y: -6.0 * scale)
|
||||
|
||||
if !firstSegment.isZero {
|
||||
if firstSegment < 1.0 {
|
||||
context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment))
|
||||
context.addLine(to: s)
|
||||
} else {
|
||||
let secondSegment = (checkProgress - 0.33) * 1.5
|
||||
context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment))
|
||||
context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y))
|
||||
context.addLine(to: s)
|
||||
}
|
||||
}
|
||||
|
||||
context.setStrokeColor(parameters.theme.strokeColor.cgColor)
|
||||
if parameters.theme.strokeColor == .clear {
|
||||
context.setBlendMode(.clear)
|
||||
}
|
||||
context.setLineWidth(checkWidth)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
context.setMiterLimit(10.0)
|
||||
|
||||
context.strokePath()
|
||||
case let .counter(number):
|
||||
let string = NSAttributedString(string: "\(number)", font: Font.with(size: 16.0, design: .round, weight: .semibold), textColor: parameters.theme.strokeColor)
|
||||
let stringSize = string.boundingRect(with: bounds.size, options: .usesLineFragmentOrigin, context: nil).size
|
||||
string.draw(at: CGPoint(x: floorToScreenPixels((bounds.width - stringSize.width) / 2.0), y: floorToScreenPixels((bounds.height - stringSize.height) / 2.0)))
|
||||
}
|
||||
CheckLayer.drawContents(
|
||||
context: context,
|
||||
size: bounds.size,
|
||||
parameters: parameters
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,6 +313,28 @@ public class CheckLayer: CALayer {
|
||||
animation.timingFunction = CAMediaTimingFunction(name: selected ? CAMediaTimingFunctionName.easeOut : CAMediaTimingFunctionName.easeIn)
|
||||
animation.duration = selected ? 0.21 : 0.15
|
||||
self.pop_add(animation, forKey: "progress")
|
||||
|
||||
if selected {
|
||||
self.animateScale(from: 1.0, to: 0.9, duration: 0.08, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.animateScale(from: 0.9, to: 1.1, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.animateScale(from: 1.1, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
self.animateScale(from: 1.0, to: 0.9, duration: 0.08, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.animateScale(from: 0.9, to: 1.0, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
self.pop_removeAllAnimations()
|
||||
self.animatingOut = false
|
||||
@ -404,100 +351,125 @@ public class CheckLayer: CALayer {
|
||||
return
|
||||
}
|
||||
self.contents = generateImage(self.bounds.size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
CheckLayer.drawContents(
|
||||
context: context,
|
||||
size: size,
|
||||
parameters: CheckNodeParameters(theme: self.theme, content: self.content, animationProgress: self.animationProgress, selected: self.selected, animatingOut: self.animatingOut)
|
||||
)
|
||||
})?.cgImage
|
||||
}
|
||||
|
||||
fileprivate static func drawContents(context: CGContext, size: CGSize, parameters: CheckNodeParameters) {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let parameters = CheckNodeParameters(theme: self.theme, content: self.content, animationProgress: self.animationProgress, selected: self.selected, animatingOut: self.animatingOut)
|
||||
let center = CGPoint(x: size.width / 2.0, y: size.width / 2.0)
|
||||
|
||||
let center = CGPoint(x: bounds.width / 2.0, y: bounds.width / 2.0)
|
||||
var borderWidth: CGFloat = 1.0 + UIScreenPixel
|
||||
if parameters.theme.hasInset {
|
||||
borderWidth = 1.5
|
||||
}
|
||||
if let customBorderWidth = parameters.theme.borderWidth {
|
||||
borderWidth = customBorderWidth
|
||||
}
|
||||
|
||||
var borderWidth: CGFloat = 1.0 + UIScreenPixel
|
||||
if parameters.theme.hasInset {
|
||||
borderWidth = 1.5
|
||||
}
|
||||
if let customBorderWidth = parameters.theme.borderWidth {
|
||||
borderWidth = customBorderWidth
|
||||
let checkWidth: CGFloat = 1.5
|
||||
|
||||
let inset: CGFloat = parameters.theme.hasInset ? 2.0 - UIScreenPixel : 0.0
|
||||
|
||||
let checkProgress: CGFloat
|
||||
|
||||
context.setStrokeColor(parameters.theme.borderColor.cgColor)
|
||||
context.setLineWidth(borderWidth)
|
||||
|
||||
let maybeScaleOut = {
|
||||
if parameters.animatingOut {
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: parameters.animationProgress, y: parameters.animationProgress)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
context.setAlpha(parameters.animationProgress)
|
||||
}
|
||||
}
|
||||
|
||||
let checkWidth: CGFloat = 1.5
|
||||
|
||||
let inset: CGFloat = parameters.theme.hasInset ? 2.0 - UIScreenPixel : 0.0
|
||||
|
||||
let checkProgress = parameters.animatingOut ? 1.0 : parameters.animationProgress
|
||||
if !parameters.theme.filledBorder {
|
||||
checkProgress = parameters.animationProgress
|
||||
|
||||
let fillProgress: CGFloat = parameters.animationProgress
|
||||
|
||||
context.setFillColor(parameters.theme.backgroundColor.mixedWith(parameters.theme.borderColor, alpha: 1.0 - fillProgress).cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let innerDiameter: CGFloat = (fillProgress * 0.0) + (1.0 - fillProgress) * (size.width - borderWidth * 2.0)
|
||||
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: (size.width - innerDiameter) * 0.5, y: (size.height - innerDiameter) * 0.5), size: CGSize(width: innerDiameter, height: innerDiameter)))
|
||||
context.setBlendMode(.normal)
|
||||
} else {
|
||||
checkProgress = parameters.animatingOut ? 1.0 : parameters.animationProgress
|
||||
|
||||
let fillProgress = parameters.animatingOut ? 1.0 : min(1.0, parameters.animationProgress * 1.35)
|
||||
|
||||
context.setStrokeColor(parameters.theme.borderColor.cgColor)
|
||||
context.setLineWidth(borderWidth)
|
||||
|
||||
let maybeScaleOut = {
|
||||
if parameters.animatingOut {
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: parameters.animationProgress, y: parameters.animationProgress)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
context.setAlpha(parameters.animationProgress)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let borderInset = borderWidth / 2.0 + inset
|
||||
let borderProgress: CGFloat = parameters.theme.filledBorder ? fillProgress : 1.0
|
||||
let borderFrame = bounds.insetBy(dx: borderInset, dy: borderInset)
|
||||
|
||||
let borderFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: borderInset, dy: borderInset)
|
||||
|
||||
if parameters.theme.filledBorder {
|
||||
maybeScaleOut()
|
||||
}
|
||||
|
||||
|
||||
context.saveGState()
|
||||
if parameters.theme.hasShadow {
|
||||
context.setShadow(offset: CGSize(), blur: 2.5, color: UIColor(rgb: 0x000000, alpha: 0.22).cgColor)
|
||||
}
|
||||
|
||||
|
||||
context.strokeEllipse(in: borderFrame.insetBy(dx: borderFrame.width * (1.0 - borderProgress), dy: borderFrame.height * (1.0 - borderProgress)))
|
||||
context.restoreGState()
|
||||
|
||||
|
||||
if !parameters.theme.filledBorder {
|
||||
maybeScaleOut()
|
||||
}
|
||||
|
||||
|
||||
context.setFillColor(parameters.theme.backgroundColor.cgColor)
|
||||
|
||||
|
||||
let fillInset = parameters.theme.overlayBorder ? borderWidth + inset : inset
|
||||
let fillFrame = bounds.insetBy(dx: fillInset, dy: fillInset)
|
||||
let fillFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: fillInset, dy: fillInset)
|
||||
context.fillEllipse(in: fillFrame.insetBy(dx: fillFrame.width * (1.0 - fillProgress), dy: fillFrame.height * (1.0 - fillProgress)))
|
||||
}
|
||||
|
||||
switch parameters.content {
|
||||
case .check:
|
||||
let scale = (bounds.width - inset) / 18.0
|
||||
let firstSegment: CGFloat = max(0.0, min(1.0, checkProgress * 3.0))
|
||||
let s = CGPoint(x: center.x - (4.0 - 0.3333) * scale, y: center.y + 0.5 * scale)
|
||||
let p1 = CGPoint(x: 2.5 * scale, y: 3.0 * scale)
|
||||
let p2 = CGPoint(x: 4.6667 * scale, y: -6.0 * scale)
|
||||
switch parameters.content {
|
||||
case .check:
|
||||
let scale = (size.width - inset) / 18.0
|
||||
let firstSegment: CGFloat = max(0.0, min(1.0, checkProgress * 3.0))
|
||||
let s = CGPoint(x: center.x - (4.0 - 0.3333) * scale, y: center.y + 0.5 * scale)
|
||||
let p1 = CGPoint(x: 2.5 * scale, y: 3.0 * scale)
|
||||
let p2 = CGPoint(x: 4.6667 * scale, y: -6.0 * scale)
|
||||
|
||||
if !firstSegment.isZero {
|
||||
if firstSegment < 1.0 {
|
||||
context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment))
|
||||
context.addLine(to: s)
|
||||
} else {
|
||||
let secondSegment = (checkProgress - 0.33) * 1.5
|
||||
context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment))
|
||||
context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y))
|
||||
context.addLine(to: s)
|
||||
}
|
||||
if !firstSegment.isZero {
|
||||
if firstSegment < 1.0 {
|
||||
context.move(to: CGPoint(x: s.x + p1.x * firstSegment, y: s.y + p1.y * firstSegment))
|
||||
context.addLine(to: s)
|
||||
} else {
|
||||
let secondSegment = (checkProgress - 0.33) * 1.5
|
||||
context.move(to: CGPoint(x: s.x + p1.x + p2.x * secondSegment, y: s.y + p1.y + p2.y * secondSegment))
|
||||
context.addLine(to: CGPoint(x: s.x + p1.x, y: s.y + p1.y))
|
||||
context.addLine(to: s)
|
||||
}
|
||||
}
|
||||
|
||||
context.setStrokeColor(parameters.theme.strokeColor.cgColor)
|
||||
if parameters.theme.strokeColor == .clear {
|
||||
context.setBlendMode(.clear)
|
||||
}
|
||||
context.setLineWidth(checkWidth)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
context.setMiterLimit(10.0)
|
||||
context.setStrokeColor(parameters.theme.strokeColor.cgColor)
|
||||
if parameters.theme.strokeColor == .clear {
|
||||
context.setBlendMode(.clear)
|
||||
}
|
||||
context.setLineWidth(checkWidth)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
context.setMiterLimit(10.0)
|
||||
|
||||
context.strokePath()
|
||||
case let .counter(number):
|
||||
let text = NSAttributedString(string: "\(number)", font: Font.with(size: 16.0, design: .round, weight: .regular, traits: []), textColor: parameters.theme.strokeColor)
|
||||
text.draw(at: CGPoint())
|
||||
}
|
||||
})?.cgImage
|
||||
context.strokePath()
|
||||
case let .counter(number):
|
||||
let text = NSAttributedString(string: "\(number)", font: Font.with(size: 16.0, design: .round, weight: .regular, traits: []), textColor: parameters.theme.strokeColor)
|
||||
text.draw(at: CGPoint())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -365,6 +365,10 @@ public extension CALayer {
|
||||
func animateScale(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
||||
}
|
||||
|
||||
func animateSublayerScale(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "sublayerTransform.scale", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
||||
}
|
||||
|
||||
func animateScaleX(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale.x", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion)
|
||||
|
@ -770,8 +770,12 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
string = component.count >= premiumLimit ? strings.Premium_MaxFoldersCountFinalText("\(premiumLimit)").string : strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string
|
||||
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||
badgeGraphPosition = badgePosition
|
||||
if component.count >= premiumLimit {
|
||||
badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit))
|
||||
} else {
|
||||
badgeGraphPosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit))
|
||||
}
|
||||
badgePosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit))
|
||||
|
||||
if !state.isPremium && badgePosition > 0.5 {
|
||||
string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string
|
||||
@ -811,11 +815,11 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
defaultValue = count > limit ? "\(limit)" : ""
|
||||
premiumValue = count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||
if count >= premiumLimit {
|
||||
badgeGraphPosition = max(0.1, CGFloat(limit) / CGFloat(premiumLimit))
|
||||
badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit))
|
||||
} else {
|
||||
badgeGraphPosition = max(0.1, CGFloat(count) / CGFloat(premiumLimit))
|
||||
badgeGraphPosition = max(0.15, CGFloat(count) / CGFloat(premiumLimit))
|
||||
}
|
||||
badgePosition = max(0.1, CGFloat(count) / CGFloat(premiumLimit))
|
||||
badgePosition = max(0.15, CGFloat(count) / CGFloat(premiumLimit))
|
||||
|
||||
if isPremiumDisabled {
|
||||
badgeText = "\(limit)"
|
||||
@ -829,8 +833,12 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
string = component.count >= premiumLimit ? strings.Premium_MaxSharedFolderMembershipFinalText("\(premiumLimit)").string : strings.Premium_MaxSharedFolderMembershipText("\(limit)", "\(premiumLimit)").string
|
||||
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||
badgeGraphPosition = badgePosition
|
||||
if component.count >= premiumLimit {
|
||||
badgeGraphPosition = max(0.15, CGFloat(limit) / CGFloat(premiumLimit))
|
||||
} else {
|
||||
badgeGraphPosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit))
|
||||
}
|
||||
badgePosition = max(0.15, CGFloat(component.count) / CGFloat(premiumLimit))
|
||||
|
||||
if isPremiumDisabled {
|
||||
badgeText = "\(limit)"
|
||||
|
@ -641,10 +641,13 @@ private final class FetchImpl {
|
||||
isComplete = true
|
||||
let resultingSize = fetchRange.lowerBound + actualLength
|
||||
if let currentKnownSize = self.knownSize {
|
||||
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): setting known size to min(\(currentKnownSize), \(resultingSize)) = \(min(currentKnownSize, resultingSize))")
|
||||
self.knownSize = min(currentKnownSize, resultingSize)
|
||||
} else {
|
||||
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): setting known size to \(resultingSize)")
|
||||
self.knownSize = resultingSize
|
||||
}
|
||||
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): reporting resource size \(fetchRange.lowerBound + actualLength)")
|
||||
self.onNext(.resourceSizeUpdated(fetchRange.lowerBound + actualLength))
|
||||
}
|
||||
|
||||
@ -662,15 +665,21 @@ private final class FetchImpl {
|
||||
} else {
|
||||
actualData = Data()
|
||||
}
|
||||
|
||||
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): extracting aligned part \(partRange) (\(fetchRange)): \(actualData.count)")
|
||||
}
|
||||
|
||||
if !actualData.isEmpty {
|
||||
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): emitting data part \(partRange) (aligned as \(fetchRange)): \(actualData.count), isComplete: \(isComplete)")
|
||||
|
||||
self.onNext(.dataPart(
|
||||
resourceOffset: partRange.lowerBound,
|
||||
data: actualData,
|
||||
range: 0 ..< Int64(actualData.count),
|
||||
complete: isComplete
|
||||
))
|
||||
} else {
|
||||
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): not emitting data part \(partRange) (aligned as \(fetchRange))")
|
||||
}
|
||||
case let .cdnRedirect(cdnData):
|
||||
self.state = .fetching(FetchImpl.FetchingState(
|
||||
|
@ -0,0 +1,19 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "AnimatedCounterComponent",
|
||||
module_name = "AnimatedCounterComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,279 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
||||
final class AnimatedCounterItemComponent: Component {
|
||||
public let font: UIFont
|
||||
public let color: UIColor
|
||||
public let text: String
|
||||
public let numericValue: Int
|
||||
public let alignment: CGFloat
|
||||
|
||||
public init(
|
||||
font: UIFont,
|
||||
color: UIColor,
|
||||
text: String,
|
||||
numericValue: Int,
|
||||
alignment: CGFloat
|
||||
) {
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.text = text
|
||||
self.numericValue = numericValue
|
||||
self.alignment = alignment
|
||||
}
|
||||
|
||||
public static func ==(lhs: AnimatedCounterItemComponent, rhs: AnimatedCounterItemComponent) -> Bool {
|
||||
if lhs.font != rhs.font {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.numericValue != rhs.numericValue {
|
||||
return false
|
||||
}
|
||||
if lhs.alignment != rhs.alignment {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let contentView: UIImageView
|
||||
|
||||
private var component: AnimatedCounterItemComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.contentView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.contentView)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: AnimatedCounterItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let previousNumericValue = self.component?.numericValue
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let text = NSAttributedString(string: component.text, font: component.font, textColor: component.color)
|
||||
let textBounds = text.boundingRect(with: availableSize, options: [.usesLineFragmentOrigin], context: nil)
|
||||
let size = CGSize(width: ceil(textBounds.width), height: ceil(textBounds.height))
|
||||
|
||||
let previousContentImage = self.contentView.image
|
||||
let previousContentFrame = self.contentView.frame
|
||||
|
||||
self.contentView.image = generateImage(size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
text.draw(at: textBounds.origin)
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
self.contentView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
if !transition.animation.isImmediate, let previousContentImage, !previousContentFrame.isEmpty, let previousNumericValue, previousNumericValue != component.numericValue {
|
||||
let previousContentView = UIImageView()
|
||||
previousContentView.image = previousContentImage
|
||||
previousContentView.frame = CGRect(origin: CGPoint(x: size.width * component.alignment - previousContentFrame.width * component.alignment, y: previousContentFrame.minY), size: previousContentFrame.size)
|
||||
self.addSubview(previousContentView)
|
||||
|
||||
let offsetY: CGFloat = size.height * 0.6 * (previousNumericValue < component.numericValue ? -1.0 : 1.0)
|
||||
|
||||
let subTransition = Transition(animation: .curve(duration: 0.16, curve: .easeInOut))
|
||||
|
||||
subTransition.animatePosition(view: self.contentView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
|
||||
subTransition.animateAlpha(view: self.contentView, from: 0.0, to: 1.0)
|
||||
|
||||
subTransition.setPosition(view: previousContentView, position: CGPoint(x: previousContentView.layer.position.x, y: previousContentView.layer.position.y - offsetY))
|
||||
subTransition.setAlpha(view: previousContentView, alpha: 0.0, completion: { [weak previousContentView] _ in
|
||||
previousContentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final class AnimatedCounterComponent: Component {
|
||||
public enum Alignment {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
public struct Item: Equatable {
|
||||
public var id: AnyHashable
|
||||
public var text: String
|
||||
public var numericValue: Int
|
||||
|
||||
public init(id: AnyHashable, text: String, numericValue: Int) {
|
||||
self.id = id
|
||||
self.text = text
|
||||
self.numericValue = numericValue
|
||||
}
|
||||
}
|
||||
|
||||
public let font: UIFont
|
||||
public let color: UIColor
|
||||
public let alignment: Alignment
|
||||
public let items: [Item]
|
||||
|
||||
public init(
|
||||
font: UIFont,
|
||||
color: UIColor,
|
||||
alignment: Alignment,
|
||||
items: [Item]
|
||||
) {
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.alignment = alignment
|
||||
self.items = items
|
||||
}
|
||||
|
||||
public static func ==(lhs: AnimatedCounterComponent, rhs: AnimatedCounterComponent) -> Bool {
|
||||
if lhs.font != rhs.font {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
if lhs.alignment != rhs.alignment {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private final class ItemView {
|
||||
let view = ComponentView<Empty>()
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private var itemViews: [AnyHashable: ItemView] = [:]
|
||||
|
||||
private var component: AnimatedCounterComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var measuredSpaceWidth: CGFloat?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: AnimatedCounterComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let spaceWidth: CGFloat
|
||||
if let measuredSpaceWidth = self.measuredSpaceWidth, let previousComponent = self.component, previousComponent.font.pointSize == component.font.pointSize {
|
||||
spaceWidth = measuredSpaceWidth
|
||||
} else {
|
||||
spaceWidth = ceil(NSAttributedString(string: " ", font: component.font, textColor: .black).boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil).width)
|
||||
self.measuredSpaceWidth = spaceWidth
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var size = CGSize()
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
for item in component.items {
|
||||
if size.width != 0.0 {
|
||||
size.width += spaceWidth
|
||||
}
|
||||
|
||||
validIds.append(item.id)
|
||||
|
||||
let itemView: ItemView
|
||||
var itemTransition = transition
|
||||
if let current = self.itemViews[item.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemTransition = .immediate
|
||||
itemView = ItemView()
|
||||
self.itemViews[item.id] = itemView
|
||||
}
|
||||
|
||||
let itemSize = itemView.view.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(AnimatedCounterItemComponent(
|
||||
font: component.font,
|
||||
color: component.color,
|
||||
text: item.text,
|
||||
numericValue: item.numericValue,
|
||||
alignment: component.alignment == .left ? 0.0 : 1.0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
|
||||
if let itemComponentView = itemView.view.view {
|
||||
if itemComponentView.superview == nil {
|
||||
self.addSubview(itemComponentView)
|
||||
}
|
||||
let itemFrame = CGRect(origin: CGPoint(x: size.width, y: 0.0), size: itemSize)
|
||||
switch component.alignment {
|
||||
case .left:
|
||||
itemComponentView.layer.anchorPoint = CGPoint(x: 0.0, y: 0.5)
|
||||
itemTransition.setPosition(view: itemComponentView, position: CGPoint(x: itemFrame.minX, y: itemFrame.midY))
|
||||
case .right:
|
||||
itemComponentView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.5)
|
||||
itemTransition.setPosition(view: itemComponentView, position: CGPoint(x: itemFrame.maxX, y: itemFrame.midY))
|
||||
}
|
||||
itemComponentView.bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||
}
|
||||
|
||||
size.width += itemSize.width
|
||||
size.height = max(size.height, itemSize.height)
|
||||
}
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, itemView) in self.itemViews {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
if let componentView = itemView.view.view {
|
||||
transition.setAlpha(view: componentView, alpha: 0.0, completion: { [weak componentView] _ in
|
||||
componentView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ swift_library(
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SolidRoundedButtonComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/TelegramUI/Components/AnimatedCounterComponent",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/CheckNode",
|
||||
"//submodules/Markdown",
|
||||
|
@ -20,6 +20,8 @@ import ButtonComponent
|
||||
import ContextUI
|
||||
import QrCodeUI
|
||||
import InviteLinksUI
|
||||
import PlainButtonComponent
|
||||
import AnimatedCounterComponent
|
||||
|
||||
private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@ -154,7 +156,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
|
||||
self.addSubview(self.navigationBarContainer)
|
||||
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.delaysContentTouches = false
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
@ -800,15 +802,21 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
listHeaderTitle = " "
|
||||
}
|
||||
|
||||
let listHeaderActionTitle: String
|
||||
//TODO:localize
|
||||
let listHeaderActionItems: [AnimatedCounterComponent.Item]
|
||||
if self.selectedItems.count == self.items.count {
|
||||
listHeaderActionTitle = "DESELECT ALL"
|
||||
listHeaderActionItems = [
|
||||
AnimatedCounterComponent.Item(id: AnyHashable(0), text: "DESELECT", numericValue: 0),
|
||||
AnimatedCounterComponent.Item(id: AnyHashable(1), text: "ALL", numericValue: 1)
|
||||
]
|
||||
} else {
|
||||
listHeaderActionTitle = "SELECT ALL"
|
||||
listHeaderActionItems = [
|
||||
AnimatedCounterComponent.Item(id: AnyHashable(0), text: "SELECT", numericValue: 1),
|
||||
AnimatedCounterComponent.Item(id: AnyHashable(1), text: "ALL", numericValue: 1)
|
||||
]
|
||||
}
|
||||
|
||||
let listHeaderBody = MarkdownAttributeSet(font: Font.with(size: 13.0, design: .regular, traits: [.monospacedNumbers]), textColor: environment.theme.list.freeTextColor)
|
||||
let listHeaderActionBody = MarkdownAttributeSet(font: Font.with(size: 13.0, design: .regular, traits: [.monospacedNumbers]), textColor: environment.theme.list.itemAccentColor)
|
||||
|
||||
let listHeaderTextSize = self.listHeaderText.update(
|
||||
transition: .immediate,
|
||||
@ -838,19 +846,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
}
|
||||
|
||||
let listHeaderActionSize = self.listHeaderAction.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(MultilineTextComponent(
|
||||
text: .markdown(
|
||||
text: listHeaderActionTitle,
|
||||
attributes: MarkdownAttributes(
|
||||
body: listHeaderActionBody,
|
||||
bold: listHeaderActionBody,
|
||||
link: listHeaderActionBody,
|
||||
linkAttribute: { _ in nil }
|
||||
)
|
||||
)
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(AnimatedCounterComponent(
|
||||
font: Font.regular(13.0),
|
||||
color: environment.theme.list.itemAccentColor,
|
||||
alignment: .right,
|
||||
items: listHeaderActionItems
|
||||
)),
|
||||
effectAlignment: .right,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component, let linkContents = component.linkContents else {
|
||||
return
|
||||
@ -877,8 +881,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
self.scrollContentView.addSubview(listHeaderActionView)
|
||||
}
|
||||
let listHeaderActionFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - 15.0 - listHeaderActionSize.width, y: contentHeight), size: listHeaderActionSize)
|
||||
contentTransition.setPosition(view: listHeaderActionView, position: CGPoint(x: listHeaderActionFrame.maxX, y: listHeaderActionFrame.minY))
|
||||
listHeaderActionView.bounds = CGRect(origin: CGPoint(), size: listHeaderActionFrame.size)
|
||||
contentTransition.setFrame(view: listHeaderActionView, frame: listHeaderActionFrame)
|
||||
|
||||
if let linkContents = component.linkContents, !allChatsAdded, linkContents.peers.count > 1 {
|
||||
listHeaderActionView.isHidden = false
|
||||
|
19
submodules/TelegramUI/Components/PlainButtonComponent/BUILD
Normal file
19
submodules/TelegramUI/Components/PlainButtonComponent/BUILD
Normal file
@ -0,0 +1,19 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PlainButtonComponent",
|
||||
module_name = "PlainButtonComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,154 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
||||
public final class PlainButtonComponent: Component {
|
||||
public enum EffectAlignment {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
public let content: AnyComponent<Empty>
|
||||
public let effectAlignment: EffectAlignment
|
||||
public let action: () -> Void
|
||||
|
||||
public init(
|
||||
content: AnyComponent<Empty>,
|
||||
effectAlignment: EffectAlignment,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.content = content
|
||||
self.effectAlignment = effectAlignment
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public static func ==(lhs: PlainButtonComponent, rhs: PlainButtonComponent) -> Bool {
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
if lhs.effectAlignment != rhs.effectAlignment {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: HighlightTrackingButton {
|
||||
private var component: PlainButtonComponent?
|
||||
private weak var componentState: EmptyComponentState?
|
||||
|
||||
private let contentContainer = UIView()
|
||||
private let content = ComponentView<Empty>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.contentContainer.isUserInteractionEnabled = false
|
||||
self.addSubview(self.contentContainer)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let self, self.bounds.width > 0.0 {
|
||||
let topScale: CGFloat = (self.bounds.width - 8.0) / self.bounds.width
|
||||
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
|
||||
|
||||
if highlighted {
|
||||
self.contentContainer.layer.removeAnimation(forKey: "opacity")
|
||||
self.contentContainer.layer.removeAnimation(forKey: "sublayerTransform")
|
||||
self.contentContainer.alpha = 0.7
|
||||
let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||
transition.setScale(layer: self.contentContainer.layer, scale: topScale)
|
||||
} else {
|
||||
self.contentContainer.alpha = 1.0
|
||||
self.contentContainer.layer.animateAlpha(from: 7, to: 1.0, duration: 0.2)
|
||||
|
||||
let transition = Transition(animation: .none)
|
||||
transition.setScale(layer: self.contentContainer.layer, scale: 1.0)
|
||||
|
||||
self.contentContainer.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.contentContainer.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action()
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let result = super.hitTest(point, with: event)
|
||||
if result != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
if !self.isEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.bounds.insetBy(dx: -8.0, dy: -8.0).contains(point) {
|
||||
return self
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func update(component: PlainButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
self.isEnabled = true
|
||||
|
||||
let contentAlpha: CGFloat = 1.0
|
||||
|
||||
let contentSize = self.content.update(
|
||||
transition: transition,
|
||||
component: component.content,
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let size = contentSize
|
||||
|
||||
if let contentView = self.content.view {
|
||||
var contentTransition = transition
|
||||
if contentView.superview == nil {
|
||||
contentTransition = .immediate
|
||||
contentView.isUserInteractionEnabled = false
|
||||
self.contentContainer.addSubview(contentView)
|
||||
}
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) * 0.5), y: floor((size.height - contentSize.height) * 0.5)), size: contentSize)
|
||||
|
||||
contentTransition.setFrame(view: contentView, frame: contentFrame)
|
||||
contentTransition.setAlpha(view: contentView, alpha: contentAlpha)
|
||||
}
|
||||
|
||||
self.contentContainer.layer.anchorPoint = CGPoint(x: component.effectAlignment == .left ? 0.0 : 1.0, y: 0.5)
|
||||
transition.setBounds(view: self.contentContainer, bounds: CGRect(origin: CGPoint(), size: size))
|
||||
transition.setPosition(view: self.contentContainer, position: CGPoint(x: component.effectAlignment == .left ? 0.0 : size.width, y: size.height * 0.5))
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user