Shared media improvements

This commit is contained in:
Ali 2021-10-22 22:50:50 +04:00
parent b5d1b377f2
commit 73181eabd5
16 changed files with 797 additions and 243 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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