This commit is contained in:
Ali 2021-11-09 14:37:39 +04:00
parent f936636f6a
commit ed5e3b3c5a
14 changed files with 1170 additions and 159 deletions

View File

@ -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";

View File

@ -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
}

View File

@ -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

View File

@ -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, *) {
@ -582,6 +583,30 @@ public class DrawingContext {
}
}
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)
let y = Int(point.y * 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
}

View File

@ -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,15 +249,12 @@ 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")
}

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
import AVFoundation
public struct TransformImageNodeContentAnimations: OptionSet {
public var rawValue: Int32
@ -21,11 +22,61 @@ 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,6 +129,8 @@ open class TransformImageNode: ASDisplayNode {
strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
} else if strongSelf.contentAnimations.contains(.subsequentUpdates) {
if let _ = strongSelf.captureProtectedContentLayer {
} else {
let tempLayer = CALayer()
tempLayer.frame = strongSelf.bounds
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
@ -94,12 +140,20 @@ open class TransformImageNode: ASDisplayNode {
tempLayer?.removeFromSuperlayer()
})
}
}
var imageUpdate: UIImage?
if let (transform, arguments, image) = next {
strongSelf.currentTransform = transform
strongSelf.currentArguments = arguments
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 {
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,11 +390,13 @@ 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) {
if let _ = strongSelf.captureProtectedContentLayer {
} else {
let tempLayer = CALayer()
tempLayer.frame = strongSelf.bounds
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
@ -291,12 +406,20 @@ open class TransformImageView: UIView {
tempLayer?.removeFromSuperlayer()
})
}
}
var imageUpdate: UIImage?
if let (transform, arguments, image) = next {
strongSelf.currentTransform = transform
strongSelf.currentArguments = arguments
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)

View File

@ -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
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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 {
@ -1042,6 +1066,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
transition.updateFrame(node: blurredHistoryNode, frame: contentBounds)
}
transition.updateFrame(node: self.historyScrollingArea, frame: contentBounds)
transition.updateFrame(node: self.loadingNode, frame: contentBounds)
if let restrictedNode = self.restrictedNode {

View File

@ -563,6 +563,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private var preloadAdPeerId: PeerId?
private let preloadAdPeerDisposable = MetaDisposable()
private let sparseScrollingContext: SparseMessageScrollingContext?
private let clientId: Atomic<Int32>
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?, source: ChatHistoryListSource = .default, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles, messageTransitionNode: @escaping () -> ChatMessageTransitionNode? = { nil }) {
@ -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
@ -598,6 +611,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
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
nextClientId += 1

View File

@ -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?

View File

@ -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,10 +342,18 @@ 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 {
if adAttribute.messageId != nil {
actionTitle = item.presentationData.strings.Conversation_ViewPost
} else {
actionTitle = item.presentationData.strings.Conversation_ViewGroup
}
} else {
if adAttribute.messageId != nil {
actionTitle = item.presentationData.strings.Conversation_ViewMessage
} else {
actionTitle = item.presentationData.strings.Conversation_ViewChannel
}
}
displayLine = false
}