mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Temp
This commit is contained in:
parent
f936636f6a
commit
ed5e3b3c5a
@ -2891,6 +2891,7 @@ Unused sets are archived when you add more.";
|
||||
"FastTwoStepSetup.EmailHelp" = "Please add your valid e-mail. It is the only way to recover a forgotten password.";
|
||||
|
||||
"Conversation.ViewMessage" = "VIEW MESSAGE";
|
||||
"Conversation.ViewPost" = "VIEW POST";
|
||||
|
||||
"GroupInfo.GroupHistory" = "History For New Members";
|
||||
"GroupInfo.GroupHistoryVisible" = "Visible";
|
||||
|
@ -12,14 +12,28 @@ import PhotoResources
|
||||
import DirectMediaImageCache
|
||||
import TelegramStringFormatting
|
||||
|
||||
private final class MediaPreviewView: UIView {
|
||||
private final class NullActionClass: NSObject, CAAction {
|
||||
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
||||
}
|
||||
}
|
||||
|
||||
private let nullAction = NullActionClass()
|
||||
|
||||
private class SimpleLayer: CALayer {
|
||||
override func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
}
|
||||
}
|
||||
|
||||
private final class MediaPreviewView: SimpleLayer {
|
||||
private let context: AccountContext
|
||||
private let message: EngineMessage
|
||||
private let media: EngineMedia
|
||||
private let imageCache: DirectMediaImageCache
|
||||
|
||||
private let imageView: UIImageView
|
||||
|
||||
private var requestedImage: Bool = false
|
||||
private var disposable: Disposable?
|
||||
|
||||
@ -29,12 +43,9 @@ private final class MediaPreviewView: UIView {
|
||||
self.media = media
|
||||
self.imageCache = imageCache
|
||||
|
||||
self.imageView = UIImageView()
|
||||
self.imageView.contentMode = .scaleToFill
|
||||
super.init()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.imageView)
|
||||
self.contentsGravity = .resize
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -62,7 +73,7 @@ private final class MediaPreviewView: UIView {
|
||||
self.requestedImage = true
|
||||
if let result = self.imageCache.getImage(message: self.message._asMessage(), media: self.media._asMedia(), width: 100, possibleWidths: [100], synchronous: false) {
|
||||
if let image = result.image {
|
||||
self.imageView.image = processImage(image)
|
||||
self.contents = processImage(image).cgImage
|
||||
}
|
||||
if let signal = result.loadSignal {
|
||||
self.disposable = (signal
|
||||
@ -74,49 +85,22 @@ private final class MediaPreviewView: UIView {
|
||||
return
|
||||
}
|
||||
if let image = image {
|
||||
if strongSelf.imageView.image != nil {
|
||||
let tempView = UIImageView()
|
||||
tempView.image = strongSelf.imageView.image
|
||||
tempView.frame = strongSelf.imageView.frame
|
||||
tempView.contentMode = strongSelf.imageView.contentMode
|
||||
strongSelf.addSubview(tempView)
|
||||
tempView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempView] _ in
|
||||
tempView?.removeFromSuperview()
|
||||
if strongSelf.contents != nil {
|
||||
let tempView = SimpleLayer()
|
||||
tempView.contents = strongSelf.contents
|
||||
tempView.frame = strongSelf.bounds
|
||||
tempView.contentsGravity = strongSelf.contentsGravity
|
||||
strongSelf.addSublayer(tempView)
|
||||
tempView.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempView] _ in
|
||||
tempView?.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
strongSelf.imageView.image = image
|
||||
strongSelf.contents = image.cgImage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.imageView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
/*var dimensions = CGSize(width: 100.0, height: 100.0)
|
||||
if case let .image(image) = self.media {
|
||||
if let largest = largestImageRepresentation(image.representations) {
|
||||
dimensions = largest.dimensions.cgSize
|
||||
if !self.requestedImage {
|
||||
self.requestedImage = true
|
||||
let signal = mediaGridMessagePhoto(account: self.context.account, photoReference: .message(message: MessageReference(self.message._asMessage()), media: image), fullRepresentationSize: CGSize(width: 36.0, height: 36.0), synchronousLoad: synchronousLoads)
|
||||
self.imageView.setSignal(signal, attemptSynchronously: synchronousLoads)
|
||||
}
|
||||
}
|
||||
} else if case let .file(file) = self.media {
|
||||
if let mediaDimensions = file.dimensions {
|
||||
dimensions = mediaDimensions.cgSize
|
||||
if !self.requestedImage {
|
||||
self.requestedImage = true
|
||||
let signal = mediaGridMessageVideo(postbox: self.context.account.postbox, videoReference: .message(message: MessageReference(self.message._asMessage()), media: file), synchronousLoad: synchronousLoads, autoFetchFullSizeThumbnail: true, useMiniThumbnailIfAvailable: true)
|
||||
self.imageView.setSignal(signal, attemptSynchronously: synchronousLoads)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let makeLayout = self.imageView.asyncLayout()
|
||||
self.imageView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
||||
apply()*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,12 +409,10 @@ private final class DayComponent: Component {
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let button: HighlightTrackingButton
|
||||
|
||||
private let highlightView: UIImageView
|
||||
private var selectionView: UIImageView?
|
||||
private let titleView: UIImageView
|
||||
final class View: HighlightTrackingButton {
|
||||
private let highlightView: SimpleLayer
|
||||
private var selectionView: SimpleLayer?
|
||||
private let titleView: SimpleLayer
|
||||
private var mediaPreviewView: MediaPreviewView?
|
||||
|
||||
private var action: (() -> Void)?
|
||||
@ -441,29 +423,24 @@ private final class DayComponent: Component {
|
||||
private var isHighlightingEnabled: Bool = false
|
||||
|
||||
init() {
|
||||
self.button = HighlightTrackingButton()
|
||||
self.highlightView = UIImageView()
|
||||
self.highlightView.isUserInteractionEnabled = false
|
||||
self.titleView = UIImageView()
|
||||
self.titleView.isUserInteractionEnabled = false
|
||||
self.highlightView = SimpleLayer()
|
||||
self.titleView = SimpleLayer()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.button.addSubview(self.highlightView)
|
||||
self.button.addSubview(self.titleView)
|
||||
self.layer.addSublayer(self.highlightView)
|
||||
self.layer.addSublayer(self.titleView)
|
||||
|
||||
self.addSubview(self.button)
|
||||
|
||||
self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
self.button.highligthedChanged = { [weak self] highligthed in
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
self.highligthedChanged = { [weak self] highligthed in
|
||||
guard let strongSelf = self, let mediaPreviewView = strongSelf.mediaPreviewView else {
|
||||
return
|
||||
}
|
||||
if strongSelf.isHighlightingEnabled && highligthed {
|
||||
mediaPreviewView.alpha = 0.8
|
||||
mediaPreviewView.opacity = 0.8
|
||||
} else {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
transition.updateAlpha(layer: mediaPreviewView.layer, alpha: 1.0)
|
||||
transition.updateAlpha(layer: mediaPreviewView, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -489,9 +466,9 @@ private final class DayComponent: Component {
|
||||
|
||||
let dayEnvironment = environment[DayEnvironment.self].value
|
||||
if component.media != nil {
|
||||
self.highlightView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2))
|
||||
self.highlightView.contents = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2)).cgImage
|
||||
} else {
|
||||
self.highlightView.image = nil
|
||||
self.highlightView.contents = nil
|
||||
}
|
||||
|
||||
var animateMediaIn = false
|
||||
@ -500,16 +477,15 @@ private final class DayComponent: Component {
|
||||
|
||||
if let mediaPreviewView = self.mediaPreviewView {
|
||||
self.mediaPreviewView = nil
|
||||
mediaPreviewView.removeFromSuperview()
|
||||
mediaPreviewView.removeFromSuperlayer()
|
||||
} else {
|
||||
animateMediaIn = !isFirstTime
|
||||
}
|
||||
|
||||
if let media = component.media {
|
||||
let mediaPreviewView = MediaPreviewView(context: component.context, message: media.message, media: media.media, imageCache: dayEnvironment.directImageCache)
|
||||
mediaPreviewView.isUserInteractionEnabled = false
|
||||
self.mediaPreviewView = mediaPreviewView
|
||||
self.button.insertSubview(mediaPreviewView, belowSubview: self.highlightView)
|
||||
self.layer.insertSublayer(mediaPreviewView, below: self.highlightView)
|
||||
}
|
||||
}
|
||||
|
||||
@ -552,24 +528,24 @@ private final class DayComponent: Component {
|
||||
|
||||
switch component.selection {
|
||||
case .edge:
|
||||
let selectionView: UIImageView
|
||||
let selectionView: SimpleLayer
|
||||
if let current = self.selectionView {
|
||||
selectionView = current
|
||||
} else {
|
||||
selectionView = UIImageView()
|
||||
selectionView = SimpleLayer()
|
||||
self.selectionView = selectionView
|
||||
self.button.insertSubview(selectionView, belowSubview: self.titleView)
|
||||
self.layer.insertSublayer(selectionView, below: self.titleView)
|
||||
}
|
||||
selectionView.frame = contentFrame
|
||||
if self.mediaPreviewView != nil {
|
||||
selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor)
|
||||
selectionView.contents = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor).cgImage
|
||||
} else {
|
||||
selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor)
|
||||
selectionView.contents = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor).cgImage
|
||||
}
|
||||
case .middle, .none:
|
||||
if let selectionView = self.selectionView {
|
||||
self.selectionView = nil
|
||||
selectionView.removeFromSuperview()
|
||||
selectionView.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
@ -583,32 +559,31 @@ private final class DayComponent: Component {
|
||||
|
||||
let titleImage = dayEnvironment.imageCache.text(fontSize: titleFontSize, isSemibold: titleFontIsSemibold, color: titleColor, string: component.title)
|
||||
if animateMediaIn {
|
||||
let previousTitleView = UIImageView(image: self.titleView.image)
|
||||
let previousTitleView = SimpleLayer()
|
||||
previousTitleView.contents = self.titleView.contents
|
||||
previousTitleView.frame = self.titleView.frame
|
||||
self.titleView.superview?.insertSubview(previousTitleView, aboveSubview: self.titleView)
|
||||
previousTitleView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousTitleView] _ in
|
||||
previousTitleView?.removeFromSuperview()
|
||||
self.titleView.superlayer?.insertSublayer(previousTitleView, above: self.titleView)
|
||||
previousTitleView.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousTitleView] _ in
|
||||
previousTitleView?.removeFromSuperlayer()
|
||||
})
|
||||
self.titleView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.16)
|
||||
self.titleView.animateAlpha(from: 0.0, to: 1.0, duration: 0.16)
|
||||
}
|
||||
self.titleView.image = titleImage
|
||||
self.titleView.contents = titleImage.cgImage
|
||||
let titleSize = titleImage.size
|
||||
|
||||
transition.setFrame(view: self.highlightView, frame: CGRect(origin: CGPoint(x: contentFrame.midX - contentFrame.width * contentScale / 2.0, y: contentFrame.midY - contentFrame.width * contentScale / 2.0), size: CGSize(width: contentFrame.width * contentScale, height: contentFrame.height * contentScale)))
|
||||
self.highlightView.frame = CGRect(origin: CGPoint(x: contentFrame.midX - contentFrame.width * contentScale / 2.0, y: contentFrame.midY - contentFrame.width * contentScale / 2.0), size: CGSize(width: contentFrame.width * contentScale, height: contentFrame.height * contentScale))
|
||||
|
||||
self.titleView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)
|
||||
|
||||
self.button.frame = CGRect(origin: CGPoint(), size: availableSize)
|
||||
|
||||
if let mediaPreviewView = self.mediaPreviewView {
|
||||
mediaPreviewView.frame = contentFrame
|
||||
mediaPreviewView.updateLayout(size: contentFrame.size, synchronousLoads: false)
|
||||
|
||||
mediaPreviewView.layer.sublayerTransform = CATransform3DMakeScale(contentScale, contentScale, 1.0)
|
||||
mediaPreviewView.sublayerTransform = CATransform3DMakeScale(contentScale, contentScale, 1.0)
|
||||
|
||||
if animateMediaIn {
|
||||
mediaPreviewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.highlightView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
mediaPreviewView.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.highlightView.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
@ -625,6 +600,219 @@ private final class DayComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ManualMonthComponent: Component {
|
||||
typealias EnvironmentType = DayEnvironment
|
||||
|
||||
let context: AccountContext
|
||||
let model: MonthModel
|
||||
let foregroundColor: UIColor
|
||||
let strings: PresentationStrings
|
||||
let theme: PresentationTheme
|
||||
let dayAction: (Int32) -> Void
|
||||
let selectedDays: ClosedRange<Int32>?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
model: MonthModel,
|
||||
foregroundColor: UIColor,
|
||||
strings: PresentationStrings,
|
||||
theme: PresentationTheme,
|
||||
dayAction: @escaping (Int32) -> Void,
|
||||
selectedDays: ClosedRange<Int32>?
|
||||
) {
|
||||
self.context = context
|
||||
self.model = model
|
||||
self.foregroundColor = foregroundColor
|
||||
self.strings = strings
|
||||
self.theme = theme
|
||||
self.dayAction = dayAction
|
||||
self.selectedDays = selectedDays
|
||||
}
|
||||
|
||||
static func ==(lhs: ManualMonthComponent, rhs: ManualMonthComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.model != rhs.model {
|
||||
return false
|
||||
}
|
||||
if lhs.foregroundColor != rhs.foregroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedDays != rhs.selectedDays {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let title: Text.View
|
||||
private var weekdayTitles: [UIImageView] = []
|
||||
private var days: [Int: DayComponent.View] = [:]
|
||||
|
||||
init() {
|
||||
self.title = Text.View()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.title)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: ManualMonthComponent, availableSize: CGSize, environment: Environment<DayEnvironment>, transition: Transition) -> CGSize {
|
||||
let sideInset: CGFloat = 14.0
|
||||
let titleWeekdaysSpacing: CGFloat = 18.0
|
||||
let weekdayDaySpacing: CGFloat = 14.0
|
||||
let weekdaySize: CGFloat = 46.0
|
||||
let weekdaySpacing: CGFloat = 6.0
|
||||
|
||||
let dayEnvironment = environment[DayEnvironment.self].value
|
||||
|
||||
let usableWeekdayWidth = floor((availableSize.width - sideInset * 2.0 - weekdaySpacing * 6.0) / 7.0)
|
||||
let weekdayWidth = floor((availableSize.width - sideInset * 2.0) / 7.0)
|
||||
|
||||
let monthName = stringForMonth(strings: component.strings, month: Int32(component.model.index - 1), ofYear: Int32(component.model.year - 1900))
|
||||
|
||||
let titleSize = self.title.update(
|
||||
component: Text(
|
||||
text: monthName,
|
||||
font: Font.semibold(17.0),
|
||||
color: component.foregroundColor
|
||||
),
|
||||
availableSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: 0.0), size: titleSize)
|
||||
self.title.frame = titleFrame
|
||||
|
||||
for i in 0 ..< 7 {
|
||||
let weekdayTitle: UIImageView
|
||||
if self.weekdayTitles.count > i {
|
||||
weekdayTitle = self.weekdayTitles[i]
|
||||
} else {
|
||||
weekdayTitle = UIImageView()
|
||||
self.addSubview(weekdayTitle)
|
||||
self.weekdayTitles.append(weekdayTitle)
|
||||
}
|
||||
let image = dayEnvironment.imageCache.text(fontSize: 10.0, isSemibold: false, color: component.foregroundColor, string: gridDayName(index: i, firstDayOfWeek: component.model.firstWeekday, strings: component.strings))
|
||||
weekdayTitle.image = image
|
||||
}
|
||||
|
||||
let baseWeekdayTitleY = titleFrame.maxY + titleWeekdaysSpacing
|
||||
var maxWeekdayY = baseWeekdayTitleY
|
||||
|
||||
for i in 0 ..< self.weekdayTitles.count {
|
||||
guard let image = self.weekdayTitles[i].image else {
|
||||
continue
|
||||
}
|
||||
let weekdaySize = image.size
|
||||
let weekdayFrame = CGRect(origin: CGPoint(x: sideInset + CGFloat(i) * weekdayWidth + floor((weekdayWidth - weekdaySize.width) / 2.0), y: baseWeekdayTitleY), size: weekdaySize)
|
||||
maxWeekdayY = max(maxWeekdayY, weekdayFrame.maxY)
|
||||
self.weekdayTitles[i].frame = weekdayFrame
|
||||
}
|
||||
|
||||
var daySizes: [Int: CGSize] = [:]
|
||||
for index in 0 ..< component.model.numberOfDays {
|
||||
let dayOfMonth = index + 1
|
||||
let isCurrent = component.model.currentYear == component.model.year && component.model.currentMonth == component.model.index && component.model.currentDayOfMonth == dayOfMonth
|
||||
var isEnabled = true
|
||||
if component.model.currentYear == component.model.year {
|
||||
if component.model.currentMonth == component.model.index {
|
||||
if dayOfMonth > component.model.currentDayOfMonth {
|
||||
isEnabled = false
|
||||
}
|
||||
} else if component.model.index > component.model.currentMonth {
|
||||
isEnabled = false
|
||||
}
|
||||
} else if component.model.year > component.model.currentYear {
|
||||
isEnabled = false
|
||||
}
|
||||
|
||||
let dayTimestamp = Int32(component.model.firstDay.timeIntervalSince1970) + 24 * 60 * 60 * Int32(index)
|
||||
let dayAction = component.dayAction
|
||||
|
||||
let daySelection: DayComponent.DaySelection
|
||||
if let selectedDays = component.selectedDays, selectedDays.contains(dayTimestamp) {
|
||||
if selectedDays.lowerBound == dayTimestamp || selectedDays.upperBound == dayTimestamp {
|
||||
daySelection = .edge
|
||||
} else {
|
||||
daySelection = .middle
|
||||
}
|
||||
} else {
|
||||
daySelection = .none
|
||||
}
|
||||
|
||||
let day: DayComponent.View
|
||||
if let current = self.days[index] {
|
||||
day = current
|
||||
} else {
|
||||
day = DayComponent.View()
|
||||
self.addSubview(day)
|
||||
self.days[index] = day
|
||||
}
|
||||
|
||||
let daySize = day.update(
|
||||
component: DayComponent(
|
||||
title: "\(dayOfMonth)",
|
||||
isCurrent: isCurrent,
|
||||
isEnabled: isEnabled,
|
||||
theme: component.theme,
|
||||
context: component.context,
|
||||
timestamp: dayTimestamp,
|
||||
media: component.model.mediaByDay[index],
|
||||
selection: daySelection,
|
||||
isSelecting: component.selectedDays != nil,
|
||||
action: {
|
||||
dayAction(dayTimestamp)
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: usableWeekdayWidth, height: weekdaySize),
|
||||
environment: environment,
|
||||
transition: .immediate
|
||||
)
|
||||
daySizes[index] = daySize
|
||||
}
|
||||
|
||||
let baseDayY = maxWeekdayY + weekdayDaySpacing
|
||||
var maxDayY = baseDayY
|
||||
|
||||
for i in 0 ..< component.model.numberOfDays {
|
||||
guard let dayView = self.days[i], let dayItemSize = daySizes[i] else {
|
||||
continue
|
||||
}
|
||||
let gridIndex = gridDayOffset(firstDayOfWeek: component.model.firstWeekday, firstWeekdayOfMonth: component.model.firstDayWeekday) + i
|
||||
let rowIndex = gridIndex % 7
|
||||
let lineIndex = gridIndex / 7
|
||||
|
||||
let gridX = sideInset + CGFloat(rowIndex) * weekdayWidth
|
||||
let gridY = baseDayY + CGFloat(lineIndex) * (weekdaySize + weekdaySpacing)
|
||||
let dayFrame = CGRect(origin: CGPoint(x: gridX + floor((weekdayWidth - dayItemSize.width) / 2.0), y: gridY + floor((weekdaySize - dayItemSize.height) / 2.0)), size: dayItemSize)
|
||||
maxDayY = max(maxDayY, gridY + weekdaySize)
|
||||
dayView.frame = dayFrame
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: maxDayY)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, environment: Environment<DayEnvironment>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class MonthComponent: CombinedComponent {
|
||||
typealias EnvironmentType = DayEnvironment
|
||||
|
||||
@ -1054,7 +1242,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let dayView = result.superview as? DayComponent.View else {
|
||||
guard let dayView = result as? DayComponent.View else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ public final class Text: Component {
|
||||
public final class View: UIView {
|
||||
private var measureState: MeasureState?
|
||||
|
||||
func update(component: Text, availableSize: CGSize) -> CGSize {
|
||||
public func update(component: Text, availableSize: CGSize) -> CGSize {
|
||||
let attributedText = NSAttributedString(string: component.text, attributes: [
|
||||
NSAttributedString.Key.font: component.font,
|
||||
NSAttributedString.Key.foregroundColor: component.color
|
||||
|
@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import Accelerate
|
||||
import AsyncDisplayKit
|
||||
import CoreMedia
|
||||
|
||||
public let deviceColorSpace: CGColorSpace = {
|
||||
if #available(iOSApplicationExtension 9.3, iOS 9.3, *) {
|
||||
@ -581,6 +582,30 @@ public class DrawingContext {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func generatePixelBuffer() -> CVPixelBuffer? {
|
||||
if self.scaledSize.width.isZero || self.scaledSize.height.isZero {
|
||||
return nil
|
||||
}
|
||||
if self.hasGeneratedImage {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
let ioSurfaceProperties = NSMutableDictionary()
|
||||
let options = NSMutableDictionary()
|
||||
options.setObject(ioSurfaceProperties, forKey: kCVPixelBufferIOSurfacePropertiesKey as NSString)
|
||||
|
||||
var pixelBuffer: CVPixelBuffer?
|
||||
CVPixelBufferCreateWithBytes(nil, Int(self.scaledSize.width), Int(self.scaledSize.height), kCVPixelFormatType_32BGRA, self.bytes, self.bytesPerRow, { pointer, _ in
|
||||
if let pointer = pointer {
|
||||
Unmanaged<ASCGImageBuffer>.fromOpaque(pointer).release()
|
||||
}
|
||||
}, Unmanaged.passRetained(self.imageBuffer).toOpaque(), options as CFDictionary, &pixelBuffer)
|
||||
|
||||
self.hasGeneratedImage = true
|
||||
|
||||
return pixelBuffer
|
||||
}
|
||||
|
||||
public func colorAt(_ point: CGPoint) -> UIColor {
|
||||
let x = Int(point.x * self.scale)
|
||||
@ -649,6 +674,76 @@ public class DrawingContext {
|
||||
}
|
||||
}
|
||||
|
||||
public extension UIImage {
|
||||
var cvPixelBuffer: CVPixelBuffer? {
|
||||
guard let cgImage = self.cgImage else {
|
||||
return nil
|
||||
}
|
||||
let _ = cgImage
|
||||
|
||||
var maybePixelBuffer: CVPixelBuffer? = nil
|
||||
let ioSurfaceProperties = NSMutableDictionary()
|
||||
let options = NSMutableDictionary()
|
||||
options.setObject(ioSurfaceProperties, forKey: kCVPixelBufferIOSurfacePropertiesKey as NSString)
|
||||
|
||||
let _ = CVPixelBufferCreate(kCFAllocatorDefault, Int(size.width * self.scale), Int(size.height * self.scale), kCVPixelFormatType_32ARGB, options as CFDictionary, &maybePixelBuffer)
|
||||
guard let pixelBuffer = maybePixelBuffer else {
|
||||
return nil
|
||||
}
|
||||
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
|
||||
defer {
|
||||
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
|
||||
}
|
||||
|
||||
let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)
|
||||
|
||||
let context = CGContext(
|
||||
data: baseAddress,
|
||||
width: Int(self.size.width * self.scale),
|
||||
height: Int(self.size.height * self.scale),
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),
|
||||
space: CGColorSpaceCreateDeviceRGB(),
|
||||
bitmapInfo: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue,
|
||||
releaseCallback: nil,
|
||||
releaseInfo: nil
|
||||
)!
|
||||
context.clear(CGRect(origin: .zero, size: CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale)))
|
||||
context.draw(cgImage, in: CGRect(origin: .zero, size: CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale)))
|
||||
|
||||
return pixelBuffer
|
||||
}
|
||||
|
||||
var cmSampleBuffer: CMSampleBuffer? {
|
||||
guard let pixelBuffer = self.cvPixelBuffer else {
|
||||
return nil
|
||||
}
|
||||
var newSampleBuffer: CMSampleBuffer? = nil
|
||||
|
||||
var timingInfo = CMSampleTimingInfo(
|
||||
duration: CMTimeMake(value: 1, timescale: 30),
|
||||
presentationTimeStamp: CMTimeMake(value: 0, timescale: 30),
|
||||
decodeTimeStamp: CMTimeMake(value: 0, timescale: 30)
|
||||
)
|
||||
|
||||
var videoInfo: CMVideoFormatDescription? = nil
|
||||
CMVideoFormatDescriptionCreateForImageBuffer(allocator: nil, imageBuffer: pixelBuffer, formatDescriptionOut: &videoInfo)
|
||||
guard let videoInfo = videoInfo else {
|
||||
return nil
|
||||
}
|
||||
CMSampleBufferCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: videoInfo, sampleTiming: &timingInfo, sampleBufferOut: &newSampleBuffer)
|
||||
|
||||
if let newSampleBuffer = newSampleBuffer {
|
||||
let attachments = CMSampleBufferGetSampleAttachmentsArray(newSampleBuffer, createIfNecessary: true)! as NSArray
|
||||
let dict = attachments[0] as! NSMutableDictionary
|
||||
|
||||
dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DisplayImmediately as NSString as String)
|
||||
}
|
||||
|
||||
return newSampleBuffer
|
||||
}
|
||||
}
|
||||
|
||||
public enum ParsingError: Error {
|
||||
case Generic
|
||||
}
|
||||
|
@ -150,6 +150,41 @@ private func cancelContextGestures(view: UIView) {
|
||||
}
|
||||
|
||||
open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGestureRecognizerDelegate {
|
||||
public struct ScrollingIndicatorState {
|
||||
public struct Item {
|
||||
public var index: Int
|
||||
public var offset: CGFloat
|
||||
public var height: CGFloat
|
||||
|
||||
public init(
|
||||
index: Int,
|
||||
offset: CGFloat,
|
||||
height: CGFloat
|
||||
) {
|
||||
self.index = index
|
||||
self.offset = offset
|
||||
self.height = height
|
||||
}
|
||||
}
|
||||
|
||||
public var insets: UIEdgeInsets
|
||||
public var topItem: Item
|
||||
public var bottomItem: Item
|
||||
public var itemCount: Int
|
||||
|
||||
public init(
|
||||
insets: UIEdgeInsets,
|
||||
topItem: Item,
|
||||
bottomItem: Item,
|
||||
itemCount: Int
|
||||
) {
|
||||
self.insets = insets
|
||||
self.topItem = topItem
|
||||
self.bottomItem = bottomItem
|
||||
self.itemCount = itemCount
|
||||
}
|
||||
}
|
||||
|
||||
public final let scroller: ListViewScroller
|
||||
public private(set) final var visibleSize: CGSize = CGSize()
|
||||
public private(set) final var insets = UIEdgeInsets()
|
||||
@ -214,14 +249,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
public final var snapToBottomInsetUntilFirstInteraction: Bool = false
|
||||
|
||||
public final var updateFloatingHeaderOffset: ((CGFloat, ContainedViewLayoutTransition) -> Void)? {
|
||||
didSet {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public final var updateFloatingHeaderOffset: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
public final var didScrollWithOffset: ((CGFloat, ContainedViewLayoutTransition, ListViewItemNode?) -> Void)?
|
||||
public final var addContentOffset: ((CGFloat, ListViewItemNode?) -> Void)?
|
||||
|
||||
public final var updateScrollingIndicator: ((ScrollingIndicatorState?, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
private var topItemOverscrollBackground: ListViewOverscrollBackgroundNode?
|
||||
private var bottomItemOverscrollBackground: ASDisplayNode?
|
||||
@ -3766,33 +3798,51 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
break
|
||||
}
|
||||
}
|
||||
if let topIndexAndBoundary = topIndexAndBoundary, let bottomIndexAndBoundary = bottomIndexAndBoundary {
|
||||
|
||||
var scrollingIndicatorStateValue: ScrollingIndicatorState?
|
||||
if let topIndexAndBoundaryValue = topIndexAndBoundary, let bottomIndexAndBoundaryValue = bottomIndexAndBoundary {
|
||||
let scrollingIndicatorState = ScrollingIndicatorState(
|
||||
insets: self.insets,
|
||||
topItem: ScrollingIndicatorState.Item(
|
||||
index: topIndexAndBoundaryValue.0,
|
||||
offset: topIndexAndBoundaryValue.1,
|
||||
height: topIndexAndBoundaryValue.2
|
||||
),
|
||||
bottomItem: ScrollingIndicatorState.Item(
|
||||
index: bottomIndexAndBoundaryValue.0,
|
||||
offset: bottomIndexAndBoundaryValue.1,
|
||||
height: bottomIndexAndBoundaryValue.2
|
||||
),
|
||||
itemCount: self.items.count
|
||||
)
|
||||
scrollingIndicatorStateValue = scrollingIndicatorState
|
||||
|
||||
let averageRangeItemHeight: CGFloat = 44.0
|
||||
|
||||
var upperItemsHeight = floor(averageRangeItemHeight * CGFloat(topIndexAndBoundary.0))
|
||||
var approximateContentHeight = CGFloat(self.items.count) * averageRangeItemHeight
|
||||
if topIndexAndBoundary.0 >= 0 && self.items[topIndexAndBoundary.0].approximateHeight.isZero {
|
||||
var upperItemsHeight = floor(averageRangeItemHeight * CGFloat(scrollingIndicatorState.topItem.index))
|
||||
var approximateContentHeight = CGFloat(scrollingIndicatorState.itemCount) * averageRangeItemHeight
|
||||
if scrollingIndicatorState.topItem.index >= 0 && self.items[scrollingIndicatorState.topItem.index].approximateHeight.isZero {
|
||||
upperItemsHeight -= averageRangeItemHeight
|
||||
approximateContentHeight -= averageRangeItemHeight
|
||||
}
|
||||
|
||||
var convertedTopBoundary: CGFloat
|
||||
if topIndexAndBoundary.1 < self.insets.top {
|
||||
convertedTopBoundary = (topIndexAndBoundary.1 - self.insets.top) * averageRangeItemHeight / topIndexAndBoundary.2
|
||||
if scrollingIndicatorState.topItem.offset < self.insets.top {
|
||||
convertedTopBoundary = (scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top) * averageRangeItemHeight / scrollingIndicatorState.topItem.height
|
||||
} else {
|
||||
convertedTopBoundary = topIndexAndBoundary.1 - self.insets.top
|
||||
convertedTopBoundary = scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top
|
||||
}
|
||||
convertedTopBoundary -= upperItemsHeight
|
||||
|
||||
let approximateOffset = -convertedTopBoundary
|
||||
|
||||
var convertedBottomBoundary: CGFloat = 0.0
|
||||
if bottomIndexAndBoundary.1 > self.visibleSize.height - self.insets.bottom {
|
||||
convertedBottomBoundary = ((self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1) * averageRangeItemHeight / bottomIndexAndBoundary.2
|
||||
if scrollingIndicatorState.bottomItem.offset > self.visibleSize.height - self.insets.bottom {
|
||||
convertedBottomBoundary = ((self.visibleSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset) * averageRangeItemHeight / scrollingIndicatorState.bottomItem.height
|
||||
} else {
|
||||
convertedBottomBoundary = (self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1
|
||||
convertedBottomBoundary = (self.visibleSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset
|
||||
}
|
||||
convertedBottomBoundary += CGFloat(bottomIndexAndBoundary.0 + 1) * averageRangeItemHeight
|
||||
convertedBottomBoundary += CGFloat(scrollingIndicatorState.bottomItem.index + 1) * averageRangeItemHeight
|
||||
|
||||
let approximateVisibleHeight = max(0.0, convertedBottomBoundary - approximateOffset)
|
||||
|
||||
@ -3801,8 +3851,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
let indicatorSideInset: CGFloat = 3.0
|
||||
var indicatorTopInset: CGFloat = 3.0
|
||||
if self.verticalScrollIndicatorFollowsOverscroll {
|
||||
if topIndexAndBoundary.0 == 0 {
|
||||
indicatorTopInset = max(topIndexAndBoundary.1 + 3.0 - self.insets.top, 3.0)
|
||||
if scrollingIndicatorState.topItem.index == 0 {
|
||||
indicatorTopInset = max(scrollingIndicatorState.topItem.offset + 3.0 - self.insets.top, 3.0)
|
||||
}
|
||||
}
|
||||
let indicatorBottomInset: CGFloat = 3.0
|
||||
@ -3814,7 +3864,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
if approximateContentHeight <= 0 {
|
||||
indicatorHeight = 0.0
|
||||
} else {
|
||||
indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - self.insets.top - self.insets.bottom) / approximateContentHeight))
|
||||
indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - scrollingIndicatorState.insets.top - scrollingIndicatorState.insets.bottom) / approximateContentHeight))
|
||||
}
|
||||
|
||||
let upperBound = self.scrollIndicatorInsets.top + indicatorTopInset
|
||||
@ -3852,6 +3902,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
} else {
|
||||
verticalScrollIndicator.isHidden = true
|
||||
}
|
||||
|
||||
self.updateScrollingIndicator?(scrollingIndicatorStateValue, transition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4638,7 +4690,3 @@ private func findAccessibilityFocus(_ node: ASDisplayNode) -> Bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func randomfqweeqwf() {
|
||||
print("t")
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import AVFoundation
|
||||
|
||||
public struct TransformImageNodeContentAnimations: OptionSet {
|
||||
public var rawValue: Int32
|
||||
@ -21,10 +22,60 @@ open class TransformImageNode: ASDisplayNode {
|
||||
|
||||
private var currentTransform: ((TransformImageArguments) -> DrawingContext?)?
|
||||
private var currentArguments: TransformImageArguments?
|
||||
private var image: UIImage?
|
||||
private var argumentsPromise = ValuePromise<TransformImageArguments>(ignoreRepeated: true)
|
||||
|
||||
private var overlayColor: UIColor?
|
||||
private var overlayNode: ASDisplayNode?
|
||||
|
||||
private var captureProtectedContentLayer: CaptureProtectedContentLayer?
|
||||
|
||||
public var captureProtected: Bool = false {
|
||||
didSet {
|
||||
if self.captureProtected != oldValue {
|
||||
if self.captureProtected {
|
||||
if self.captureProtectedContentLayer == nil {
|
||||
let captureProtectedContentLayer = CaptureProtectedContentLayer()
|
||||
self.captureProtectedContentLayer = captureProtectedContentLayer
|
||||
if #available(iOS 13.0, *) {
|
||||
captureProtectedContentLayer.preventsCapture = true
|
||||
captureProtectedContentLayer.preventsDisplaySleepDuringVideoPlayback = false
|
||||
}
|
||||
captureProtectedContentLayer.frame = self.bounds
|
||||
self.layer.addSublayer(captureProtectedContentLayer)
|
||||
if let image = self.image {
|
||||
if let cmSampleBuffer = image.cmSampleBuffer {
|
||||
captureProtectedContentLayer.enqueue(cmSampleBuffer)
|
||||
}
|
||||
}
|
||||
self.contents = nil
|
||||
}
|
||||
} else if let captureProtectedContentLayer = self.captureProtectedContentLayer {
|
||||
self.captureProtectedContentLayer = nil
|
||||
captureProtectedContentLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open override var bounds: CGRect {
|
||||
didSet {
|
||||
if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size {
|
||||
captureProtectedContentLayer.frame = super.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open override var frame: CGRect {
|
||||
didSet {
|
||||
if let overlayNode = self.overlayNode {
|
||||
overlayNode.frame = self.bounds
|
||||
}
|
||||
if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size {
|
||||
captureProtectedContentLayer.frame = super.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
@ -38,19 +89,12 @@ open class TransformImageNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
override open var frame: CGRect {
|
||||
didSet {
|
||||
if let overlayNode = self.overlayNode {
|
||||
overlayNode.frame = self.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
self.disposable.set(nil)
|
||||
self.currentArguments = nil
|
||||
self.currentTransform = nil
|
||||
self.contents = nil
|
||||
self.image = nil
|
||||
}
|
||||
|
||||
public func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, attemptSynchronously: Bool = false, dispatchOnDisplayLink: Bool = true) {
|
||||
@ -85,21 +129,31 @@ open class TransformImageNode: ASDisplayNode {
|
||||
strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
} else if strongSelf.contentAnimations.contains(.subsequentUpdates) {
|
||||
let tempLayer = CALayer()
|
||||
tempLayer.frame = strongSelf.bounds
|
||||
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
|
||||
tempLayer.contents = strongSelf.contents
|
||||
strongSelf.layer.addSublayer(tempLayer)
|
||||
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
|
||||
tempLayer?.removeFromSuperlayer()
|
||||
})
|
||||
if let _ = strongSelf.captureProtectedContentLayer {
|
||||
} else {
|
||||
let tempLayer = CALayer()
|
||||
tempLayer.frame = strongSelf.bounds
|
||||
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
|
||||
tempLayer.contents = strongSelf.contents
|
||||
strongSelf.layer.addSublayer(tempLayer)
|
||||
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
|
||||
tempLayer?.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var imageUpdate: UIImage?
|
||||
if let (transform, arguments, image) = next {
|
||||
strongSelf.currentTransform = transform
|
||||
strongSelf.currentArguments = arguments
|
||||
strongSelf.contents = image?.cgImage
|
||||
if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer {
|
||||
if let cmSampleBuffer = image?.cmSampleBuffer {
|
||||
captureProtectedContentLayer.enqueue(cmSampleBuffer)
|
||||
}
|
||||
} else {
|
||||
strongSelf.contents = image?.cgImage
|
||||
}
|
||||
strongSelf.image = image
|
||||
imageUpdate = image
|
||||
}
|
||||
if let _ = strongSelf.overlayColor {
|
||||
@ -135,7 +189,14 @@ open class TransformImageNode: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
if let image = updatedImage {
|
||||
strongSelf.contents = image.cgImage
|
||||
if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer {
|
||||
if let cmSampleBuffer = image.cmSampleBuffer {
|
||||
captureProtectedContentLayer.enqueue(cmSampleBuffer)
|
||||
}
|
||||
} else {
|
||||
strongSelf.contents = image.cgImage
|
||||
}
|
||||
strongSelf.image = image
|
||||
strongSelf.currentArguments = arguments
|
||||
if let _ = strongSelf.overlayColor {
|
||||
strongSelf.applyOverlayColor(animated: false)
|
||||
@ -207,6 +268,19 @@ open class TransformImageNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
private final class NullActionClass: NSObject, CAAction {
|
||||
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
||||
}
|
||||
}
|
||||
|
||||
private let nullAction = NullActionClass()
|
||||
|
||||
private class CaptureProtectedContentLayer: AVSampleBufferDisplayLayer {
|
||||
override func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
}
|
||||
}
|
||||
|
||||
open class TransformImageView: UIView {
|
||||
public var imageUpdated: ((UIImage?) -> Void)?
|
||||
public var contentAnimations: TransformImageNodeContentAnimations = []
|
||||
@ -215,10 +289,55 @@ open class TransformImageView: UIView {
|
||||
private var currentTransform: ((TransformImageArguments) -> DrawingContext?)?
|
||||
private var currentArguments: TransformImageArguments?
|
||||
private var argumentsPromise = ValuePromise<TransformImageArguments>(ignoreRepeated: true)
|
||||
private var image: UIImage?
|
||||
|
||||
private var captureProtectedContentLayer: CaptureProtectedContentLayer?
|
||||
|
||||
private var overlayColor: UIColor?
|
||||
private var overlayView: UIView?
|
||||
|
||||
open override var bounds: CGRect {
|
||||
didSet {
|
||||
if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size {
|
||||
captureProtectedContentLayer.frame = super.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open override var frame: CGRect {
|
||||
didSet {
|
||||
if let overlayView = self.overlayView {
|
||||
overlayView.frame = self.bounds
|
||||
}
|
||||
if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size {
|
||||
captureProtectedContentLayer.frame = super.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var captureProtected: Bool = false {
|
||||
didSet {
|
||||
if self.captureProtected != oldValue {
|
||||
if self.captureProtected {
|
||||
if self.captureProtectedContentLayer == nil {
|
||||
let captureProtectedContentLayer = CaptureProtectedContentLayer()
|
||||
captureProtectedContentLayer.frame = self.bounds
|
||||
self.layer.addSublayer(captureProtectedContentLayer)
|
||||
if let image = self.image {
|
||||
if let cmSampleBuffer = image.cmSampleBuffer {
|
||||
captureProtectedContentLayer.enqueue(cmSampleBuffer)
|
||||
}
|
||||
}
|
||||
self.layer.contents = nil
|
||||
}
|
||||
} else if let captureProtectedContentLayer = self.captureProtectedContentLayer {
|
||||
self.captureProtectedContentLayer = nil
|
||||
captureProtectedContentLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
@ -235,19 +354,13 @@ open class TransformImageView: UIView {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
override open var frame: CGRect {
|
||||
didSet {
|
||||
if let overlayView = self.overlayView {
|
||||
overlayView.frame = self.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func reset() {
|
||||
self.disposable.set(nil)
|
||||
self.currentArguments = nil
|
||||
self.currentTransform = nil
|
||||
self.layer.contents = nil
|
||||
self.image = nil
|
||||
self.captureProtectedContentLayer?.flushAndRemoveImage()
|
||||
}
|
||||
|
||||
public func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, attemptSynchronously: Bool = false, dispatchOnDisplayLink: Bool = true) {
|
||||
@ -277,26 +390,36 @@ open class TransformImageView: UIView {
|
||||
self.disposable.set((result |> deliverOnMainQueue).start(next: { [weak self] next in
|
||||
let apply: () -> Void = {
|
||||
if let strongSelf = self {
|
||||
if strongSelf.layer.contents == nil {
|
||||
if strongSelf.image == nil {
|
||||
if strongSelf.contentAnimations.contains(.firstUpdate) && !attemptSynchronously {
|
||||
strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
} else if strongSelf.contentAnimations.contains(.subsequentUpdates) {
|
||||
let tempLayer = CALayer()
|
||||
tempLayer.frame = strongSelf.bounds
|
||||
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
|
||||
tempLayer.contents = strongSelf.layer.contents
|
||||
strongSelf.layer.addSublayer(tempLayer)
|
||||
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
|
||||
tempLayer?.removeFromSuperlayer()
|
||||
})
|
||||
if let _ = strongSelf.captureProtectedContentLayer {
|
||||
} else {
|
||||
let tempLayer = CALayer()
|
||||
tempLayer.frame = strongSelf.bounds
|
||||
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
|
||||
tempLayer.contents = strongSelf.layer.contents
|
||||
strongSelf.layer.addSublayer(tempLayer)
|
||||
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
|
||||
tempLayer?.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var imageUpdate: UIImage?
|
||||
if let (transform, arguments, image) = next {
|
||||
strongSelf.currentTransform = transform
|
||||
strongSelf.currentArguments = arguments
|
||||
strongSelf.layer.contents = image?.cgImage
|
||||
if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer {
|
||||
if let cmSampleBuffer = image?.cmSampleBuffer {
|
||||
captureProtectedContentLayer.enqueue(cmSampleBuffer)
|
||||
}
|
||||
} else {
|
||||
strongSelf.layer.contents = image?.cgImage
|
||||
}
|
||||
strongSelf.image = image
|
||||
imageUpdate = image
|
||||
}
|
||||
if let _ = strongSelf.overlayColor {
|
||||
@ -369,13 +492,13 @@ open class TransformImageView: UIView {
|
||||
|
||||
private func applyOverlayColor(animated: Bool) {
|
||||
if let overlayColor = self.overlayColor {
|
||||
if let contents = self.layer.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID {
|
||||
if let image = self.image {
|
||||
if let overlayView = self.overlayView {
|
||||
(overlayView as! UIImageView).image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
|
||||
(overlayView as! UIImageView).image = UIImage(cgImage: image.cgImage!).withRenderingMode(.alwaysTemplate)
|
||||
overlayView.tintColor = overlayColor
|
||||
} else {
|
||||
let overlayView = UIImageView()
|
||||
overlayView.image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
|
||||
overlayView.image = UIImage(cgImage: image.cgImage!).withRenderingMode(.alwaysTemplate)
|
||||
overlayView.tintColor = overlayColor
|
||||
overlayView.frame = self.bounds
|
||||
self.addSubview(overlayView)
|
||||
|
@ -0,0 +1,393 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
|
||||
public final class SparseDiscreteScrollingArea: ASDisplayNode {
|
||||
private final class DragGesture: UIGestureRecognizer {
|
||||
private let shouldBegin: (CGPoint) -> Bool
|
||||
private let began: () -> Void
|
||||
private let ended: () -> Void
|
||||
private let moved: (CGFloat) -> Void
|
||||
|
||||
private var initialLocation: CGPoint?
|
||||
|
||||
public init(
|
||||
shouldBegin: @escaping (CGPoint) -> Bool,
|
||||
began: @escaping () -> Void,
|
||||
ended: @escaping () -> Void,
|
||||
moved: @escaping (CGFloat) -> Void
|
||||
) {
|
||||
self.shouldBegin = shouldBegin
|
||||
self.began = began
|
||||
self.ended = ended
|
||||
self.moved = moved
|
||||
|
||||
super.init(target: nil, action: nil)
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
override public func reset() {
|
||||
super.reset()
|
||||
|
||||
self.initialLocation = nil
|
||||
self.initialLocation = nil
|
||||
}
|
||||
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if self.numberOfTouches > 1 {
|
||||
self.state = .failed
|
||||
self.ended()
|
||||
return
|
||||
}
|
||||
|
||||
if self.state == .possible {
|
||||
if let location = touches.first?.location(in: self.view) {
|
||||
if self.shouldBegin(location) {
|
||||
self.initialLocation = location
|
||||
self.state = .began
|
||||
self.began()
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
self.initialLocation = nil
|
||||
|
||||
if self.state == .began || self.state == .changed {
|
||||
self.ended()
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
self.initialLocation = nil
|
||||
|
||||
if self.state == .began || self.state == .changed {
|
||||
self.ended()
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
|
||||
self.state = .changed
|
||||
let offset = location.y - initialLocation.y
|
||||
self.moved(offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let dateIndicator: ComponentHostView<Empty>
|
||||
private let lineIndicator: UIImageView
|
||||
|
||||
private var containerSize: CGSize?
|
||||
private var indicatorPosition: CGFloat?
|
||||
private var scrollIndicatorHeight: CGFloat?
|
||||
|
||||
private var dragGesture: DragGesture?
|
||||
public private(set) var isDragging: Bool = false
|
||||
|
||||
private var activityTimer: SwiftSignalKit.Timer?
|
||||
|
||||
public var openCurrentDate: (() -> Void)?
|
||||
|
||||
private var offsetBarTimer: SwiftSignalKit.Timer?
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
override public init() {
|
||||
self.dateIndicator = ComponentHostView<Empty>()
|
||||
self.lineIndicator = UIImageView()
|
||||
|
||||
self.dateIndicator.alpha = 0.0
|
||||
self.lineIndicator.alpha = 0.0
|
||||
|
||||
super.init()
|
||||
|
||||
self.dateIndicator.isUserInteractionEnabled = false
|
||||
self.lineIndicator.isUserInteractionEnabled = false
|
||||
|
||||
self.view.addSubview(self.dateIndicator)
|
||||
self.view.addSubview(self.lineIndicator)
|
||||
|
||||
let dragGesture = DragGesture(
|
||||
shouldBegin: { [weak self] point in
|
||||
guard let _ = self else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
began: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let offsetBarTimer = SwiftSignalKit.Timer(timeout: 0.2, repeat: false, completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.performOffsetBarTimerEvent()
|
||||
}, queue: .mainQueue())
|
||||
strongSelf.offsetBarTimer?.invalidate()
|
||||
strongSelf.offsetBarTimer = offsetBarTimer
|
||||
offsetBarTimer.start()
|
||||
|
||||
strongSelf.isDragging = true
|
||||
|
||||
/*if let scrollView = strongSelf.beginScrolling?() {
|
||||
strongSelf.draggingScrollView = scrollView
|
||||
strongSelf.scrollingInitialOffset = scrollView.contentOffset.y
|
||||
strongSelf.setContentOffset?(scrollView.contentOffset)
|
||||
}*/
|
||||
|
||||
strongSelf.updateActivityTimer(isScrolling: false)
|
||||
},
|
||||
ended: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.offsetBarTimer != nil {
|
||||
strongSelf.offsetBarTimer?.invalidate()
|
||||
strongSelf.offsetBarTimer = nil
|
||||
|
||||
strongSelf.openCurrentDate?()
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.dateIndicator.layer, offset: CGPoint(x: 0.0, y: 0.0))
|
||||
|
||||
strongSelf.isDragging = false
|
||||
|
||||
//strongSelf.updateLineIndicator(transition: transition)
|
||||
|
||||
strongSelf.updateActivityTimer(isScrolling: false)
|
||||
},
|
||||
moved: { [weak self] relativeOffset in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = relativeOffset
|
||||
|
||||
if strongSelf.offsetBarTimer != nil {
|
||||
strongSelf.offsetBarTimer?.invalidate()
|
||||
strongSelf.offsetBarTimer = nil
|
||||
strongSelf.performOffsetBarTimerEvent()
|
||||
}
|
||||
}
|
||||
)
|
||||
self.dragGesture = dragGesture
|
||||
|
||||
self.view.addGestureRecognizer(dragGesture)
|
||||
}
|
||||
|
||||
private func performOffsetBarTimerEvent() {
|
||||
self.hapticFeedback.impact()
|
||||
self.offsetBarTimer = nil
|
||||
|
||||
/*let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut)
|
||||
transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: CGPoint(x: -80.0, y: 0.0))
|
||||
self.updateLineIndicator(transition: transition)*/
|
||||
}
|
||||
|
||||
func feedbackTap() {
|
||||
self.hapticFeedback.tap()
|
||||
}
|
||||
|
||||
public func update(
|
||||
containerSize: CGSize,
|
||||
containerInsets: UIEdgeInsets,
|
||||
scrollingState: ListView.ScrollingIndicatorState?,
|
||||
isScrolling: Bool,
|
||||
theme: PresentationTheme,
|
||||
transition: ContainedViewLayoutTransition
|
||||
) {
|
||||
self.containerSize = containerSize
|
||||
if self.theme !== theme {
|
||||
self.theme = theme
|
||||
|
||||
/*var backgroundColors: [UInt32] = []
|
||||
switch chatPresentationInterfaceState.chatWallpaper {
|
||||
case let .file(file):
|
||||
if file.isPattern {
|
||||
backgroundColors = file.settings.colors
|
||||
}
|
||||
case let .gradient(gradient):
|
||||
backgroundColors = gradient.colors
|
||||
case let .color(color):
|
||||
backgroundColors = [color]
|
||||
default:
|
||||
break
|
||||
}*/
|
||||
let lineColor: UIColor
|
||||
if theme.overallDarkAppearance {
|
||||
lineColor = UIColor(white: 0.0, alpha: 0.3)
|
||||
} else {
|
||||
lineColor = UIColor(white: 0.0, alpha: 0.3)
|
||||
}
|
||||
self.lineIndicator.image = generateStretchableFilledCircleImage(diameter: 3.0, color: lineColor, strokeColor: nil, strokeWidth: nil, backgroundColor: nil)
|
||||
}
|
||||
|
||||
if self.dateIndicator.alpha.isZero {
|
||||
let transition: ContainedViewLayoutTransition = .immediate
|
||||
transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: CGPoint())
|
||||
}
|
||||
|
||||
if isScrolling {
|
||||
self.updateActivityTimer(isScrolling: true)
|
||||
}
|
||||
|
||||
let indicatorSize = self.dateIndicator.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(SparseItemGridScrollingIndicatorComponent(
|
||||
backgroundColor: theme.list.itemBlocksBackgroundColor,
|
||||
shadowColor: .black,
|
||||
foregroundColor: theme.list.itemPrimaryTextColor,
|
||||
dateString: "Date"
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: containerSize
|
||||
)
|
||||
let _ = indicatorSize
|
||||
|
||||
self.dateIndicator.isHidden = true
|
||||
|
||||
|
||||
if let scrollingIndicatorState = scrollingState {
|
||||
let averageRangeItemHeight: CGFloat = 44.0
|
||||
|
||||
let upperItemsHeight = floor(averageRangeItemHeight * CGFloat(scrollingIndicatorState.topItem.index))
|
||||
let approximateContentHeight = CGFloat(scrollingIndicatorState.itemCount) * averageRangeItemHeight
|
||||
|
||||
var convertedTopBoundary: CGFloat
|
||||
if scrollingIndicatorState.topItem.offset < scrollingIndicatorState.insets.top {
|
||||
convertedTopBoundary = (scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top) * averageRangeItemHeight / scrollingIndicatorState.topItem.height
|
||||
} else {
|
||||
convertedTopBoundary = scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top
|
||||
}
|
||||
convertedTopBoundary -= upperItemsHeight
|
||||
|
||||
let approximateOffset = -convertedTopBoundary
|
||||
|
||||
var convertedBottomBoundary: CGFloat = 0.0
|
||||
if scrollingIndicatorState.bottomItem.offset > containerSize.height - scrollingIndicatorState.insets.bottom {
|
||||
convertedBottomBoundary = ((containerSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset) * averageRangeItemHeight / scrollingIndicatorState.bottomItem.height
|
||||
} else {
|
||||
convertedBottomBoundary = (containerSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset
|
||||
}
|
||||
convertedBottomBoundary += CGFloat(scrollingIndicatorState.bottomItem.index + 1) * averageRangeItemHeight
|
||||
|
||||
let approximateVisibleHeight = max(0.0, convertedBottomBoundary - approximateOffset)
|
||||
|
||||
let approximateScrollingProgress = approximateOffset / (approximateContentHeight - approximateVisibleHeight)
|
||||
|
||||
let indicatorSideInset: CGFloat = 3.0
|
||||
let indicatorTopInset: CGFloat = 3.0
|
||||
/*if self.verticalScrollIndicatorFollowsOverscroll {
|
||||
if scrollingIndicatorState.topItem.index == 0 {
|
||||
indicatorTopInset = max(scrollingIndicatorState.topItem.offset + 3.0 - self.insets.top, 3.0)
|
||||
}
|
||||
}*/
|
||||
let indicatorBottomInset: CGFloat = 3.0
|
||||
let minIndicatorContentHeight: CGFloat = 12.0
|
||||
let minIndicatorHeight: CGFloat = 6.0
|
||||
|
||||
let visibleHeightWithoutIndicatorInsets = containerSize.height - containerInsets.top - containerInsets.bottom - indicatorTopInset - indicatorBottomInset
|
||||
let indicatorHeight: CGFloat
|
||||
if approximateContentHeight <= 0 {
|
||||
indicatorHeight = 0.0
|
||||
} else {
|
||||
indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (containerSize.height - scrollingIndicatorState.insets.top - scrollingIndicatorState.insets.bottom) / approximateContentHeight))
|
||||
}
|
||||
|
||||
let upperBound = containerInsets.top + indicatorTopInset
|
||||
let lowerBound = containerSize.height - containerInsets.bottom - indicatorTopInset - indicatorBottomInset - indicatorHeight
|
||||
|
||||
let indicatorOffset = ceilToScreenPixels(upperBound * (1.0 - approximateScrollingProgress) + lowerBound * approximateScrollingProgress)
|
||||
|
||||
//var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorSideInset : (self.visibleSize.width - 3.0 - indicatorSideInset), y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight))
|
||||
|
||||
var indicatorFrame = CGRect(origin: CGPoint(x: containerSize.width - 3.0 - indicatorSideInset, y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight))
|
||||
|
||||
if indicatorFrame.minY < containerInsets.top + indicatorTopInset {
|
||||
indicatorFrame.size.height -= containerInsets.top + indicatorTopInset - indicatorFrame.minY
|
||||
indicatorFrame.origin.y = containerInsets.top + indicatorTopInset
|
||||
indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height)
|
||||
}
|
||||
if indicatorFrame.maxY > containerSize.height - (containerInsets.bottom + indicatorTopInset + indicatorBottomInset) {
|
||||
indicatorFrame.size.height -= indicatorFrame.maxY - (containerSize.height - (containerInsets.bottom + indicatorTopInset))
|
||||
indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height)
|
||||
indicatorFrame.origin.y = containerSize.height - (containerInsets.bottom + indicatorBottomInset) - indicatorFrame.height
|
||||
}
|
||||
|
||||
if indicatorFrame.origin.y.isNaN {
|
||||
indicatorFrame.origin.y = indicatorTopInset
|
||||
}
|
||||
|
||||
if indicatorHeight >= visibleHeightWithoutIndicatorInsets {
|
||||
self.lineIndicator.isHidden = true
|
||||
self.lineIndicator.frame = indicatorFrame
|
||||
} else {
|
||||
if self.lineIndicator.isHidden {
|
||||
self.lineIndicator.isHidden = false
|
||||
self.lineIndicator.frame = indicatorFrame
|
||||
} else {
|
||||
self.lineIndicator.frame = indicatorFrame
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.lineIndicator.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
private func updateActivityTimer(isScrolling: Bool) {
|
||||
self.activityTimer?.invalidate()
|
||||
|
||||
if self.isDragging {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
transition.updateAlpha(layer: self.dateIndicator.layer, alpha: 1.0)
|
||||
transition.updateAlpha(layer: self.lineIndicator.layer, alpha: 1.0)
|
||||
} else {
|
||||
self.activityTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: false, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
transition.updateAlpha(layer: strongSelf.dateIndicator.layer, alpha: 0.0)
|
||||
transition.updateAlpha(layer: strongSelf.lineIndicator.layer, alpha: 0.0)
|
||||
}, queue: .mainQueue())
|
||||
self.activityTimer?.start()
|
||||
}
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.lineIndicator.alpha <= 0.01 {
|
||||
return nil
|
||||
}
|
||||
if self.lineIndicator.frame.insetBy(dx: -4.0, dy: -2.0).contains(point) {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
@ -555,7 +555,7 @@ private final class ShadowRoundedRectangle: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private final class SparseItemGridScrollingIndicatorComponent: CombinedComponent {
|
||||
final class SparseItemGridScrollingIndicatorComponent: CombinedComponent {
|
||||
let backgroundColor: UIColor
|
||||
let shadowColor: UIColor
|
||||
let foregroundColor: UIColor
|
||||
|
@ -709,6 +709,111 @@ public final class SparseMessageList {
|
||||
}
|
||||
}
|
||||
|
||||
public final class SparseMessageScrollingContext {
|
||||
public struct State: Equatable {
|
||||
public var totalCount: Int
|
||||
public var minTimestamp: Int32
|
||||
}
|
||||
|
||||
private final class Impl {
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let peerId: PeerId
|
||||
|
||||
let statePromise = Promise<State>()
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
init(queue: Queue, account: Account, peerId: PeerId) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
|
||||
self.reload()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
private func reload() {
|
||||
let account = self.account
|
||||
let peerId = self.peerId
|
||||
|
||||
let signal: Signal<State?, NoError> = self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|> mapToSignal { inputPeer -> Signal<State?, NoError> in
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .single(nil)
|
||||
}
|
||||
return account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: 1, offsetDate: 0, addOffset: -1, limit: 1, maxId: 0, minId: 0, hash: 0))
|
||||
|> map { result -> State? in
|
||||
let messages: [Api.Message]
|
||||
let totalCount: Int
|
||||
|
||||
switch result {
|
||||
case let .messages(apiMessages, _, _):
|
||||
messages = apiMessages
|
||||
totalCount = messages.count
|
||||
case let .messagesSlice(_, count, _, _, apiMessages, _, _):
|
||||
messages = apiMessages
|
||||
totalCount = Int(count)
|
||||
case let .channelMessages(_, _, count, _, apiMessages, _, _):
|
||||
messages = apiMessages
|
||||
totalCount = Int(count)
|
||||
case .messagesNotModified:
|
||||
messages = []
|
||||
totalCount = 0
|
||||
}
|
||||
|
||||
if let apiMessage = messages.first, let message = StoreMessage(apiMessage: apiMessage) {
|
||||
return State(totalCount: totalCount, minTimestamp: message.timestamp)
|
||||
} else {
|
||||
return State(totalCount: 0, minTimestamp: 0)
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<State?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|
||||
self.disposable.set((signal |> deliverOn(self.queue)).start(next: { [weak self] state in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let state = state {
|
||||
strongSelf.statePromise.set(.single(state))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
private let impl: QueueLocalObject<Impl>
|
||||
|
||||
public var state: Signal<State, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.statePromise.get().start(next: subscriber.putNext))
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
init(account: Account, peerId: PeerId) {
|
||||
let queue = Queue()
|
||||
self.queue = queue
|
||||
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return Impl(queue: queue, account: account, peerId: peerId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public final class SparseMessageCalendar {
|
||||
private final class Impl {
|
||||
struct InternalState {
|
||||
|
@ -251,6 +251,10 @@ public extension TelegramEngine {
|
||||
return SparseMessageCalendar(account: self.account, peerId: peerId, messageTag: tag)
|
||||
}
|
||||
|
||||
public func sparseMessageScrollingContext(peerId: EnginePeer.Id) -> SparseMessageScrollingContext {
|
||||
return SparseMessageScrollingContext(account: self.account, peerId: peerId)
|
||||
}
|
||||
|
||||
public func refreshMessageTagStats(peerId: EnginePeer.Id, tags: [EngineMessage.Tags]) -> Signal<Never, NoError> {
|
||||
let account = self.account
|
||||
return self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
|
@ -16,6 +16,7 @@ import FastBlur
|
||||
import ConfettiEffect
|
||||
import WallpaperBackgroundNode
|
||||
import GridMessageSelectionNode
|
||||
import SparseItemGrid
|
||||
|
||||
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
|
||||
let itemNode: OverlayMediaItemNode
|
||||
@ -81,6 +82,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let backgroundNode: WallpaperBackgroundNode
|
||||
let historyNode: ChatHistoryListNode
|
||||
let historyScrollingArea: SparseDiscreteScrollingArea
|
||||
var blurredHistoryNode: ASImageNode?
|
||||
let historyNodeContainer: ASDisplayNode
|
||||
let loadingNode: ChatLoadingNode
|
||||
@ -323,8 +325,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
return getMessageTransitionNode?()
|
||||
})
|
||||
self.historyNode.rotated = true
|
||||
|
||||
self.historyScrollingArea = SparseDiscreteScrollingArea()
|
||||
|
||||
self.historyNodeContainer = ASDisplayNode()
|
||||
self.historyNodeContainer.addSubnode(self.historyNode)
|
||||
self.historyNodeContainer.addSubnode(self.historyScrollingArea)
|
||||
|
||||
var getContentAreaInScreenSpaceImpl: (() -> CGRect)?
|
||||
var onTransitionEventImpl: ((ContainedViewLayoutTransition) -> Void)?
|
||||
@ -438,7 +444,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
})
|
||||
|
||||
var backgroundColors: [UInt32] = []
|
||||
/*var backgroundColors: [UInt32] = []
|
||||
switch chatPresentationInterfaceState.chatWallpaper {
|
||||
case let .file(file):
|
||||
if file.isPattern {
|
||||
@ -461,7 +467,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
} else {
|
||||
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8)
|
||||
}
|
||||
self.historyNode.enableExtractedBackgrounds = true
|
||||
self.historyNode.enableExtractedBackgrounds = true*/
|
||||
self.historyNode.verticalScrollIndicatorColor = .clear
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.historyNodeContainer)
|
||||
@ -522,6 +529,23 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.textInputPanelNode?.updateActivity = { [weak self] in
|
||||
self?.updateTypingActivity(true)
|
||||
}
|
||||
|
||||
self.historyNode.updateScrollingIndicator = { [weak self] scrollingState, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let (_, _) = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
strongSelf.historyScrollingArea.update(
|
||||
containerSize: strongSelf.historyNode.bounds.size,
|
||||
containerInsets: UIEdgeInsets(top: strongSelf.historyNode.scrollIndicatorInsets.bottom, left: 0.0, bottom: strongSelf.historyNode.scrollIndicatorInsets.top, right: 0.0),
|
||||
scrollingState: scrollingState,
|
||||
isScrolling: true,
|
||||
theme: strongSelf.chatPresentationInterfaceState.theme,
|
||||
transition: transition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -1041,6 +1065,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if let blurredHistoryNode = self.blurredHistoryNode {
|
||||
transition.updateFrame(node: blurredHistoryNode, frame: contentBounds)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.historyScrollingArea, frame: contentBounds)
|
||||
|
||||
transition.updateFrame(node: self.loadingNode, frame: contentBounds)
|
||||
|
||||
|
@ -562,6 +562,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
private let adMessagesContext: AdMessagesHistoryContext?
|
||||
private var preloadAdPeerId: PeerId?
|
||||
private let preloadAdPeerDisposable = MetaDisposable()
|
||||
|
||||
private let sparseScrollingContext: SparseMessageScrollingContext?
|
||||
|
||||
private let clientId: Atomic<Int32>
|
||||
|
||||
@ -588,8 +590,19 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
|
||||
self.prefetchManager = InChatPrefetchManager(context: context)
|
||||
|
||||
var displayAdPeer: PeerId?
|
||||
var sparseScrollPeerId: PeerId?
|
||||
switch subject {
|
||||
case .none, .message:
|
||||
if case let .peer(peerId) = chatLocation {
|
||||
displayAdPeer = peerId
|
||||
sparseScrollPeerId = peerId
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
var adMessages: Signal<[Message], NoError>
|
||||
if case .bubbles = mode, case let .peer(peerId) = chatLocation, case .none = subject {
|
||||
if case .bubbles = mode, let peerId = displayAdPeer {
|
||||
let adMessagesContext = context.engine.messages.adMessages(peerId: peerId)
|
||||
self.adMessagesContext = adMessagesContext
|
||||
adMessages = adMessagesContext.state
|
||||
@ -597,6 +610,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
self.adMessagesContext = nil
|
||||
adMessages = .single([])
|
||||
}
|
||||
|
||||
if case .bubbles = mode, let peerId = sparseScrollPeerId {
|
||||
self.sparseScrollingContext = context.engine.messages.sparseMessageScrollingContext(peerId: peerId)
|
||||
} else {
|
||||
self.sparseScrollingContext = nil
|
||||
}
|
||||
|
||||
let clientId = Atomic<Int32>(value: nextClientId)
|
||||
self.clientId = clientId
|
||||
|
@ -969,6 +969,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
}
|
||||
|
||||
if let updateImageSignal = updateImageSignal {
|
||||
strongSelf.imageNode.captureProtected = message.id.peerId.namespace == Namespaces.Peer.SecretChat
|
||||
strongSelf.imageNode.setSignal(updateImageSignal(synchronousLoads, false), attemptSynchronously: synchronousLoads)
|
||||
|
||||
var imageDimensions: CGSize?
|
||||
|
@ -321,7 +321,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let _ = item.message.adAttribute {
|
||||
} else if let adAttribute = item.message.adAttribute {
|
||||
title = nil
|
||||
subtitle = nil
|
||||
text = item.message.text
|
||||
@ -342,9 +342,17 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let author = item.message.author as? TelegramUser, author.botInfo != nil {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewBot
|
||||
} else if let author = item.message.author as? TelegramChannel, case .group = author.info {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewGroup
|
||||
if adAttribute.messageId != nil {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewPost
|
||||
} else {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewGroup
|
||||
}
|
||||
} else {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewChannel
|
||||
if adAttribute.messageId != nil {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewMessage
|
||||
} else {
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewChannel
|
||||
}
|
||||
}
|
||||
displayLine = false
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user