mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Shared media improvements
This commit is contained in:
parent
53b4e8fd32
commit
e8ec9b85fc
@ -411,7 +411,7 @@ private func peerAvatar(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer) -
|
||||
}
|
||||
}
|
||||
|
||||
let cachedPath = mediaBox.cachedRepresentationPathForId("lettersAvatar-\(peer.displayLetters.joined(separator: ","))", representationId: "intents.png", keepDuration: .shortLived)
|
||||
let cachedPath = mediaBox.cachedRepresentationPathForId("lettersAvatar2-\(peer.displayLetters.joined(separator: ","))", representationId: "intents.png", keepDuration: .shortLived)
|
||||
if let _ = fileSize(cachedPath) {
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} else {
|
||||
|
@ -19,6 +19,7 @@ swift_library(
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/PhotoResources:PhotoResources",
|
||||
"//submodules/DirectMediaImageCache:DirectMediaImageCache"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -9,24 +9,27 @@ import AccountContext
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import PhotoResources
|
||||
import DirectMediaImageCache
|
||||
|
||||
private final class MediaPreviewView: UIView {
|
||||
private let context: AccountContext
|
||||
private let message: EngineMessage
|
||||
private let media: EngineMedia
|
||||
private let imageCache: DirectMediaImageCache
|
||||
|
||||
private let imageView: TransformImageView
|
||||
private let imageView: UIImageView
|
||||
|
||||
private var requestedImage: Bool = false
|
||||
private var disposable: Disposable?
|
||||
|
||||
init(context: AccountContext, message: EngineMessage, media: EngineMedia) {
|
||||
init(context: AccountContext, message: EngineMessage, media: EngineMedia, imageCache: DirectMediaImageCache) {
|
||||
self.context = context
|
||||
self.message = message
|
||||
self.media = media
|
||||
self.imageCache = imageCache
|
||||
|
||||
self.imageView = TransformImageView()
|
||||
self.imageView.contentAnimations = .subsequentUpdates
|
||||
self.imageView = UIImageView()
|
||||
self.imageView.contentMode = .scaleToFill
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
@ -42,7 +45,53 @@ private final class MediaPreviewView: UIView {
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, synchronousLoads: Bool) {
|
||||
var dimensions = CGSize(width: 100.0, height: 100.0)
|
||||
let processImage: (UIImage) -> UIImage = { image in
|
||||
return generateImage(size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.addEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
context.clip()
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
image.draw(in: CGRect(origin: CGPoint(), size: size))
|
||||
UIGraphicsPopContext()
|
||||
})!
|
||||
}
|
||||
|
||||
if !self.requestedImage {
|
||||
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)
|
||||
}
|
||||
if let signal = result.loadSignal {
|
||||
self.disposable = (signal
|
||||
|> map { image in
|
||||
return image.flatMap(processImage)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
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()
|
||||
})
|
||||
}
|
||||
strongSelf.imageView.image = image
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@ -66,7 +115,7 @@ private final class MediaPreviewView: UIView {
|
||||
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()
|
||||
apply()*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,6 +299,20 @@ private final class ImageCache: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private final class DayEnvironment: Equatable {
|
||||
let imageCache: ImageCache
|
||||
let directImageCache: DirectMediaImageCache
|
||||
|
||||
init(imageCache: ImageCache, directImageCache: DirectMediaImageCache) {
|
||||
self.imageCache = imageCache
|
||||
self.directImageCache = directImageCache
|
||||
}
|
||||
|
||||
static func ==(lhs: DayEnvironment, rhs: DayEnvironment) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
}
|
||||
|
||||
private final class ImageComponent: Component {
|
||||
let image: UIImage?
|
||||
|
||||
@ -292,7 +355,7 @@ private final class ImageComponent: Component {
|
||||
}
|
||||
|
||||
private final class DayComponent: Component {
|
||||
typealias EnvironmentType = ImageCache
|
||||
typealias EnvironmentType = DayEnvironment
|
||||
|
||||
enum DaySelection {
|
||||
case none
|
||||
@ -410,7 +473,9 @@ private final class DayComponent: Component {
|
||||
self.action?()
|
||||
}
|
||||
|
||||
func update(component: DayComponent, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
|
||||
func update(component: DayComponent, availableSize: CGSize, environment: Environment<DayEnvironment>, transition: Transition) -> CGSize {
|
||||
let isFirstTime = self.action == nil
|
||||
|
||||
self.action = component.action
|
||||
self.index = component.media?.message.index
|
||||
self.isHighlightingEnabled = component.isEnabled && component.media != nil && !component.isSelecting
|
||||
@ -418,23 +483,26 @@ private final class DayComponent: Component {
|
||||
let diameter = min(availableSize.width, availableSize.height)
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - diameter) / 2.0), y: floor((availableSize.height - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter))
|
||||
|
||||
let imageCache = environment[ImageCache.self]
|
||||
let dayEnvironment = environment[DayEnvironment.self].value
|
||||
if component.media != nil {
|
||||
self.highlightView.image = imageCache.value.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2))
|
||||
self.highlightView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2))
|
||||
} else {
|
||||
self.highlightView.image = nil
|
||||
}
|
||||
|
||||
var animateMediaIn = false
|
||||
if self.currentMedia != component.media {
|
||||
self.currentMedia = component.media
|
||||
|
||||
if let mediaPreviewView = self.mediaPreviewView {
|
||||
self.mediaPreviewView = nil
|
||||
mediaPreviewView.removeFromSuperview()
|
||||
} else {
|
||||
animateMediaIn = !isFirstTime
|
||||
}
|
||||
|
||||
if let media = component.media {
|
||||
let mediaPreviewView = MediaPreviewView(context: component.context, message: media.message, media: media.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)
|
||||
@ -490,9 +558,9 @@ private final class DayComponent: Component {
|
||||
}
|
||||
selectionView.frame = contentFrame
|
||||
if self.mediaPreviewView != nil {
|
||||
selectionView.image = imageCache.value.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor)
|
||||
selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor)
|
||||
} else {
|
||||
selectionView.image = imageCache.value.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor)
|
||||
selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor)
|
||||
}
|
||||
case .middle, .none:
|
||||
if let selectionView = self.selectionView {
|
||||
@ -509,7 +577,7 @@ private final class DayComponent: Component {
|
||||
contentScale = 1.0
|
||||
}
|
||||
|
||||
let titleImage = imageCache.value.text(fontSize: titleFontSize, isSemibold: titleFontIsSemibold, color: titleColor, string: component.title)
|
||||
let titleImage = dayEnvironment.imageCache.text(fontSize: titleFontSize, isSemibold: titleFontIsSemibold, color: titleColor, string: component.title)
|
||||
self.titleView.image = titleImage
|
||||
let titleSize = titleImage.size
|
||||
|
||||
@ -524,6 +592,10 @@ private final class DayComponent: Component {
|
||||
mediaPreviewView.updateLayout(size: contentFrame.size, synchronousLoads: false)
|
||||
|
||||
mediaPreviewView.layer.sublayerTransform = CATransform3DMakeScale(contentScale, contentScale, 1.0)
|
||||
|
||||
if animateMediaIn {
|
||||
mediaPreviewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize
|
||||
@ -534,13 +606,13 @@ private final class DayComponent: Component {
|
||||
return View()
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
|
||||
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 = ImageCache
|
||||
typealias EnvironmentType = DayEnvironment
|
||||
|
||||
let context: AccountContext
|
||||
let model: MonthModel
|
||||
@ -593,7 +665,7 @@ private final class MonthComponent: CombinedComponent {
|
||||
static var body: Body {
|
||||
let title = Child(Text.self)
|
||||
let weekdayTitles = ChildMap(environment: Empty.self, keyedBy: Int.self)
|
||||
let days = ChildMap(environment: ImageCache.self, keyedBy: Int.self)
|
||||
let days = ChildMap(environment: DayEnvironment.self, keyedBy: Int.self)
|
||||
let selections = ChildMap(environment: Empty.self, keyedBy: Int.self)
|
||||
|
||||
return { context in
|
||||
@ -673,7 +745,7 @@ private final class MonthComponent: CombinedComponent {
|
||||
}
|
||||
)),
|
||||
environment: {
|
||||
context.environment[ImageCache.self]
|
||||
context.environment[DayEnvironment.self]
|
||||
},
|
||||
availableSize: CGSize(width: usableWeekdayWidth, height: weekdaySize),
|
||||
transition: .immediate
|
||||
@ -739,7 +811,7 @@ private final class MonthComponent: CombinedComponent {
|
||||
|
||||
if let selectedDays = context.component.selectedDays {
|
||||
for (lineIndex, selection) in selectionsByLine {
|
||||
let imageCache = context.environment[ImageCache.self]
|
||||
let dayEnvironment = context.environment[DayEnvironment.self].value
|
||||
|
||||
let dayItemSize = updatedDays[0].size
|
||||
let deltaWidth = floor((weekdayWidth - dayItemSize.width) / 2.0)
|
||||
@ -766,7 +838,7 @@ private final class MonthComponent: CombinedComponent {
|
||||
|
||||
let selectionRect = CGRect(origin: CGPoint(x: minX, y: minY), size: CGSize(width: maxX - minX, height: maxY - minY))
|
||||
let selection = selections[lineIndex].update(
|
||||
component: AnyComponent(ImageComponent(image: imageCache.value.monthSelection(leftRadius: leftRadius, rightRadius: rightRadius, maxRadius: dayItemSize.width, color: monthSelectionColor))),
|
||||
component: AnyComponent(ImageComponent(image: dayEnvironment.imageCache.monthSelection(leftRadius: leftRadius, rightRadius: rightRadius, maxRadius: dayItemSize.width, color: monthSelectionColor))),
|
||||
availableSize: selectionRect.size,
|
||||
transition: .immediate
|
||||
)
|
||||
@ -883,10 +955,10 @@ public final class CalendarMessageScreen: ViewController {
|
||||
private let calendarSource: SparseMessageCalendar
|
||||
|
||||
private var months: [MonthModel] = []
|
||||
private var monthViews: [Int: ComponentHostView<ImageCache>] = [:]
|
||||
private var monthViews: [Int: ComponentHostView<DayEnvironment>] = [:]
|
||||
private let contextGestureContainerNode: ContextControllerSourceNode
|
||||
|
||||
private let imageCache = ImageCache()
|
||||
private let dayEnvironment: DayEnvironment
|
||||
|
||||
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
||||
private var scrollLayout: (width: CGFloat, contentHeight: CGFloat, frames: [Int: CGRect])?
|
||||
@ -933,6 +1005,8 @@ public final class CalendarMessageScreen: ViewController {
|
||||
self.scrollView.indicatorStyle = .black
|
||||
}
|
||||
|
||||
self.dayEnvironment = DayEnvironment(imageCache: ImageCache(), directImageCache: DirectMediaImageCache(account: context.account))
|
||||
|
||||
super.init()
|
||||
|
||||
self.contextGestureContainerNode.shouldBegin = { [weak self] point in
|
||||
@ -1403,8 +1477,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
var contentHeight: CGFloat = layout.intrinsicInsets.bottom
|
||||
var frames: [Int: CGRect] = [:]
|
||||
|
||||
let measureView = ComponentHostView<ImageCache>()
|
||||
let imageCache = ImageCache()
|
||||
let measureView = ComponentHostView<DayEnvironment>()
|
||||
for i in 0 ..< self.months.count {
|
||||
let monthSize = measureView.update(
|
||||
transition: .immediate,
|
||||
@ -1419,7 +1492,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
selectedDays: nil
|
||||
)),
|
||||
environment: {
|
||||
imageCache
|
||||
self.dayEnvironment
|
||||
},
|
||||
containerSize: CGSize(width: layout.size.width, height: 10000.0
|
||||
))
|
||||
@ -1458,7 +1531,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
}
|
||||
validMonths.insert(i)
|
||||
|
||||
let monthView: ComponentHostView<ImageCache>
|
||||
let monthView: ComponentHostView<DayEnvironment>
|
||||
if let current = self.monthViews[i] {
|
||||
monthView = current
|
||||
} else {
|
||||
@ -1516,7 +1589,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
selectedDays: self.selectionState?.dayRange
|
||||
)),
|
||||
environment: {
|
||||
self.imageCache
|
||||
self.dayEnvironment
|
||||
},
|
||||
containerSize: CGSize(width: width, height: 10000.0
|
||||
))
|
||||
@ -1582,14 +1655,14 @@ public final class CalendarMessageScreen: ViewController {
|
||||
updatedMedia[i] = [:]
|
||||
}
|
||||
|
||||
for day in 0 ..< self.months[i].numberOfDays {
|
||||
let firstDayTimestamp = Int32(self.months[i].firstDay.timeIntervalSince1970)
|
||||
let firstDayTimestamp = Int32(self.months[i].firstDay.timeIntervalSince1970)
|
||||
|
||||
for day in 0 ..< self.months[i].numberOfDays {
|
||||
let dayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day)
|
||||
let nextDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day - 1)
|
||||
let nextDayTimestamp = dayTimestamp + 24 * 60 * 60
|
||||
|
||||
for message in messageMap {
|
||||
if message.timestamp <= dayTimestamp && message.timestamp >= nextDayTimestamp {
|
||||
if message.timestamp >= dayTimestamp && message.timestamp < nextDayTimestamp {
|
||||
mediaLoop: for media in message.media {
|
||||
switch media {
|
||||
case _ as TelegramMediaImage, _ as TelegramMediaFile:
|
||||
@ -1644,9 +1717,9 @@ public final class CalendarMessageScreen: ViewController {
|
||||
//TODO:localize
|
||||
self.navigationItem.setTitle("Calendar", animated: false)
|
||||
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
/*if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.toggleSelectPressed)), animated: false)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
@ -1111,7 +1111,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
self.currentItems = items
|
||||
self.currentActionsMinHeight = minHeight
|
||||
|
||||
|
||||
let previousActionsContainerNode = self.actionsContainerNode
|
||||
let previousActionsContainerFrame = previousActionsContainerNode.view.convert(previousActionsContainerNode.bounds, to: self.view)
|
||||
self.actionsContainerNode = ContextActionsContainerNode(presentationData: self.presentationData, items: items, getController: { [weak self] in
|
||||
@ -1690,9 +1690,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
switch maybePassthrough {
|
||||
case .ignore:
|
||||
break
|
||||
case let .dismiss(consume):
|
||||
case let .dismiss(consume, hitTestResult):
|
||||
self.getController()?.dismiss(completion: nil)
|
||||
|
||||
if let hitTestResult = hitTestResult {
|
||||
return hitTestResult
|
||||
}
|
||||
if !consume {
|
||||
return nil
|
||||
}
|
||||
@ -1862,7 +1865,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
|
||||
public enum HandledTouchEvent {
|
||||
case ignore
|
||||
case dismiss(consume: Bool)
|
||||
case dismiss(consume: Bool, result: UIView?)
|
||||
}
|
||||
|
||||
public var passthroughTouchEvent: ((UIView, CGPoint) -> HandledTouchEvent)?
|
||||
|
@ -101,6 +101,9 @@ public final class ConstantDisplayLinkAnimator {
|
||||
self.displayLink = CADisplayLink(target: DisplayLinkTarget({ [weak self] in
|
||||
self?.tick()
|
||||
}), selector: #selector(DisplayLinkTarget.event))
|
||||
if #available(iOS 15.0, *) {
|
||||
self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 120.0, preferred: 120.0)
|
||||
}
|
||||
self.displayLink.isPaused = true
|
||||
self.displayLink.add(to: RunLoop.main, forMode: .common)
|
||||
}
|
||||
|
@ -426,7 +426,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
let zoomLevel: ZoomLevel
|
||||
|
||||
private let scrollView: UIScrollView
|
||||
let scrollView: UIScrollView
|
||||
private let shimmer: Shimmer
|
||||
|
||||
var theme: PresentationTheme
|
||||
@ -448,6 +448,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
let coveringOffsetUpdated: (Viewport, ContainedViewLayoutTransition) -> Void
|
||||
|
||||
private var decelerationAnimator: ConstantDisplayLinkAnimator?
|
||||
|
||||
init(theme: PresentationTheme, zoomLevel: ZoomLevel, maybeLoadHoleAnchor: @escaping (HoleAnchor, HoleLocation) -> Void, coveringOffsetUpdated: @escaping (Viewport, ContainedViewLayoutTransition) -> Void) {
|
||||
self.theme = theme
|
||||
self.zoomLevel = zoomLevel
|
||||
@ -487,6 +489,10 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
@objc func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
self.items?.itemBinding.didScroll()
|
||||
if let decelerationAnimator = self.decelerationAnimator {
|
||||
self.decelerationAnimator = nil
|
||||
decelerationAnimator.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
@ -638,10 +644,16 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
|
||||
func anchorItem(at point: CGPoint) -> Item? {
|
||||
guard let items = self.items, !items.items.isEmpty else {
|
||||
guard let items = self.items, !items.items.isEmpty, let layout = self.layout else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if layout.containerLayout.lockScrollingAtTop {
|
||||
if let item = items.item(at: 0) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
let localPoint = self.scrollView.convert(point, from: self.view)
|
||||
|
||||
var closestItem: (CGFloat, AnyHashable)?
|
||||
@ -714,6 +726,49 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.scrollView.setContentOffset(self.scrollView.contentOffset, animated: false)
|
||||
}
|
||||
|
||||
func transferVelocity(_ velocity: CGFloat) {
|
||||
if velocity <= 0.0 {
|
||||
return
|
||||
}
|
||||
self.decelerationAnimator?.isPaused = true
|
||||
let startTime = CACurrentMediaTime()
|
||||
var currentOffset = self.scrollView.contentOffset
|
||||
let decelerationRate: CGFloat = 0.998
|
||||
self.scrollViewDidEndDragging(self.scrollView, willDecelerate: true)
|
||||
self.decelerationAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let t = CACurrentMediaTime() - startTime
|
||||
var currentVelocity = velocity * 15.0 * CGFloat(pow(Double(decelerationRate), 1000.0 * t))
|
||||
currentOffset.y += currentVelocity
|
||||
let maxOffset = strongSelf.scrollView.contentSize.height - strongSelf.scrollView.bounds.height
|
||||
if currentOffset.y >= maxOffset {
|
||||
currentOffset.y = maxOffset
|
||||
currentVelocity = 0.0
|
||||
}
|
||||
if currentOffset.y < 0.0 {
|
||||
currentOffset.y = 0.0
|
||||
currentVelocity = 0.0
|
||||
}
|
||||
|
||||
var didEnd = false
|
||||
if abs(currentVelocity) < 0.1 {
|
||||
strongSelf.decelerationAnimator?.isPaused = true
|
||||
strongSelf.decelerationAnimator = nil
|
||||
didEnd = true
|
||||
}
|
||||
var contentOffset = strongSelf.scrollView.contentOffset
|
||||
contentOffset.y = floorToScreenPixels(currentOffset.y)
|
||||
strongSelf.scrollView.setContentOffset(contentOffset, animated: false)
|
||||
strongSelf.scrollViewDidScroll(strongSelf.scrollView)
|
||||
if didEnd {
|
||||
strongSelf.scrollViewDidEndDecelerating(strongSelf.scrollView)
|
||||
}
|
||||
})
|
||||
self.decelerationAnimator?.isPaused = false
|
||||
}
|
||||
|
||||
private func updateVisibleItems(resetScrolling: Bool, synchronous: Bool, restoreScrollPosition: (y: CGFloat, index: Int)?) {
|
||||
guard let layout = self.layout, let items = self.items else {
|
||||
return
|
||||
@ -951,7 +1006,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
containerInsets: layout.containerLayout.insets,
|
||||
contentHeight: contentHeight,
|
||||
contentOffset: self.scrollView.bounds.minY,
|
||||
isScrolling: self.scrollView.isDragging || self.scrollView.isDecelerating,
|
||||
isScrolling: self.scrollView.isDragging || self.scrollView.isDecelerating || self.decelerationAnimator != nil,
|
||||
dateString: dateString ?? "",
|
||||
theme: self.theme,
|
||||
transition: .immediate
|
||||
@ -1006,13 +1061,15 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
let previousProgress = self.currentProgress
|
||||
self.currentProgress = progress
|
||||
|
||||
if let fromItem = self.fromViewport.anchorItem(at: fromAnchorFrame.center), let fromFrame = self.fromViewport.frameForItem(at: fromItem.index) {
|
||||
let fixedAnchorPoint = CGPoint(x: fromAnchorFrame.minX + 1.0, y: fromAnchorFrame.minY + 1.0)
|
||||
|
||||
if let fromItem = self.fromViewport.anchorItem(at: fixedAnchorPoint), let fromFrame = self.fromViewport.frameForItem(at: fromItem.index) {
|
||||
fromAnchorFrame.origin.y = fromFrame.midY
|
||||
fromAnchorFrame.origin.x = fromFrame.midX
|
||||
fromAnchorFrame.size.width = 0.0
|
||||
}
|
||||
|
||||
if let toItem = self.toViewport.anchorItem(at: fromAnchorFrame.center), let toFrame = self.toViewport.frameForItem(at: toItem.index) {
|
||||
if let toItem = self.toViewport.anchorItem(at: fixedAnchorPoint), let toFrame = self.toViewport.frameForItem(at: toItem.index) {
|
||||
toAnchorFrame.origin.y = toFrame.midY
|
||||
toAnchorFrame.origin.x = toFrame.midX
|
||||
toAnchorFrame.size.width = 0.0
|
||||
@ -1056,7 +1113,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
completion()
|
||||
})
|
||||
|
||||
let fromAlphaStartProgress: CGFloat = 0.6
|
||||
let fromAlphaStartProgress: CGFloat = 0.7
|
||||
let fromAlphaEndProgress: CGFloat = 1.0
|
||||
let fromAlphaProgress = max(0.0, progress - fromAlphaStartProgress) / (fromAlphaEndProgress - fromAlphaStartProgress)
|
||||
|
||||
@ -1105,6 +1162,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
|
||||
public var cancelExternalContentGestures: (() -> Void)?
|
||||
public var zoomLevelUpdated: ((ZoomLevel) -> Void)?
|
||||
|
||||
public init(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
@ -1288,7 +1346,9 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
let previousViewport = strongSelf.currentViewport
|
||||
|
||||
strongSelf.currentViewport = progress < 0.5 ? currentViewportTransition.fromViewport : currentViewportTransition.toViewport
|
||||
let updatedViewport = progress < 0.5 ? currentViewportTransition.fromViewport : currentViewportTransition.toViewport
|
||||
strongSelf.currentViewport = updatedViewport
|
||||
strongSelf.zoomLevelUpdated?(updatedViewport.zoomLevel)
|
||||
|
||||
if let previousViewport = previousViewport, previousViewport !== strongSelf.currentViewport {
|
||||
previousViewport.removeFromSupernode()
|
||||
@ -1319,7 +1379,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.scrollingArea.isHidden = lockScrollingAtTop
|
||||
|
||||
self.tapRecognizer?.isEnabled = fixedItemHeight == nil
|
||||
self.pinchRecognizer?.isEnabled = fixedItemHeight == nil && !lockScrollingAtTop
|
||||
self.pinchRecognizer?.isEnabled = fixedItemHeight == nil
|
||||
|
||||
if self.currentViewport == nil {
|
||||
let currentViewport = Viewport(theme: self.theme, zoomLevel: self.initialZoomLevel ?? ZoomLevel(rawValue: 3), maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
@ -1544,4 +1604,30 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
itemShimmerLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
public func hitTestResultForScrolling() -> UIView? {
|
||||
if let _ = self.currentViewportTransition {
|
||||
return nil
|
||||
} else if let currentViewport = self.currentViewport {
|
||||
return currentViewport.scrollView
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func brieflyDisableTouchActions() {
|
||||
let tapEnabled = self.tapRecognizer?.isEnabled ?? true
|
||||
self.tapRecognizer?.isEnabled = false
|
||||
let pinchEnabled = self.pinchRecognizer?.isEnabled ?? true
|
||||
self.pinchRecognizer?.isEnabled = false
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.tapRecognizer?.isEnabled = tapEnabled
|
||||
self.pinchRecognizer?.isEnabled = pinchEnabled
|
||||
}
|
||||
}
|
||||
|
||||
public func transferVelocity(_ velocity: CGFloat) {
|
||||
self.currentViewport?.transferVelocity(velocity)
|
||||
}
|
||||
}
|
||||
|
@ -769,7 +769,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
self.offsetBarTimer = nil
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut)
|
||||
transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: self.beganAtDateIndicator ? CGPoint(x: -80.0, y: 0.0) : CGPoint(x: -3.0, y: 0.0))
|
||||
transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: CGPoint(x: -80.0, y: 0.0))
|
||||
self.updateLineIndicator(transition: transition)
|
||||
}
|
||||
|
||||
|
@ -670,8 +670,8 @@ public final class SparseMessageCalendar {
|
||||
var messagesByDay: [Int32: Message] = [:]
|
||||
for period in periods {
|
||||
switch period {
|
||||
case let .searchResultsCalendarPeriod(date, minMsgId, maxMsgId, _):
|
||||
if let message = transaction.getMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: maxMsgId)) {
|
||||
case let .searchResultsCalendarPeriod(date, minMsgId, _, _):
|
||||
if let message = transaction.getMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: minMsgId)) {
|
||||
messagesByDay[date] = message
|
||||
}
|
||||
if let minMessageIdValue = minMessageId {
|
||||
|
@ -676,7 +676,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
PresentationAppIcon(name: "BlackFilledIcon", imageName: "BlackFilledIcon")
|
||||
]
|
||||
if buildConfig.isInternalBuild {
|
||||
icons.append(PresentationAppIcon(name: "WhiteFilled", imageName: "WhiteFilledIcon"))
|
||||
icons.append(PresentationAppIcon(name: "WhiteFilledIcon", imageName: "WhiteFilledIcon"))
|
||||
}
|
||||
return icons
|
||||
} else {
|
||||
|
@ -1554,6 +1554,13 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
self.itemGrid.cancelExternalContentGestures = { [weak self] in
|
||||
self?.contextGestureContainerNode.cancelGesture()
|
||||
}
|
||||
|
||||
self.itemGrid.zoomLevelUpdated = { [weak self] zoomLevel in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = updateVisualMediaStoredState(postbox: strongSelf.context.account.postbox, peerId: strongSelf.peerId, messageTag: strongSelf.stateTag, state: VisualMediaStoredState(zoomLevel: Int32(zoomLevel.rawValue))).start()
|
||||
}
|
||||
|
||||
self._itemInteraction = VisualMediaItemInteraction(
|
||||
openMessage: { [weak self] message in
|
||||
@ -1644,7 +1651,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
let rect = strongSelf.itemGrid.frameForItem(layer: itemLayer)
|
||||
|
||||
let proxyNode = ASDisplayNode()
|
||||
/*let proxyNode = ASDisplayNode()
|
||||
proxyNode.frame = rect
|
||||
proxyNode.contents = itemLayer.contents
|
||||
proxyNode.isHidden = true
|
||||
@ -1656,9 +1663,9 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
|
||||
Queue.mainQueue().after(1.0, {
|
||||
escapeNotification.keep()
|
||||
})
|
||||
})*/
|
||||
|
||||
strongSelf.chatControllerInteraction.openMessageContextActions(message, proxyNode, proxyNode.bounds, gesture)
|
||||
strongSelf.chatControllerInteraction.openMessageContextActions(message, strongSelf, rect, gesture)
|
||||
|
||||
strongSelf.itemGrid.cancelGestures()
|
||||
}
|
||||
@ -1957,6 +1964,14 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
func scrollToTop() -> Bool {
|
||||
return self.itemGrid.scrollToTop()
|
||||
}
|
||||
|
||||
func hitTestResultForScrolling() -> UIView? {
|
||||
return self.itemGrid.hitTestResultForScrolling()
|
||||
}
|
||||
|
||||
func brieflyDisableTouchActions() {
|
||||
self.itemGrid.brieflyDisableTouchActions()
|
||||
}
|
||||
|
||||
func findLoadedMessage(id: MessageId) -> Message? {
|
||||
guard let items = self.items else {
|
||||
@ -1991,45 +2006,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
|
||||
func transferVelocity(_ velocity: CGFloat) {
|
||||
/*if velocity > 0.0 {
|
||||
self.decelerationAnimator?.isPaused = true
|
||||
let startTime = CACurrentMediaTime()
|
||||
var currentOffset = self.scrollNode.view.contentOffset
|
||||
let decelerationRate: CGFloat = 0.998
|
||||
self.scrollViewDidEndDragging(self.scrollNode.view, willDecelerate: true)
|
||||
self.decelerationAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let t = CACurrentMediaTime() - startTime
|
||||
var currentVelocity = velocity * 15.0 * CGFloat(pow(Double(decelerationRate), 1000.0 * t))
|
||||
currentOffset.y += currentVelocity
|
||||
let maxOffset = strongSelf.scrollNode.view.contentSize.height - strongSelf.scrollNode.bounds.height
|
||||
if currentOffset.y >= maxOffset {
|
||||
currentOffset.y = maxOffset
|
||||
currentVelocity = 0.0
|
||||
}
|
||||
if currentOffset.y < 0.0 {
|
||||
currentOffset.y = 0.0
|
||||
currentVelocity = 0.0
|
||||
}
|
||||
|
||||
var didEnd = false
|
||||
if abs(currentVelocity) < 0.1 {
|
||||
strongSelf.decelerationAnimator?.isPaused = true
|
||||
strongSelf.decelerationAnimator = nil
|
||||
didEnd = true
|
||||
}
|
||||
var contentOffset = strongSelf.scrollNode.view.contentOffset
|
||||
contentOffset.y = floorToScreenPixels(currentOffset.y)
|
||||
strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false)
|
||||
strongSelf.scrollViewDidScroll(strongSelf.scrollNode.view)
|
||||
if didEnd {
|
||||
strongSelf.scrollViewDidEndDecelerating(strongSelf.scrollNode.view)
|
||||
}
|
||||
})
|
||||
self.decelerationAnimator?.isPaused = false
|
||||
}*/
|
||||
self.itemGrid.transferVelocity(velocity)
|
||||
}
|
||||
|
||||
func cancelPreviewGestures() {
|
||||
|
@ -2059,7 +2059,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
switch previewData {
|
||||
case let .gallery(gallery):
|
||||
gallery.setHintWillBePresentedInPreviewingContext(true)
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node, sourceRect: rect)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture)
|
||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||
case .instantPage:
|
||||
break
|
||||
@ -6120,9 +6120,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
items.append(.action(generateAction(true)))
|
||||
items.append(.action(generateAction(false)))
|
||||
|
||||
var ignoreNextActions = false
|
||||
items.append(.action(ContextMenuActionItem(text: "Show Calendar", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, a in
|
||||
if ignoreNextActions {
|
||||
return
|
||||
}
|
||||
ignoreNextActions = true
|
||||
a(.default)
|
||||
|
||||
self?.openMediaCalendar()
|
||||
@ -6204,14 +6209,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil)
|
||||
guard let localResult = strongSelf.hitTest(localPoint, with: nil) else {
|
||||
return .dismiss(consume: true)
|
||||
return .dismiss(consume: true, result: nil)
|
||||
}
|
||||
|
||||
var testView: UIView? = localResult
|
||||
while true {
|
||||
if let testViewValue = testView {
|
||||
if testViewValue.asyncdisplaykit_node is PeerInfoVisualMediaPaneNode {
|
||||
return .dismiss(consume: false)
|
||||
if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode {
|
||||
node.brieflyDisableTouchActions()
|
||||
return .dismiss(consume: false, result: nil)
|
||||
} else {
|
||||
testView = testViewValue.superview
|
||||
}
|
||||
@ -6220,7 +6226,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
}
|
||||
|
||||
return .dismiss(consume: true)
|
||||
return .dismiss(consume: true, result: nil)
|
||||
}
|
||||
strongSelf.mediaGalleryContextMenu = contextController
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
|
Loading…
x
Reference in New Issue
Block a user