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
b5d1b377f2
commit
73181eabd5
@ -271,7 +271,9 @@ private final class DayComponent: Component {
|
||||
init() {
|
||||
self.button = HighlightableButton()
|
||||
self.highlightView = UIImageView()
|
||||
self.highlightView.isUserInteractionEnabled = false
|
||||
self.titleView = UIImageView()
|
||||
self.titleView.isUserInteractionEnabled = false
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
@ -316,6 +318,7 @@ private final class DayComponent: Component {
|
||||
|
||||
if let media = component.media {
|
||||
let mediaPreviewView = MediaPreviewView(context: component.context, message: media.message, media: media.media)
|
||||
mediaPreviewView.isUserInteractionEnabled = false
|
||||
self.mediaPreviewView = mediaPreviewView
|
||||
self.button.insertSubview(mediaPreviewView, belowSubview: self.highlightView)
|
||||
}
|
||||
@ -603,6 +606,8 @@ public final class CalendarMessageScreen: ViewController {
|
||||
private var presentationData: PresentationData
|
||||
private var scrollView: Scroller
|
||||
|
||||
private let calendarSource: SparseMessageCalendar
|
||||
|
||||
private var initialMonthIndex: Int = 0
|
||||
private var months: [MonthModel] = []
|
||||
private var monthViews: [Int: ComponentHostView<ImageCache>] = [:]
|
||||
@ -612,9 +617,15 @@ public final class CalendarMessageScreen: ViewController {
|
||||
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
||||
private var scrollLayout: (width: CGFloat, contentHeight: CGFloat, frames: [Int: CGRect])?
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, initialTimestamp: Int32, navigateToDay: @escaping (Int32) -> Void) {
|
||||
private var calendarState: SparseMessageCalendar.State?
|
||||
|
||||
private var isLoadingMoreDisposable: Disposable?
|
||||
private var stateDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (Int32) -> Void) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.calendarSource = calendarSource
|
||||
self.navigateToDay = navigateToDay
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -679,7 +690,29 @@ public final class CalendarMessageScreen: ViewController {
|
||||
self.scrollView.delegate = self
|
||||
self.view.addSubview(self.scrollView)
|
||||
|
||||
self.reloadMediaInfo()
|
||||
self.isLoadingMoreDisposable = (self.calendarSource.isLoadingMore
|
||||
|> distinctUntilChanged
|
||||
|> filter { !$0 }
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.calendarSource.loadMore()
|
||||
})
|
||||
|
||||
self.stateDisposable = (self.calendarSource.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.calendarState = state
|
||||
strongSelf.reloadMediaInfo()
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.isLoadingMoreDisposable?.dispose()
|
||||
self.stateDisposable?.dispose()
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
@ -820,7 +853,66 @@ public final class CalendarMessageScreen: ViewController {
|
||||
}
|
||||
|
||||
private func reloadMediaInfo() {
|
||||
let peerId = self.peerId
|
||||
guard let calendarState = self.calendarState else {
|
||||
return
|
||||
}
|
||||
var messageMap: [Message] = []
|
||||
for (_, message) in calendarState.messagesByDay {
|
||||
messageMap.append(message)
|
||||
}
|
||||
|
||||
let _ = messageMap
|
||||
|
||||
var updatedMedia: [Int: [Int: DayMedia]] = [:]
|
||||
var removeMonths: [Int] = []
|
||||
for i in 0 ..< self.months.count {
|
||||
let firstDayTimestamp = Int32(self.months[i].firstDay.timeIntervalSince1970)
|
||||
let lastDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(self.months[i].numberOfDays)
|
||||
|
||||
if let minTimestamp = calendarState.minTimestamp, minTimestamp > lastDayTimestamp {
|
||||
removeMonths.append(i)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
for message in messageMap {
|
||||
if message.timestamp <= dayTimestamp && message.timestamp >= nextDayTimestamp {
|
||||
mediaLoop: for media in message.media {
|
||||
switch media {
|
||||
case _ as TelegramMediaImage, _ as TelegramMediaFile:
|
||||
if updatedMedia[i] == nil {
|
||||
updatedMedia[i] = [:]
|
||||
}
|
||||
updatedMedia[i]![day] = DayMedia(message: EngineMessage(message), media: EngineMedia(media))
|
||||
break mediaLoop
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (monthIndex, mediaByDay) in updatedMedia {
|
||||
self.months[monthIndex].mediaByDay = mediaByDay
|
||||
}
|
||||
|
||||
for i in removeMonths.reversed() {
|
||||
self.months.remove(at: i)
|
||||
}
|
||||
|
||||
if !removeMonths.isEmpty {
|
||||
self.scrollLayout = nil
|
||||
let _ = self.updateScrollLayoutIfNeeded()
|
||||
}
|
||||
|
||||
self.updateMonthViews()
|
||||
|
||||
/*let peerId = self.peerId
|
||||
let months = self.months
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> [Int: [Int: DayMedia]] in
|
||||
var updatedMedia: [Int: [Int: DayMedia]] = [:]
|
||||
@ -859,7 +951,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
strongSelf.months[monthIndex].mediaByDay = mediaByDay
|
||||
}
|
||||
strongSelf.updateMonthViews()
|
||||
})
|
||||
})*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -869,12 +961,14 @@ public final class CalendarMessageScreen: ViewController {
|
||||
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let calendarSource: SparseMessageCalendar
|
||||
private let initialTimestamp: Int32
|
||||
private let navigateToDay: (CalendarMessageScreen, Int32) -> Void
|
||||
|
||||
public init(context: AccountContext, peerId: PeerId, initialTimestamp: Int32, navigateToDay: @escaping (CalendarMessageScreen, Int32) -> Void) {
|
||||
public init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (CalendarMessageScreen, Int32) -> Void) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.calendarSource = calendarSource
|
||||
self.initialTimestamp = initialTimestamp
|
||||
self.navigateToDay = navigateToDay
|
||||
|
||||
@ -898,7 +992,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(context: self.context, peerId: self.peerId, initialTimestamp: self.initialTimestamp, navigateToDay: { [weak self] timestamp in
|
||||
self.displayNode = Node(context: self.context, peerId: self.peerId, calendarSource: self.calendarSource, initialTimestamp: self.initialTimestamp, navigateToDay: { [weak self] timestamp in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
@ -1635,12 +1635,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
return nil
|
||||
}
|
||||
let mappedPoint = self.view.convert(point, to: self.scrollNode.view)
|
||||
var maybePassthrough = false
|
||||
var maybePassthrough: ContextController.HandledTouchEvent?
|
||||
if let maybeContentNode = self.contentContainerNode.contentNode {
|
||||
switch maybeContentNode {
|
||||
case .reference:
|
||||
if let controller = self.getController() as? ContextController {
|
||||
maybePassthrough = controller.passthroughTouchEvents
|
||||
if let controller = self.getController() as? ContextController, let passthroughTouchEvent = controller.passthroughTouchEvent {
|
||||
maybePassthrough = passthroughTouchEvent(self.view, point)
|
||||
}
|
||||
case let .extracted(contentParentNode, _):
|
||||
if case let .extracted(source) = self.source {
|
||||
@ -1681,9 +1681,17 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
return self.actionsContainerNode.hitTest(self.view.convert(point, to: self.actionsContainerNode.view), with: event)
|
||||
}
|
||||
|
||||
if maybePassthrough {
|
||||
self.getController()?.dismiss(completion: nil)
|
||||
return nil
|
||||
if let maybePassthrough = maybePassthrough {
|
||||
switch maybePassthrough {
|
||||
case .ignore:
|
||||
break
|
||||
case let .dismiss(consume):
|
||||
self.getController()?.dismiss(completion: nil)
|
||||
|
||||
if !consume {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.dismissNode.view
|
||||
@ -1847,7 +1855,12 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
public var useComplexItemsTransitionAnimation = false
|
||||
public var immediateItemsTransitionAnimation = false
|
||||
|
||||
public var passthroughTouchEvents = false
|
||||
public enum HandledTouchEvent {
|
||||
case ignore
|
||||
case dismiss(consume: Bool)
|
||||
}
|
||||
|
||||
public var passthroughTouchEvent: ((UIView, CGPoint) -> HandledTouchEvent)?
|
||||
|
||||
private var shouldBeDismissedDisposable: Disposable?
|
||||
|
||||
|
@ -7,6 +7,7 @@ import TinyThumbnail
|
||||
import Display
|
||||
import FastBlur
|
||||
import MozjpegBinding
|
||||
import Accelerate
|
||||
|
||||
private func generateBlurredThumbnail(image: UIImage) -> UIImage? {
|
||||
let thumbnailContextSize = CGSize(width: 32.0, height: 32.0)
|
||||
@ -28,16 +29,33 @@ private func storeImage(context: DrawingContext, to path: String) -> UIImage? {
|
||||
guard let file = ManagedFile(queue: nil, path: path, mode: .readwrite) else {
|
||||
return nil
|
||||
}
|
||||
var header: UInt32 = 0xcaf1
|
||||
var header: UInt32 = 0xcaf2
|
||||
let _ = file.write(&header, count: 4)
|
||||
var width: UInt16 = UInt16(context.size.width)
|
||||
let _ = file.write(&width, count: 2)
|
||||
var height: UInt16 = UInt16(context.size.height)
|
||||
let _ = file.write(&height, count: 2)
|
||||
var bytesPerRow: UInt16 = UInt16(context.bytesPerRow)
|
||||
let _ = file.write(&bytesPerRow, count: 2)
|
||||
|
||||
let _ = file.write(context.bytes, count: context.length)
|
||||
var source = vImage_Buffer()
|
||||
source.width = UInt(context.size.width)
|
||||
source.height = UInt(context.size.height)
|
||||
source.rowBytes = context.bytesPerRow
|
||||
source.data = context.bytes
|
||||
|
||||
var target = vImage_Buffer()
|
||||
target.width = UInt(context.size.width)
|
||||
target.height = UInt(context.size.height)
|
||||
target.rowBytes = Int(context.size.width) * 2
|
||||
let targetLength = Int(target.height) * target.rowBytes
|
||||
let targetData = malloc(targetLength)!
|
||||
defer {
|
||||
free(targetData)
|
||||
}
|
||||
target.data = targetData
|
||||
|
||||
vImageConvert_BGRA8888toRGB565(&source, &target, vImage_Flags(kvImageDoNotTile))
|
||||
|
||||
let _ = file.write(targetData, count: targetLength)
|
||||
|
||||
return context.generateImage()
|
||||
} else {
|
||||
@ -91,6 +109,37 @@ private func loadImage(data: Data) -> UIImage? {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else if header == 0xcaf2 {
|
||||
var width: UInt16 = 0
|
||||
withUnsafeMutableBytes(of: &width, { width in
|
||||
data.copyBytes(to: width.baseAddress!.assumingMemoryBound(to: UInt8.self), from: 4 ..< (4 + 2))
|
||||
})
|
||||
var height: UInt16 = 0
|
||||
withUnsafeMutableBytes(of: &height, { height in
|
||||
data.copyBytes(to: height.baseAddress!.assumingMemoryBound(to: UInt8.self), from: (4 + 2) ..< (4 + 2 + 2))
|
||||
})
|
||||
|
||||
return data.withUnsafeBytes { data -> UIImage? in
|
||||
let sourceBytes = data.baseAddress!
|
||||
|
||||
var source = vImage_Buffer()
|
||||
source.width = UInt(width)
|
||||
source.height = UInt(height)
|
||||
source.rowBytes = Int(width * 2)
|
||||
source.data = UnsafeMutableRawPointer(mutating: sourceBytes.advanced(by: 4 + 2 + 2))
|
||||
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: true, clear: false)
|
||||
|
||||
var target = vImage_Buffer()
|
||||
target.width = UInt(width)
|
||||
target.height = UInt(height)
|
||||
target.rowBytes = context.bytesPerRow
|
||||
target.data = context.bytes
|
||||
|
||||
vImageConvert_RGB565toBGRA8888(0xff, &source, &target, vImage_Flags(kvImageDoNotTile))
|
||||
|
||||
return context.generateImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,34 +230,85 @@ public final class DirectMediaImageCache {
|
||||
}
|
||||
}
|
||||
|
||||
public func getImage(message: Message, media: Media, width: Int) -> GetMediaResult? {
|
||||
var immediateThumbnailData: Data?
|
||||
var resource: MediaResourceReference?
|
||||
if let image = media as? TelegramMediaImage {
|
||||
immediateThumbnailData = image.immediateThumbnailData
|
||||
resource = self.getResource(message: message, image: image)
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
immediateThumbnailData = file.immediateThumbnailData
|
||||
resource = self.getResource(message: message, file: file)
|
||||
}
|
||||
|
||||
if let resource = resource {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .square(width: width)))), let image = loadImage(data: data) {
|
||||
return GetMediaResult(image: image, loadSignal: nil)
|
||||
public func getImage(message: Message, media: Media, width: Int, synchronous: Bool) -> GetMediaResult? {
|
||||
if synchronous {
|
||||
var immediateThumbnailData: Data?
|
||||
var resource: MediaResourceReference?
|
||||
if let image = media as? TelegramMediaImage {
|
||||
immediateThumbnailData = image.immediateThumbnailData
|
||||
resource = self.getResource(message: message, image: image)
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
immediateThumbnailData = file.immediateThumbnailData
|
||||
resource = self.getResource(message: message, file: file)
|
||||
}
|
||||
|
||||
var blurredImage: UIImage?
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .blurredThumbnail))), let image = loadImage(data: data) {
|
||||
blurredImage = image
|
||||
} else if let data = immediateThumbnailData.flatMap(decodeTinyThumbnail), let image = loadImage(data: data) {
|
||||
if let blurredImageValue = generateBlurredThumbnail(image: image) {
|
||||
blurredImage = blurredImageValue
|
||||
if let resource = resource {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .square(width: width)))), let image = loadImage(data: data) {
|
||||
return GetMediaResult(image: image, loadSignal: nil)
|
||||
}
|
||||
|
||||
var blurredImage: UIImage?
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .blurredThumbnail))), let image = loadImage(data: data) {
|
||||
blurredImage = image
|
||||
} else if let data = immediateThumbnailData.flatMap(decodeTinyThumbnail), let image = loadImage(data: data) {
|
||||
if let blurredImageValue = generateBlurredThumbnail(image: image) {
|
||||
blurredImage = blurredImageValue
|
||||
}
|
||||
}
|
||||
|
||||
return GetMediaResult(image: blurredImage, loadSignal: self.getLoadSignal(resource: resource, width: width))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return GetMediaResult(image: nil, loadSignal: Signal { subscriber in
|
||||
var immediateThumbnailData: Data?
|
||||
var resource: MediaResourceReference?
|
||||
if let image = media as? TelegramMediaImage {
|
||||
immediateThumbnailData = image.immediateThumbnailData
|
||||
resource = self.getResource(message: message, image: image)
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
immediateThumbnailData = file.immediateThumbnailData
|
||||
resource = self.getResource(message: message, file: file)
|
||||
}
|
||||
|
||||
if let resource = resource {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .square(width: width)))), let image = loadImage(data: data) {
|
||||
subscriber.putNext(image)
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
|
||||
var blurredImage: UIImage?
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .blurredThumbnail))), let image = loadImage(data: data) {
|
||||
blurredImage = image
|
||||
} else if let data = immediateThumbnailData.flatMap(decodeTinyThumbnail), let image = loadImage(data: data) {
|
||||
if let blurredImageValue = generateBlurredThumbnail(image: image) {
|
||||
blurredImage = blurredImageValue
|
||||
}
|
||||
}
|
||||
|
||||
if let blurredImage = blurredImage {
|
||||
subscriber.putNext(blurredImage)
|
||||
}
|
||||
|
||||
if let signal = self.getLoadSignal(resource: resource, width: width) {
|
||||
return signal.start(next: subscriber.putNext, completed: subscriber.putCompletion)
|
||||
} else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
} else {
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
return GetMediaResult(image: blurredImage, loadSignal: self.getLoadSignal(resource: resource, width: width))
|
||||
} else {
|
||||
return nil
|
||||
|> runOn(.concurrentDefaultQueue()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,14 @@ private final class MediaPlayerNodeLayerNullAction: NSObject, CAAction {
|
||||
}
|
||||
|
||||
private final class MediaPlayerNodeLayer: AVSampleBufferDisplayLayer {
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func action(forKey event: String) -> CAAction? {
|
||||
return MediaPlayerNodeLayerNullAction()
|
||||
}
|
||||
|
@ -380,9 +380,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.itemSpacing = 1.0
|
||||
|
||||
let width = containerLayout.size.width
|
||||
let baseItemWidth = floor(min(150.0, width / 3.0))
|
||||
let unclippedItemWidth = (CGFloat(zoomLevel.rawValue) / 100.0) * baseItemWidth
|
||||
let itemsPerRow = floor(width / unclippedItemWidth)
|
||||
let itemsPerRow = CGFloat(zoomLevel.rawValue)
|
||||
self.itemsPerRow = Int(itemsPerRow)
|
||||
let itemSize = floorToScreenPixels((width - (self.itemSpacing * CGFloat(self.itemsPerRow - 1))) / itemsPerRow)
|
||||
self.itemSize = CGSize(width: itemSize, height: itemSize)
|
||||
@ -890,6 +888,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
private final class ViewportTransition: ASDisplayNode {
|
||||
struct InteractiveState {
|
||||
var anchorLocation: CGPoint
|
||||
var initialScale: CGFloat
|
||||
var targetScale: CGFloat
|
||||
}
|
||||
@ -927,11 +926,13 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.currentProgress = progress
|
||||
|
||||
if let fromItem = self.fromViewport.anchorItem(at: fromAnchorFrame.center), 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) {
|
||||
toAnchorFrame.origin.y = toFrame.midY
|
||||
toAnchorFrame.origin.x = toFrame.midX
|
||||
toAnchorFrame.size.width = 0.0
|
||||
}
|
||||
@ -1004,6 +1005,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
private var currentViewportTransition: ViewportTransition?
|
||||
private let scrollingArea: SparseItemGridScrollingArea
|
||||
|
||||
private var initialZoomLevel: ZoomLevel?
|
||||
|
||||
private var isLoadingHole: Bool = false
|
||||
private let loadingHoleDisposable = MetaDisposable()
|
||||
|
||||
@ -1070,7 +1073,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
//print("progress: \(progress), scale: \(scale), initial: \(interactiveState.initialScale), target: \(interactiveState.targetScale)")
|
||||
if progress < 0.0 || progress > 1.0 {
|
||||
let boundaryViewport = progress > 1.0 ? currentViewportTransition.toViewport : currentViewportTransition.fromViewport
|
||||
let zoomLevels = self.availableZoomLevels(startingAt: boundaryViewport.zoomLevel)
|
||||
let zoomLevels = self.availableZoomLevels(width: containerLayout.size.width, startingAt: boundaryViewport.zoomLevel)
|
||||
|
||||
let isZoomingIn = interactiveState.targetScale > interactiveState.initialScale
|
||||
var nextZoomLevel: ZoomLevel?
|
||||
@ -1079,7 +1082,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
if isZoomingIn {
|
||||
if progress > 1.0 {
|
||||
nextZoomLevel = zoomLevels.increment
|
||||
nextScale = startScale * 2.0
|
||||
nextScale = startScale * 1.5
|
||||
} else {
|
||||
nextZoomLevel = zoomLevels.decrement
|
||||
nextScale = startScale * 0.5
|
||||
@ -1090,14 +1093,23 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
nextScale = startScale * 0.5
|
||||
} else {
|
||||
nextZoomLevel = zoomLevels.increment
|
||||
nextScale = startScale * 2.0
|
||||
nextScale = startScale * 1.5
|
||||
}
|
||||
}
|
||||
|
||||
if let nextZoomLevel = nextZoomLevel, let anchorItemFrame = boundaryViewport.frameForItem(at: currentViewportTransition.anchorItemIndex) {
|
||||
let anchorLocation = interactiveState.anchorLocation
|
||||
|
||||
let nextAnchorItemIndex: Int
|
||||
if let anchorItem = boundaryViewport.anchorItem(at: anchorLocation) {
|
||||
nextAnchorItemIndex = anchorItem.index
|
||||
} else {
|
||||
nextAnchorItemIndex = currentViewportTransition.anchorItemIndex
|
||||
}
|
||||
|
||||
if let nextZoomLevel = nextZoomLevel, let anchorItemFrame = boundaryViewport.frameForItem(at: nextAnchorItemIndex) {
|
||||
replacedTransition = true
|
||||
|
||||
let restoreScrollPosition: (y: CGFloat, index: Int)? = (anchorItemFrame.minY, currentViewportTransition.anchorItemIndex)
|
||||
let restoreScrollPosition: (y: CGFloat, index: Int)? = (anchorItemFrame.minY, nextAnchorItemIndex)
|
||||
|
||||
let nextViewport = Viewport(zoomLevel: nextZoomLevel, maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
guard let strongSelf = self else {
|
||||
@ -1111,11 +1123,14 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
self.currentViewportTransition?.removeFromSupernode()
|
||||
|
||||
let currentViewportTransition = ViewportTransition(interactiveState: ViewportTransition.InteractiveState(initialScale: startScale, targetScale: nextScale), layout: containerLayout, anchorItemIndex: currentViewportTransition.anchorItemIndex, from: boundaryViewport, to: nextViewport)
|
||||
let nextInteractiveState = ViewportTransition.InteractiveState(anchorLocation: anchorLocation, initialScale: startScale, targetScale: nextScale)
|
||||
let currentViewportTransition = ViewportTransition(interactiveState: nextInteractiveState, layout: containerLayout, anchorItemIndex: currentViewportTransition.anchorItemIndex, from: boundaryViewport, to: nextViewport)
|
||||
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||
self.insertSubnode(currentViewportTransition, belowSubnode: self.scrollingArea)
|
||||
self.currentViewportTransition = currentViewportTransition
|
||||
currentViewportTransition.update(progress: progress, transition: .immediate, completion: {})
|
||||
|
||||
let nextProgress = (scale - nextInteractiveState.initialScale) / (nextInteractiveState.targetScale - nextInteractiveState.initialScale)
|
||||
currentViewportTransition.update(progress: nextProgress, transition: .immediate, completion: {})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1131,13 +1146,13 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
nextZoomLevel = zoomLevels.decrement
|
||||
}
|
||||
if let previousViewport = self.currentViewport, let nextZoomLevel = nextZoomLevel {
|
||||
let interactiveState = ViewportTransition.InteractiveState(initialScale: 1.0, targetScale: scale > 1.0 ? 2.0 : 0.5)
|
||||
let anchorLocation = recognizer.location(in: self.view)
|
||||
|
||||
let interactiveState = ViewportTransition.InteractiveState(anchorLocation: anchorLocation, initialScale: 1.0, targetScale: scale > 1.0 ? scale * 1.5 : scale * 0.5)
|
||||
|
||||
var progress = (scale - interactiveState.initialScale) / (interactiveState.targetScale - interactiveState.initialScale)
|
||||
progress = max(0.0, min(1.0, progress))
|
||||
|
||||
let anchorLocation = recognizer.location(in: self.view)
|
||||
|
||||
if let anchorItem = previousViewport.anchorItem(at: anchorLocation), let anchorItemFrame = previousViewport.frameForItem(at: anchorItem.index) {
|
||||
let restoreScrollPosition: (y: CGFloat, index: Int)? = (anchorItemFrame.minY, anchorItem.index)
|
||||
let anchorItemIndex = anchorItem.index
|
||||
@ -1207,7 +1222,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.pinchRecognizer?.isEnabled = fixedItemHeight == nil
|
||||
|
||||
if self.currentViewport == nil {
|
||||
let currentViewport = Viewport(zoomLevel: ZoomLevel(rawValue: 100), maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
let currentViewport = Viewport(zoomLevel: self.initialZoomLevel ?? ZoomLevel(rawValue: 3), maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1252,17 +1267,17 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
guard let currentViewport = self.currentViewport else {
|
||||
return (nil, nil)
|
||||
}
|
||||
return self.availableZoomLevels(startingAt: currentViewport.zoomLevel)
|
||||
guard let containerLayout = self.containerLayout else {
|
||||
return (nil, nil)
|
||||
}
|
||||
return self.availableZoomLevels(width: containerLayout.size.width, startingAt: currentViewport.zoomLevel)
|
||||
}
|
||||
|
||||
private func availableZoomLevels(startingAt zoomLevel: ZoomLevel) -> (decrement: ZoomLevel?, increment: ZoomLevel?) {
|
||||
let zoomLevels: [ZoomLevel] = [
|
||||
ZoomLevel(rawValue: 25),
|
||||
ZoomLevel(rawValue: 40),
|
||||
ZoomLevel(rawValue: 75),
|
||||
ZoomLevel(rawValue: 100),
|
||||
ZoomLevel(rawValue: 150)
|
||||
]
|
||||
private func availableZoomLevels(width: CGFloat, startingAt zoomLevel: ZoomLevel) -> (decrement: ZoomLevel?, increment: ZoomLevel?) {
|
||||
var zoomLevels: [ZoomLevel] = []
|
||||
for i in (2 ... 12).reversed() {
|
||||
zoomLevels.append(ZoomLevel(rawValue: i))
|
||||
}
|
||||
if let index = zoomLevels.firstIndex(of: zoomLevel) {
|
||||
return (index == 0 ? nil : zoomLevels[index - 1], index == (zoomLevels.count - 1) ? nil : zoomLevels[index + 1])
|
||||
} else {
|
||||
@ -1272,6 +1287,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
public func setZoomLevel(level: ZoomLevel) {
|
||||
guard let previousViewport = self.currentViewport else {
|
||||
self.initialZoomLevel = level
|
||||
return
|
||||
}
|
||||
if self.currentViewportTransition != nil {
|
||||
|
@ -680,6 +680,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
}
|
||||
|
||||
strongSelf.updateActivityTimer(isScrolling: false)
|
||||
strongSelf.dismissLineTooltip()
|
||||
},
|
||||
ended: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
@ -878,17 +879,21 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
transition.updateAlpha(layer: strongSelf.dateIndicator.layer, alpha: 0.0)
|
||||
transition.updateAlpha(layer: strongSelf.lineIndicator.layer, alpha: 0.0)
|
||||
|
||||
if let lineTooltip = strongSelf.lineTooltip {
|
||||
strongSelf.lineTooltip = nil
|
||||
lineTooltip.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak lineTooltip] _ in
|
||||
lineTooltip?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
strongSelf.dismissLineTooltip()
|
||||
}, queue: .mainQueue())
|
||||
self.activityTimer?.start()
|
||||
}
|
||||
}
|
||||
|
||||
private func dismissLineTooltip() {
|
||||
if let lineTooltip = self.lineTooltip {
|
||||
self.lineTooltip = nil
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak lineTooltip] _ in
|
||||
lineTooltip?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func displayTooltipOnFirstScroll() {
|
||||
guard let displayTooltip = self.displayTooltip else {
|
||||
return
|
||||
|
@ -336,7 +336,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-842824308] = { return Api.account.WallPapers.parse_wallPapers($0) }
|
||||
dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) }
|
||||
dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) }
|
||||
dict[2014782332] = { return Api.messages.SearchResultsRawMessages.parse_searchResultsRawMessages($0) }
|
||||
dict[-2032041631] = { return Api.Poll.parse_poll($0) }
|
||||
dict[-1195615476] = { return Api.InputNotifyPeer.parse_inputNotifyPeer($0) }
|
||||
dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) }
|
||||
@ -837,7 +836,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) }
|
||||
dict[-519864430] = { return Api.MessageAction.parse_messageActionChatMigrateTo($0) }
|
||||
dict[-365344535] = { return Api.MessageAction.parse_messageActionChannelMigrateFrom($0) }
|
||||
dict[-339958837] = { return Api.MessageAction.parse_messageActionChatJoinedByRequest($0) }
|
||||
dict[-1799538451] = { return Api.MessageAction.parse_messageActionPinMessage($0) }
|
||||
dict[-1615153660] = { return Api.MessageAction.parse_messageActionHistoryClear($0) }
|
||||
dict[-1834538890] = { return Api.MessageAction.parse_messageActionGameScore($0) }
|
||||
@ -856,6 +854,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1441072131] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) }
|
||||
dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) }
|
||||
dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) }
|
||||
dict[-339958837] = { return Api.MessageAction.parse_messageActionChatJoinedByRequest($0) }
|
||||
dict[1399245077] = { return Api.PhoneCall.parse_phoneCallEmpty($0) }
|
||||
dict[-987599081] = { return Api.PhoneCall.parse_phoneCallWaiting($0) }
|
||||
dict[347139340] = { return Api.PhoneCall.parse_phoneCallRequested($0) }
|
||||
@ -1151,8 +1150,6 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.InputTheme:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.SearchResultsRawMessages:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.Poll:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.InputNotifyPeer:
|
||||
|
@ -477,56 +477,6 @@ public struct messages {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum SearchResultsRawMessages: TypeConstructorDescription {
|
||||
case searchResultsRawMessages(msgIds: [Int32], msgDates: [Int32])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .searchResultsRawMessages(let msgIds, let msgDates):
|
||||
if boxed {
|
||||
buffer.appendInt32(2014782332)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(msgIds.count))
|
||||
for item in msgIds {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(msgDates.count))
|
||||
for item in msgDates {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .searchResultsRawMessages(let msgIds, let msgDates):
|
||||
return ("searchResultsRawMessages", [("msgIds", msgIds), ("msgDates", msgDates)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_searchResultsRawMessages(_ reader: BufferReader) -> SearchResultsRawMessages? {
|
||||
var _1: [Int32]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||
}
|
||||
var _2: [Int32]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.SearchResultsRawMessages.searchResultsRawMessages(msgIds: _1!, msgDates: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ExportedChatInvites: TypeConstructorDescription {
|
||||
case exportedChatInvites(count: Int32, invites: [Api.ExportedChatInvite], users: [Api.User])
|
||||
|
@ -21089,7 +21089,6 @@ public extension Api {
|
||||
case messageActionChannelCreate(title: String)
|
||||
case messageActionChatMigrateTo(channelId: Int64)
|
||||
case messageActionChannelMigrateFrom(title: String, chatId: Int64)
|
||||
case messageActionChatJoinedByRequest
|
||||
case messageActionPinMessage
|
||||
case messageActionHistoryClear
|
||||
case messageActionGameScore(gameId: Int64, score: Int32)
|
||||
@ -21108,6 +21107,7 @@ public extension Api {
|
||||
case messageActionSetMessagesTTL(period: Int32)
|
||||
case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32)
|
||||
case messageActionSetChatTheme(emoticon: String)
|
||||
case messageActionChatJoinedByRequest
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -21186,12 +21186,6 @@ public extension Api {
|
||||
}
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
serializeInt64(chatId, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .messageActionChatJoinedByRequest:
|
||||
if boxed {
|
||||
buffer.appendInt32(-339958837)
|
||||
}
|
||||
|
||||
break
|
||||
case .messageActionPinMessage:
|
||||
if boxed {
|
||||
@ -21330,6 +21324,12 @@ public extension Api {
|
||||
buffer.appendInt32(-1434950843)
|
||||
}
|
||||
serializeString(emoticon, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .messageActionChatJoinedByRequest:
|
||||
if boxed {
|
||||
buffer.appendInt32(-339958837)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -21358,8 +21358,6 @@ public extension Api {
|
||||
return ("messageActionChatMigrateTo", [("channelId", channelId)])
|
||||
case .messageActionChannelMigrateFrom(let title, let chatId):
|
||||
return ("messageActionChannelMigrateFrom", [("title", title), ("chatId", chatId)])
|
||||
case .messageActionChatJoinedByRequest:
|
||||
return ("messageActionChatJoinedByRequest", [])
|
||||
case .messageActionPinMessage:
|
||||
return ("messageActionPinMessage", [])
|
||||
case .messageActionHistoryClear:
|
||||
@ -21396,6 +21394,8 @@ public extension Api {
|
||||
return ("messageActionGroupCallScheduled", [("call", call), ("scheduleDate", scheduleDate)])
|
||||
case .messageActionSetChatTheme(let emoticon):
|
||||
return ("messageActionSetChatTheme", [("emoticon", emoticon)])
|
||||
case .messageActionChatJoinedByRequest:
|
||||
return ("messageActionChatJoinedByRequest", [])
|
||||
}
|
||||
}
|
||||
|
||||
@ -21516,9 +21516,6 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_messageActionChatJoinedByRequest(_ reader: BufferReader) -> MessageAction? {
|
||||
return Api.MessageAction.messageActionChatJoinedByRequest
|
||||
}
|
||||
public static func parse_messageActionPinMessage(_ reader: BufferReader) -> MessageAction? {
|
||||
return Api.MessageAction.messageActionPinMessage
|
||||
}
|
||||
@ -21763,6 +21760,9 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_messageActionChatJoinedByRequest(_ reader: BufferReader) -> MessageAction? {
|
||||
return Api.MessageAction.messageActionChatJoinedByRequest
|
||||
}
|
||||
|
||||
}
|
||||
public enum PhoneCall: TypeConstructorDescription {
|
||||
|
@ -2106,13 +2106,15 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func deleteHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
|
||||
public static func deleteHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(469850889)
|
||||
buffer.appendInt32(-1332768214)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(maxId, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "messages.deleteHistory", parameters: [("flags", flags), ("peer", peer), ("maxId", maxId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(minDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(maxDate!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "messages.deleteHistory", parameters: [("flags", flags), ("peer", peer), ("maxId", maxId), ("minDate", minDate), ("maxDate", maxDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.AffectedHistory?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -4394,15 +4396,14 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func getSearchResultsCalendar(flags: Int32, peer: Api.InputPeer, filter: Api.MessagesFilter, offsetId: Int32, offsetDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.SearchResultsCalendar>) {
|
||||
public static func getSearchResultsCalendar(peer: Api.InputPeer, filter: Api.MessagesFilter, offsetId: Int32, offsetDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.SearchResultsCalendar>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1991714845)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(1240514025)
|
||||
peer.serialize(buffer, true)
|
||||
filter.serialize(buffer, true)
|
||||
serializeInt32(offsetId, buffer: buffer, boxed: false)
|
||||
serializeInt32(offsetDate, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "messages.getSearchResultsCalendar", parameters: [("flags", flags), ("peer", peer), ("filter", filter), ("offsetId", offsetId), ("offsetDate", offsetDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsCalendar? in
|
||||
return (FunctionDescription(name: "messages.getSearchResultsCalendar", parameters: [("peer", peer), ("filter", filter), ("offsetId", offsetId), ("offsetDate", offsetDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsCalendar? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.SearchResultsCalendar?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -4412,39 +4413,6 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func getSearchResultsRawMessages(peer: Api.InputPeer, filter: Api.MessagesFilter, offsetId: Int32, offsetDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.SearchResultsRawMessages>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1258852762)
|
||||
peer.serialize(buffer, true)
|
||||
filter.serialize(buffer, true)
|
||||
serializeInt32(offsetId, buffer: buffer, boxed: false)
|
||||
serializeInt32(offsetDate, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "messages.getSearchResultsRawMessages", parameters: [("peer", peer), ("filter", filter), ("offsetId", offsetId), ("offsetDate", offsetDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsRawMessages? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.SearchResultsRawMessages?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsRawMessages
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func hideChatJoinRequest(flags: Int32, peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(2145904661)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
peer.serialize(buffer, true)
|
||||
userId.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "messages.hideChatJoinRequest", parameters: [("flags", flags), ("peer", peer), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func getSearchResultsPositions(peer: Api.InputPeer, filter: Api.MessagesFilter, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.SearchResultsPositions>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1855292323)
|
||||
@ -4461,6 +4429,22 @@ public extension Api {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func hideChatJoinRequest(flags: Int32, peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(2145904661)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
peer.serialize(buffer, true)
|
||||
userId.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "messages.hideChatJoinRequest", parameters: [("flags", flags), ("peer", peer), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct channels {
|
||||
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
@ -7558,14 +7542,12 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func installTheme(flags: Int32, theme: Api.InputTheme?, format: String?, baseTheme: Api.BaseTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
public static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-953697477)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {theme!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeString(format!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {baseTheme!.serialize(buffer, true)}
|
||||
return (FunctionDescription(name: "account.installTheme", parameters: [("flags", flags), ("theme", theme), ("format", format), ("baseTheme", baseTheme)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
buffer.appendInt32(-229175188)
|
||||
theme.serialize(buffer, true)
|
||||
unsave.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "account.saveTheme", parameters: [("theme", theme), ("unsave", unsave)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -7575,26 +7557,14 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
|
||||
public static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.Themes>) {
|
||||
public static func installTheme(flags: Int32, theme: Api.InputTheme?, format: String?, baseTheme: Api.BaseTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-700916087)
|
||||
serializeInt64(hash, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.account.Themes?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.account.Themes
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-229175188)
|
||||
theme.serialize(buffer, true)
|
||||
unsave.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "account.saveTheme", parameters: [("theme", theme), ("unsave", unsave)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
buffer.appendInt32(-953697477)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {theme!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeString(format!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {baseTheme!.serialize(buffer, true)}
|
||||
return (FunctionDescription(name: "account.installTheme", parameters: [("flags", flags), ("theme", theme), ("format", format), ("baseTheme", baseTheme)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -7753,6 +7723,20 @@ public extension Api {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.Themes>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-700916087)
|
||||
serializeInt64(hash, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.account.Themes?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.account.Themes
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct langpack {
|
||||
public static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.LangPackDifference>) {
|
||||
|
@ -347,7 +347,7 @@ private func requestClearHistory(postbox: Postbox, network: Network, stateManage
|
||||
if case .forEveryone = type {
|
||||
flags |= 1 << 1
|
||||
}
|
||||
let signal = network.request(Api.functions.messages.deleteHistory(flags: flags, peer: inputPeer, maxId: maxId))
|
||||
let signal = network.request(Api.functions.messages.deleteHistory(flags: flags, peer: inputPeer, maxId: maxId, minDate: nil, maxDate: nil))
|
||||
|> map { result -> Api.messages.AffectedHistory? in
|
||||
return result
|
||||
}
|
||||
|
@ -301,6 +301,8 @@ public final class SparseMessageList {
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in updated })
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
|
||||
let _ = transaction.addMessages(parsedMessages, location: .Random)
|
||||
|
||||
var result: [Message] = []
|
||||
@ -632,15 +634,224 @@ public final class SparseMessageList {
|
||||
}
|
||||
}
|
||||
|
||||
/*public func loadPlaceholders(ids: [MessageId]) {
|
||||
self.impl.with { impl in
|
||||
impl.loadPlaceholders(ids: ids)
|
||||
}
|
||||
}*/
|
||||
|
||||
public func loadHole(anchor: MessageId, direction: LoadHoleDirection, completion: @escaping () -> Void) {
|
||||
self.impl.with { impl in
|
||||
impl.loadHole(anchor: anchor, direction: direction, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class SparseMessageCalendar {
|
||||
private final class Impl {
|
||||
struct InternalState {
|
||||
var nextRequestOffset: Int32?
|
||||
var minTimestamp: Int32?
|
||||
var messagesByDay: [Int32: Message]
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let peerId: PeerId
|
||||
private let messageTag: MessageTags
|
||||
|
||||
private var state: InternalState
|
||||
let statePromise = Promise<InternalState>()
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
private var isLoadingMore: Bool = false {
|
||||
didSet {
|
||||
self.isLoadingMorePromise.set(.single(self.isLoadingMore))
|
||||
}
|
||||
}
|
||||
|
||||
private let isLoadingMorePromise = Promise<Bool>(false)
|
||||
var isLoadingMoreSignal: Signal<Bool, NoError> {
|
||||
return self.isLoadingMorePromise.get()
|
||||
}
|
||||
|
||||
init(queue: Queue, account: Account, peerId: PeerId, messageTag: MessageTags) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
self.messageTag = messageTag
|
||||
|
||||
self.state = InternalState(nextRequestOffset: 0, minTimestamp: nil, messagesByDay: [:])
|
||||
self.statePromise.set(.single(self.state))
|
||||
|
||||
self.maybeLoadMore()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func maybeLoadMore() {
|
||||
if self.isLoadingMore {
|
||||
return
|
||||
}
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
private func loadMore() {
|
||||
guard let nextRequestOffset = self.state.nextRequestOffset else {
|
||||
return
|
||||
}
|
||||
|
||||
self.isLoadingMore = true
|
||||
|
||||
struct LoadResult {
|
||||
var messagesByDay: [Int32: Message]
|
||||
var nextOffset: Int32?
|
||||
var minMessageId: MessageId?
|
||||
var minTimestamp: Int32?
|
||||
}
|
||||
|
||||
let account = self.account
|
||||
let peerId = self.peerId
|
||||
let messageTag = self.messageTag
|
||||
self.disposable.set((self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|> mapToSignal { inputPeer -> Signal<LoadResult, NoError> in
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .single(LoadResult(messagesByDay: [:], nextOffset: nil, minMessageId: nil, minTimestamp: nil))
|
||||
}
|
||||
guard let messageFilter = messageFilterForTagMask(messageTag) else {
|
||||
return .single(LoadResult(messagesByDay: [:], nextOffset: nil, minMessageId: nil, minTimestamp: nil))
|
||||
}
|
||||
return self.account.network.request(Api.functions.messages.getSearchResultsCalendar(peer: inputPeer, filter: messageFilter, offsetId: nextRequestOffset, offsetDate: 0))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.SearchResultsCalendar?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<LoadResult, NoError> in
|
||||
return account.postbox.transaction { transaction -> LoadResult in
|
||||
guard let result = result else {
|
||||
return LoadResult(messagesByDay: [:], nextOffset: nil, minMessageId: nil, minTimestamp: nil)
|
||||
}
|
||||
|
||||
switch result {
|
||||
case let .searchResultsCalendar(_, _, minDate, minMsgId, _, periods, messages, chats, users):
|
||||
var parsedMessages: [StoreMessage] = []
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(groupOrChannel)
|
||||
}
|
||||
}
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
peerPresences[telegramUser.id] = presence
|
||||
}
|
||||
}
|
||||
|
||||
for message in messages {
|
||||
if let parsedMessage = StoreMessage(apiMessage: message) {
|
||||
parsedMessages.append(parsedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated in updated })
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
|
||||
let _ = transaction.addMessages(parsedMessages, location: .Random)
|
||||
|
||||
var minMessageId: Int32?
|
||||
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)) {
|
||||
messagesByDay[date] = message
|
||||
}
|
||||
if let minMessageIdValue = minMessageId {
|
||||
if minMsgId < minMessageIdValue {
|
||||
minMessageId = minMsgId
|
||||
}
|
||||
} else {
|
||||
minMessageId = minMsgId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LoadResult(messagesByDay: messagesByDay, nextOffset: minMessageId, minMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: minMsgId), minTimestamp: minDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let minTimestamp = result.minTimestamp {
|
||||
strongSelf.state.minTimestamp = minTimestamp
|
||||
}
|
||||
strongSelf.state.nextRequestOffset = result.nextOffset
|
||||
|
||||
for (timestamp, message) in result.messagesByDay {
|
||||
strongSelf.state.messagesByDay[timestamp] = message
|
||||
}
|
||||
|
||||
strongSelf.statePromise.set(.single(strongSelf.state))
|
||||
strongSelf.isLoadingMore = false
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
public struct State {
|
||||
public var messagesByDay: [Int32: Message]
|
||||
public var minTimestamp: Int32?
|
||||
public var hasMore: Bool
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
private let impl: QueueLocalObject<Impl>
|
||||
|
||||
init(account: Account, peerId: PeerId, messageTag: MessageTags) {
|
||||
let queue = Queue()
|
||||
self.queue = queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return Impl(queue: queue, account: account, peerId: peerId, messageTag: messageTag)
|
||||
})
|
||||
}
|
||||
|
||||
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: { state in
|
||||
subscriber.putNext(State(
|
||||
messagesByDay: state.messagesByDay,
|
||||
minTimestamp: state.minTimestamp,
|
||||
hasMore: state.nextRequestOffset != nil
|
||||
))
|
||||
}))
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public var isLoadingMore: Signal<Bool, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.isLoadingMoreSignal.start(next: subscriber.putNext))
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func loadMore() {
|
||||
self.impl.with { impl in
|
||||
impl.maybeLoadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,6 +247,10 @@ public extension TelegramEngine {
|
||||
return SparseMessageList(account: self.account, peerId: peerId, messageTag: tag)
|
||||
}
|
||||
|
||||
public func sparseMessageCalendar(peerId: EnginePeer.Id, tag: EngineMessage.Tags) -> SparseMessageCalendar {
|
||||
return SparseMessageCalendar(account: self.account, peerId: peerId, messageTag: tag)
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -19,6 +19,7 @@ import QuartzCore
|
||||
import DirectMediaImageCache
|
||||
import ComponentFlow
|
||||
import TelegramNotices
|
||||
import TelegramUIPreferences
|
||||
|
||||
private final class FrameSequenceThumbnailNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
@ -704,8 +705,51 @@ private struct Month: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private let durationFont = Font.regular(12.0)
|
||||
|
||||
private final class DurationLayer: CALayer {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
self.contentsGravity = .topRight
|
||||
self.contentsScale = UIScreenScale
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
}
|
||||
|
||||
func update(duration: Int32) {
|
||||
let string = NSAttributedString(string: stringForDuration(duration), font: durationFont, textColor: .white)
|
||||
let bounds = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
let textSize = CGSize(width: ceil(bounds.width), height: ceil(bounds.height))
|
||||
let sideInset: CGFloat = 6.0
|
||||
let verticalInset: CGFloat = 2.0
|
||||
let image = generateImage(CGSize(width: textSize.width + sideInset * 2.0, height: textSize.height + verticalInset * 2.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor(white: 0.0, alpha: 0.5).cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.height, height: size.height)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - size.height, y: 0.0), size: CGSize(width: size.height, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(x: size.height / 2.0, y: 0.0), size: CGSize(width: size.width - size.height, height: size.height)))
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
UIGraphicsPushContext(context)
|
||||
string.draw(in: bounds.offsetBy(dx: sideInset, dy: verticalInset))
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
self.contents = image?.cgImage
|
||||
}
|
||||
}
|
||||
|
||||
private final class ItemLayer: CALayer, SparseItemGridLayer {
|
||||
var item: VisualMediaItem?
|
||||
var durationLayer: DurationLayer?
|
||||
var disposable: Disposable?
|
||||
|
||||
var hasContents: Bool = false
|
||||
@ -741,6 +785,22 @@ private final class ItemLayer: CALayer, SparseItemGridLayer {
|
||||
}
|
||||
}
|
||||
|
||||
func updateDuration(duration: Int32?) {
|
||||
if let duration = duration {
|
||||
if let durationLayer = self.durationLayer {
|
||||
durationLayer.update(duration: duration)
|
||||
} else {
|
||||
let durationLayer = DurationLayer()
|
||||
durationLayer.update(duration: duration)
|
||||
self.addSublayer(durationLayer)
|
||||
durationLayer.frame = CGRect(origin: CGPoint(x: self.bounds.width - 3.0, y: self.bounds.height - 3.0), size: CGSize())
|
||||
}
|
||||
} else if let durationLayer = self.durationLayer {
|
||||
self.durationLayer = nil
|
||||
durationLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
func unbind() {
|
||||
self.item = nil
|
||||
}
|
||||
@ -750,6 +810,9 @@ private final class ItemLayer: CALayer, SparseItemGridLayer {
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
if let durationLayer = self.durationLayer {
|
||||
durationLayer.frame = CGRect(origin: CGPoint(x: size.width - 3.0, y: size.height - 3.0), size: CGSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -959,8 +1022,9 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let selectedMedia = selectedMedia {
|
||||
if let result = directMediaImageCache.getImage(message: message, media: selectedMedia, width: imageWidthSpec) {
|
||||
if let result = directMediaImageCache.getImage(message: message, media: selectedMedia, width: imageWidthSpec, synchronous: synchronous) {
|
||||
if let image = result.image {
|
||||
layer.contents = image.cgImage
|
||||
layer.hasContents = true
|
||||
@ -989,6 +1053,12 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var duration: Int32?
|
||||
if layer.bounds.width > 80.0, let file = selectedMedia as? TelegramMediaFile {
|
||||
duration = file.duration
|
||||
}
|
||||
layer.updateDuration(duration: duration)
|
||||
}
|
||||
|
||||
layer.bind(item: item)
|
||||
@ -1084,6 +1154,14 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
init(_ value: SparseItemGrid.ZoomLevel) {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
var rawValue: Int32 {
|
||||
return Int32(self.value.rawValue)
|
||||
}
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
self.value = SparseItemGrid.ZoomLevel(rawValue: Int(rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
@ -1135,10 +1213,14 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
|
||||
private var animationTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private(set) var calendarSource: SparseMessageCalendar?
|
||||
private var listSource: SparseMessageList
|
||||
|
||||
var openCurrentDate: (() -> Void)?
|
||||
var paneDidScroll: (() -> Void)?
|
||||
|
||||
private let stateTag: MessageTags
|
||||
private var storedStateDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, contentType: ContentType) {
|
||||
self.context = context
|
||||
@ -1146,6 +1228,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
self.chatControllerInteraction = chatControllerInteraction
|
||||
self.contentType = contentType
|
||||
self.contentTypePromise = ValuePromise<ContentType>(contentType)
|
||||
self.stateTag = tagMaskForType(contentType)
|
||||
|
||||
self.itemGrid = SparseItemGrid()
|
||||
self.directMediaImageCache = DirectMediaImageCache(account: context.account)
|
||||
@ -1192,6 +1275,12 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
)
|
||||
|
||||
self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, tag: tagMaskForType(self.contentType))
|
||||
switch contentType {
|
||||
case .photoOrVideo, .photo, .video:
|
||||
self.calendarSource = self.context.engine.messages.sparseMessageCalendar(peerId: self.peerId, tag: tagMaskForType(self.contentType))
|
||||
default:
|
||||
self.calendarSource = nil
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
@ -1200,7 +1289,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if count < 1 {
|
||||
if count < 1 || true {
|
||||
//TODO:localize
|
||||
strongSelf.itemGrid.updateScrollingAreaTooltip(tooltip: SparseItemGridScrollingArea.DisplayTooltip(animation: "anim_infotip", text: "You can hold and move this bar for faster scrolling", completed: {
|
||||
guard let strongSelf = self else {
|
||||
@ -1310,6 +1399,14 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
self.itemInteraction.selectedMessageIds = chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
|
||||
|
||||
self.addSubnode(self.itemGrid)
|
||||
|
||||
self.storedStateDisposable = (visualMediaStoredState(postbox: context.account.postbox, peerId: peerId, messageTag: self.stateTag)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self, let value = value else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateZoomLevel(level: ZoomLevel(rawValue: value.zoomLevel))
|
||||
})
|
||||
|
||||
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false)
|
||||
|
||||
@ -1517,6 +1614,8 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
|
||||
func updateZoomLevel(level: ZoomLevel) {
|
||||
self.itemGrid.setZoomLevel(level: level.value)
|
||||
|
||||
let _ = updateVisualMediaStoredState(postbox: self.context.account.postbox, peerId: self.peerId, messageTag: self.stateTag, state: VisualMediaStoredState(zoomLevel: level.rawValue)).start()
|
||||
}
|
||||
|
||||
func ensureMessageIsVisible(id: MessageId) {
|
||||
@ -1528,39 +1627,46 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
self.isRequestingView = true
|
||||
var firstTime = true
|
||||
let queue = Queue()
|
||||
|
||||
self.listDisposable.set((self.listSource.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] list in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|> deliverOn(queue)).start(next: { [weak self] list in
|
||||
let timezoneOffset = Int32(TimeZone.current.secondsFromGMT())
|
||||
|
||||
var mappedItems: [SparseItemGrid.Item] = []
|
||||
var mappeHoles: [SparseItemGrid.HoleAnchor] = []
|
||||
for item in list.items {
|
||||
switch item.content {
|
||||
case let .message(message, _):
|
||||
mappedItems.append(VisualMediaItem(index: item.index, message: message, localMonthTimestamp: Month(localTimestamp: message.timestamp + timezoneOffset).packedValue))
|
||||
case let .placeholder(id, timestamp):
|
||||
mappeHoles.append(VisualMediaHoleAnchor(index: item.index, messageId: id, localMonthTimestamp: Month(localTimestamp: timestamp + timezoneOffset).packedValue))
|
||||
}
|
||||
}
|
||||
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let items = SparseItemGrid.Items(
|
||||
items: mappedItems,
|
||||
holeAnchors: mappeHoles,
|
||||
count: list.totalCount,
|
||||
itemBinding: strongSelf.itemGridBinding
|
||||
)
|
||||
|
||||
let currentSynchronous = synchronous && firstTime
|
||||
let currentReloadAtTop = reloadAtTop && firstTime
|
||||
firstTime = false
|
||||
strongSelf.updateHistory(items: items, synchronous: currentSynchronous, reloadAtTop: currentReloadAtTop)
|
||||
strongSelf.isRequestingView = false
|
||||
}
|
||||
let currentSynchronous = synchronous && firstTime
|
||||
let currentReloadAtTop = reloadAtTop && firstTime
|
||||
firstTime = false
|
||||
strongSelf.updateHistory(list: list, synchronous: currentSynchronous, reloadAtTop: currentReloadAtTop)
|
||||
strongSelf.isRequestingView = false
|
||||
}))
|
||||
}
|
||||
|
||||
private func updateHistory(list: SparseMessageList.State, synchronous: Bool, reloadAtTop: Bool) {
|
||||
let timezoneOffset = Int32(TimeZone.current.secondsFromGMT())
|
||||
|
||||
var mappedItems: [SparseItemGrid.Item] = []
|
||||
var mappeHoles: [SparseItemGrid.HoleAnchor] = []
|
||||
for item in list.items {
|
||||
switch item.content {
|
||||
case let .message(message, _):
|
||||
mappedItems.append(VisualMediaItem(index: item.index, message: message, localMonthTimestamp: Month(localTimestamp: message.timestamp + timezoneOffset).packedValue))
|
||||
case let .placeholder(id, timestamp):
|
||||
mappeHoles.append(VisualMediaHoleAnchor(index: item.index, messageId: id, localMonthTimestamp: Month(localTimestamp: timestamp + timezoneOffset).packedValue))
|
||||
}
|
||||
}
|
||||
|
||||
self.items = SparseItemGrid.Items(
|
||||
items: mappedItems,
|
||||
holeAnchors: mappeHoles,
|
||||
count: list.totalCount,
|
||||
itemBinding: self.itemGridBinding
|
||||
)
|
||||
private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) {
|
||||
self.items = items
|
||||
|
||||
if let (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams {
|
||||
var gridSnapshot: UIView?
|
||||
@ -1855,3 +1961,41 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
return (levels.decrement.flatMap(ZoomLevel.init), levels.increment.flatMap(ZoomLevel.init))
|
||||
}
|
||||
}
|
||||
|
||||
final class VisualMediaStoredState: Codable {
|
||||
let zoomLevel: Int32
|
||||
|
||||
public init(zoomLevel: Int32) {
|
||||
self.zoomLevel = zoomLevel
|
||||
}
|
||||
}
|
||||
|
||||
func visualMediaStoredState(postbox: Postbox, peerId: PeerId, messageTag: MessageTags) -> Signal<VisualMediaStoredState?, NoError> {
|
||||
return postbox.transaction { transaction -> VisualMediaStoredState? in
|
||||
let key = ValueBoxKey(length: 8 + 4)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
key.setUInt32(8, value: messageTag.rawValue)
|
||||
if let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.visualMediaStoredState, key: key))?.get(VisualMediaStoredState.self) {
|
||||
return entry
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let collectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 25, highWaterItemCount: 50)
|
||||
|
||||
func updateVisualMediaStoredState(postbox: Postbox, peerId: PeerId, messageTag: MessageTags, state: VisualMediaStoredState?) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
let key = ValueBoxKey(length: 8 + 4)
|
||||
key.setInt64(0, value: peerId.toInt64())
|
||||
key.setUInt32(8, value: messageTag.rawValue)
|
||||
|
||||
let id = ItemCacheEntryId(collectionId: ApplicationSpecificItemCacheCollectionId.visualMediaStoredState, key: key)
|
||||
if let state = state, let entry = CodableEntry(state) {
|
||||
transaction.putItemCacheEntry(id: id, entry: entry, collectionSpec: collectionSpec)
|
||||
} else {
|
||||
transaction.removeItemCacheEntry(id: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6130,7 +6130,31 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
pane.updateContentType(contentType: updatedContentType)
|
||||
})))
|
||||
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(items: items)), gesture: nil)
|
||||
contextController.passthroughTouchEvents = true
|
||||
contextController.passthroughTouchEvent = { [weak self] sourceView, point in
|
||||
guard let strongSelf = self else {
|
||||
return .ignore
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var testView: UIView? = localResult
|
||||
while true {
|
||||
if let testViewValue = testView {
|
||||
if testViewValue.asyncdisplaykit_node is PeerInfoVisualMediaPaneNode {
|
||||
return .dismiss(consume: false)
|
||||
} else {
|
||||
testView = testViewValue.superview
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return .dismiss(consume: true)
|
||||
}
|
||||
self.mediaGalleryContextMenu = contextController
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
@ -6138,11 +6162,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
private func openMediaCalendar() {
|
||||
var initialTimestamp = Int32(Date().timeIntervalSince1970)
|
||||
|
||||
if let pane = self.paneContainerNode.currentPane?.node as? PeerInfoVisualMediaPaneNode, let timestamp = pane.currentTopTimestamp() {
|
||||
initialTimestamp = timestamp
|
||||
guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoVisualMediaPaneNode, let timestamp = pane.currentTopTimestamp(), let calendarSource = pane.calendarSource else {
|
||||
return
|
||||
}
|
||||
initialTimestamp = timestamp
|
||||
|
||||
self.controller?.push(CalendarMessageScreen(context: self.context, peerId: self.peerId, initialTimestamp: initialTimestamp, navigateToDay: { [weak self] c, timestamp in
|
||||
self.controller?.push(CalendarMessageScreen(context: self.context, peerId: self.peerId, calendarSource: calendarSource, initialTimestamp: initialTimestamp, navigateToDay: { [weak self] c, timestamp in
|
||||
guard let strongSelf = self else {
|
||||
c.dismiss()
|
||||
return
|
||||
|
@ -64,6 +64,7 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
|
||||
case cachedWallpapers = 2
|
||||
case mediaPlaybackStoredState = 3
|
||||
case cachedGeocodes = 4
|
||||
case visualMediaStoredState = 5
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificItemCacheCollectionId {
|
||||
@ -72,6 +73,7 @@ public struct ApplicationSpecificItemCacheCollectionId {
|
||||
public static let cachedWallpapers = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedWallpapers.rawValue)
|
||||
public static let mediaPlaybackStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaPlaybackStoredState.rawValue)
|
||||
public static let cachedGeocodes = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.cachedGeocodes.rawValue)
|
||||
public static let visualMediaStoredState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.visualMediaStoredState.rawValue)
|
||||
}
|
||||
|
||||
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
||||
@ -79,6 +81,7 @@ private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
||||
case wallpaperSearchRecentQueries = 1
|
||||
case settingsSearchRecentItems = 2
|
||||
case localThemes = 3
|
||||
case visualMediaStoredState = 4
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificOrderedItemListCollectionId {
|
||||
|
Loading…
x
Reference in New Issue
Block a user