Swiftgram/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift
2021-10-08 23:03:22 +04:00

874 lines
33 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import TelegramPresentationData
import ComponentFlow
import PhotoResources
private final class MediaPreviewNode: ASDisplayNode {
private let context: AccountContext
private let message: EngineMessage
private let media: EngineMedia
private let imageNode: TransformImageNode
private var requestedImage: Bool = false
private var disposable: Disposable?
init(context: AccountContext, message: EngineMessage, media: EngineMedia) {
self.context = context
self.message = message
self.media = media
self.imageNode = TransformImageNode()
super.init()
self.addSubnode(self.imageNode)
}
deinit {
self.disposable?.dispose()
}
func updateLayout(size: CGSize, synchronousLoads: Bool) {
var dimensions = CGSize(width: 100.0, height: 100.0)
if case let .image(image) = self.media {
if let largest = largestImageRepresentation(image.representations) {
dimensions = largest.dimensions.cgSize
if !self.requestedImage {
self.requestedImage = true
let signal = mediaGridMessagePhoto(account: self.context.account, photoReference: .message(message: MessageReference(self.message._asMessage()), media: image), fullRepresentationSize: CGSize(width: 36.0, height: 36.0), synchronousLoad: synchronousLoads)
self.imageNode.setSignal(signal, attemptSynchronously: synchronousLoads)
}
}
} else if case let .file(file) = self.media {
if let mediaDimensions = file.dimensions {
dimensions = mediaDimensions.cgSize
if !self.requestedImage {
self.requestedImage = true
let signal = mediaGridMessageVideo(postbox: self.context.account.postbox, videoReference: .message(message: MessageReference(self.message._asMessage()), media: file), synchronousLoad: synchronousLoads, autoFetchFullSizeThumbnail: true, useMiniThumbnailIfAvailable: true)
self.imageNode.setSignal(signal, attemptSynchronously: synchronousLoads)
}
}
}
let makeLayout = self.imageNode.asyncLayout()
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
apply()
}
}
private func monthName(index: Int, strings: PresentationStrings) -> String {
switch index {
case 0:
return strings.Month_GenJanuary
case 1:
return strings.Month_GenFebruary
case 2:
return strings.Month_GenMarch
case 3:
return strings.Month_GenApril
case 4:
return strings.Month_GenMay
case 5:
return strings.Month_GenJune
case 6:
return strings.Month_GenJuly
case 7:
return strings.Month_GenAugust
case 8:
return strings.Month_GenSeptember
case 9:
return strings.Month_GenOctober
case 10:
return strings.Month_GenNovember
case 11:
return strings.Month_GenDecember
default:
return ""
}
}
private func dayName(index: Int, strings: PresentationStrings) -> String {
let _ = strings
//TODO:localize
switch index {
case 0:
return "M"
case 1:
return "T"
case 2:
return "W"
case 3:
return "T"
case 4:
return "F"
case 5:
return "S"
case 6:
return "S"
default:
return ""
}
}
private class Scroller: UIScrollView {
override init(frame: CGRect) {
super.init(frame: frame)
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.contentInsetAdjustmentBehavior = .never
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
}
private final class ImageCache: Equatable {
static func ==(lhs: ImageCache, rhs: ImageCache) -> Bool {
return lhs === rhs
}
private struct FilledCircle: Hashable {
var diameter: CGFloat
var color: UInt32
}
private var items: [AnyHashable: UIImage] = [:]
func filledCircle(diameter: CGFloat, color: UIColor) -> UIImage {
let key = AnyHashable(FilledCircle(diameter: diameter, color: color.argb))
if let image = self.items[key] {
return image
}
let image = generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
})!.stretchableImage(withLeftCapWidth: Int(diameter) / 2, topCapHeight: Int(diameter) / 2)
self.items[key] = image
return image
}
}
private final class DayComponent: Component {
typealias EnvironmentType = ImageCache
let title: String
let isCurrent: Bool
let isEnabled: Bool
let theme: PresentationTheme
let context: AccountContext
let media: DayMedia?
let action: () -> Void
init(
title: String,
isCurrent: Bool,
isEnabled: Bool,
theme: PresentationTheme,
context: AccountContext,
media: DayMedia?,
action: @escaping () -> Void
) {
self.title = title
self.isCurrent = isCurrent
self.isEnabled = isEnabled
self.theme = theme
self.context = context
self.media = media
self.action = action
}
static func ==(lhs: DayComponent, rhs: DayComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.isCurrent != rhs.isCurrent {
return false
}
if lhs.isEnabled != rhs.isEnabled {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.context !== rhs.context {
return false
}
if lhs.media != rhs.media {
return false
}
return true
}
final class View: UIView {
private let buttonNode: HighlightableButtonNode
private let highlightNode: ASImageNode
private let titleNode: ImmediateTextNode
private var mediaPreviewNode: MediaPreviewNode?
private var action: (() -> Void)?
private var currentMedia: DayMedia?
init() {
self.buttonNode = HighlightableButtonNode()
self.highlightNode = ASImageNode()
self.titleNode = ImmediateTextNode()
super.init(frame: CGRect())
self.buttonNode.addSubnode(self.highlightNode)
self.buttonNode.addSubnode(self.titleNode)
self.addSubnode(self.buttonNode)
self.buttonNode.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
preconditionFailure()
}
@objc private func pressed() {
self.action?()
}
func update(component: DayComponent, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
self.action = component.action
let shadowInset: CGFloat = 0.0
let diameter = min(availableSize.width, availableSize.height)
let imageCache = environment[ImageCache.self]
if component.media != nil {
self.highlightNode.image = imageCache.value.filledCircle(diameter: diameter, color: UIColor(white: 0.0, alpha: 0.2))
} else if component.isCurrent {
self.highlightNode.image = imageCache.value.filledCircle(diameter: diameter, color: component.theme.list.itemAccentColor)
} else {
self.highlightNode.image = nil
}
if self.currentMedia != component.media {
self.currentMedia = component.media
if let mediaPreviewNode = self.mediaPreviewNode {
self.mediaPreviewNode = nil
mediaPreviewNode.removeFromSupernode()
}
if let media = component.media {
let mediaPreviewNode = MediaPreviewNode(context: component.context, message: media.message, media: media.media)
self.mediaPreviewNode = mediaPreviewNode
self.buttonNode.insertSubnode(mediaPreviewNode, belowSubnode: self.highlightNode)
}
}
let titleColor: UIColor
let titleFont: UIFont
if component.isCurrent || component.media != nil {
titleColor = component.theme.list.itemCheckColors.foregroundColor
titleFont = Font.semibold(17.0)
} else if component.isEnabled {
titleColor = component.theme.list.itemPrimaryTextColor
titleFont = Font.regular(17.0)
} else {
titleColor = component.theme.list.itemDisabledTextColor
titleFont = Font.regular(17.0)
}
self.titleNode.attributedText = NSAttributedString(string: component.title, font: titleFont, textColor: titleColor)
let titleSize = self.titleNode.updateLayout(availableSize)
transition.setFrame(view: self.highlightNode.view, frame: CGRect(origin: CGPoint(x: -shadowInset, y: -shadowInset), size: CGSize(width: availableSize.width + shadowInset * 2.0, height: availableSize.height + shadowInset * 2.0)))
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)
self.buttonNode.frame = CGRect(origin: CGPoint(), size: availableSize)
self.buttonNode.isEnabled = component.isEnabled && component.media != nil
if let mediaPreviewNode = self.mediaPreviewNode {
mediaPreviewNode.frame = CGRect(origin: CGPoint(), size: availableSize)
mediaPreviewNode.updateLayout(size: availableSize, synchronousLoads: false)
}
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
}
}
private final class MonthComponent: CombinedComponent {
typealias EnvironmentType = ImageCache
let context: AccountContext
let model: MonthModel
let foregroundColor: UIColor
let strings: PresentationStrings
let theme: PresentationTheme
let navigateToDay: (Int32) -> Void
init(
context: AccountContext,
model: MonthModel,
foregroundColor: UIColor,
strings: PresentationStrings,
theme: PresentationTheme,
navigateToDay: @escaping (Int32) -> Void
) {
self.context = context
self.model = model
self.foregroundColor = foregroundColor
self.strings = strings
self.theme = theme
self.navigateToDay = navigateToDay
}
static func ==(lhs: MonthComponent, rhs: MonthComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.model != rhs.model {
return false
}
if lhs.foregroundColor != rhs.foregroundColor {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.theme !== rhs.theme {
return false
}
return true
}
static var body: Body {
let title = Child(Text.self)
let weekdayTitles = ChildMap(environment: Empty.self, keyedBy: Int.self)
let days = ChildMap(environment: ImageCache.self, keyedBy: Int.self)
return { context in
let sideInset: CGFloat = 14.0
let titleWeekdaysSpacing: CGFloat = 18.0
let weekdayDaySpacing: CGFloat = 14.0
let weekdaySize: CGFloat = 46.0
let weekdaySpacing: CGFloat = 6.0
let weekdayWidth = floor((context.availableSize.width - sideInset * 2.0) / 7.0)
let title = title.update(
component: Text(
text: "\(monthName(index: context.component.model.index - 1, strings: context.component.strings)) \(context.component.model.year)",
font: Font.semibold(17.0),
color: .black
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 100.0),
transition: .immediate
)
let updatedWeekdayTitles = (0 ..< 7).map { index in
return weekdayTitles[index].update(
component: AnyComponent(Text(
text: dayName(index: index, strings: context.component.strings),
font: Font.regular(10.0),
color: .black
)),
availableSize: CGSize(width: 100.0, height: 100.0),
transition: .immediate
)
}
let updatedDays = (0 ..< context.component.model.numberOfDays).map { index -> _UpdatedChildComponent in
let dayOfMonth = index + 1
let isCurrent = context.component.model.currentYear == context.component.model.year && context.component.model.currentMonth == context.component.model.index && context.component.model.currentDayOfMonth == dayOfMonth
var isEnabled = true
if context.component.model.currentYear == context.component.model.year {
if context.component.model.currentMonth == context.component.model.index {
if dayOfMonth > context.component.model.currentDayOfMonth {
isEnabled = false
}
} else if context.component.model.index > context.component.model.currentMonth {
isEnabled = false
}
} else if context.component.model.year > context.component.model.currentYear {
isEnabled = false
}
let dayTimestamp = Int32(context.component.model.firstDay.timeIntervalSince1970) + 24 * 60 * 60 * Int32(index)
let navigateToDay = context.component.navigateToDay
return days[index].update(
component: AnyComponent(DayComponent(
title: "\(dayOfMonth)",
isCurrent: isCurrent,
isEnabled: isEnabled,
theme: context.component.theme,
context: context.component.context,
media: context.component.model.mediaByDay[index],
action: {
navigateToDay(dayTimestamp)
}
)),
environment: {
context.environment[ImageCache.self]
},
availableSize: CGSize(width: weekdaySize, height: weekdaySize),
transition: .immediate
)
}
let titleFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - title.size.width) / 2.0), y: 0.0), size: title.size)
context.add(title
.position(CGPoint(x: titleFrame.midX, y: titleFrame.midY))
)
let baseWeekdayTitleY = titleFrame.maxY + titleWeekdaysSpacing
var maxWeekdayY = baseWeekdayTitleY
for i in 0 ..< updatedWeekdayTitles.count {
let weekdaySize = updatedWeekdayTitles[i].size
let weekdayFrame = CGRect(origin: CGPoint(x: sideInset + CGFloat(i) * weekdayWidth + floor((weekdayWidth - weekdaySize.width) / 2.0), y: baseWeekdayTitleY), size: weekdaySize)
maxWeekdayY = max(maxWeekdayY, weekdayFrame.maxY)
context.add(updatedWeekdayTitles[i]
.position(CGPoint(x: weekdayFrame.midX, y: weekdayFrame.midY))
)
}
let baseDayY = maxWeekdayY + weekdayDaySpacing
var maxDayY = baseDayY
for i in 0 ..< updatedDays.count {
let gridIndex = (context.component.model.firstDayWeekday - 1) + i
let gridX = sideInset + CGFloat(gridIndex % 7) * weekdayWidth
let gridY = baseDayY + CGFloat(gridIndex / 7) * (weekdaySize + weekdaySpacing)
let dayItemSize = updatedDays[i].size
let dayFrame = CGRect(origin: CGPoint(x: gridX + floor((weekdayWidth - dayItemSize.width) / 2.0), y: gridY + floor((weekdaySize - dayItemSize.height) / 2.0)), size: dayItemSize)
maxDayY = max(maxDayY, gridY + weekdaySize)
context.add(updatedDays[i]
.position(CGPoint(x: dayFrame.midX, y: dayFrame.midY))
)
}
return CGSize(width: context.availableSize.width, height: maxDayY)
}
}
}
private struct DayMedia: Equatable {
var message: EngineMessage
var media: EngineMedia
static func ==(lhs: DayMedia, rhs: DayMedia) -> Bool {
if lhs.message.id != rhs.message.id {
return false
}
return true
}
}
private struct MonthModel: Equatable {
var year: Int
var index: Int
var numberOfDays: Int
var firstDay: Date
var firstDayWeekday: Int
var currentYear: Int
var currentMonth: Int
var currentDayOfMonth: Int
var mediaByDay: [Int: DayMedia]
init(
year: Int,
index: Int,
numberOfDays: Int,
firstDay: Date,
firstDayWeekday: Int,
currentYear: Int,
currentMonth: Int,
currentDayOfMonth: Int,
mediaByDay: [Int: DayMedia]
) {
self.year = year
self.index = index
self.numberOfDays = numberOfDays
self.firstDay = firstDay
self.firstDayWeekday = firstDayWeekday
self.currentYear = currentYear
self.currentMonth = currentMonth
self.currentDayOfMonth = currentDayOfMonth
self.mediaByDay = mediaByDay
}
}
private func monthMetadata(calendar: Calendar, for baseDate: Date, currentYear: Int, currentMonth: Int, currentDayOfMonth: Int) -> MonthModel? {
guard let numberOfDaysInMonth = calendar.range(of: .day, in: .month, for: baseDate)?.count, let firstDayOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: baseDate)) else {
return nil
}
let year = calendar.component(.year, from: firstDayOfMonth)
let month = calendar.component(.month, from: firstDayOfMonth)
let firstDayWeekday = calendar.component(.weekday, from: firstDayOfMonth)
return MonthModel(
year: year,
index: month,
numberOfDays: numberOfDaysInMonth,
firstDay: firstDayOfMonth,
firstDayWeekday: firstDayWeekday,
currentYear: currentYear,
currentMonth: currentMonth,
currentDayOfMonth: currentDayOfMonth,
mediaByDay: [:]
)
}
public final class CalendarMessageScreen: ViewController {
private final class Node: ViewControllerTracingNode, UIScrollViewDelegate {
private let context: AccountContext
private let peerId: PeerId
private let navigateToDay: (Int32) -> Void
private var presentationData: PresentationData
private var scrollView: Scroller
private var initialMonthIndex: Int = 0
private var months: [MonthModel] = []
private var monthViews: [Int: ComponentHostView<ImageCache>] = [:]
private let imageCache = ImageCache()
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) {
self.context = context
self.peerId = peerId
self.navigateToDay = navigateToDay
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.scrollView = Scroller()
self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.scrollsToTop = false
self.scrollView.delaysContentTouches = false
self.scrollView.canCancelContentTouches = true
if #available(iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
}
self.scrollView.layer.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
self.scrollView.disablesInteractiveModalDismiss = true
super.init()
let calendar = Calendar(identifier: .gregorian)
let baseDate = Date()
let currentYear = calendar.component(.year, from: baseDate)
let currentMonth = calendar.component(.month, from: baseDate)
let currentDayOfMonth = calendar.component(.day, from: baseDate)
let initialDate = Date(timeIntervalSince1970: TimeInterval(initialTimestamp))
for i in 0 ..< 12 * 20 {
guard let firstDayOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: baseDate)) else {
break
}
guard let monthBaseDate = calendar.date(byAdding: .month, value: -i, to: firstDayOfMonth) else {
break
}
guard let monthModel = monthMetadata(calendar: calendar, for: monthBaseDate, currentYear: currentYear, currentMonth: currentMonth, currentDayOfMonth: currentDayOfMonth) else {
break
}
if monthModel.year < 2013 {
break
}
if monthModel.year == 2013 {
if monthModel.index < 8 {
break
}
}
self.months.append(monthModel)
}
if self.months.count > 1 {
for i in 0 ..< self.months.count - 1 {
if initialDate >= self.months[i].firstDay {
self.initialMonthIndex = i
break
}
}
}
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.scrollView.delegate = self
self.view.addSubview(self.scrollView)
self.reloadMediaInfo()
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let isFirstLayout = self.validLayout == nil
self.validLayout = (layout, navigationHeight)
if self.updateScrollLayoutIfNeeded() {
}
if isFirstLayout, let frame = self.scrollLayout?.frames[self.initialMonthIndex] {
var contentOffset = floor(frame.midY - self.scrollView.bounds.height / 2.0)
if contentOffset < 0 {
contentOffset = 0
}
if contentOffset > self.scrollView.contentSize.height - self.scrollView.bounds.height {
contentOffset = self.scrollView.contentSize.height - self.scrollView.bounds.height
}
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffset), animated: false)
}
updateMonthViews()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let indicator = scrollView.value(forKey: "_verticalScrollIndicator") as? UIView {
indicator.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
}
self.updateMonthViews()
}
func updateScrollLayoutIfNeeded() -> Bool {
guard let (layout, navigationHeight) = self.validLayout else {
return false
}
if self.scrollLayout?.width == layout.size.width {
return false
}
var contentHeight: CGFloat = layout.intrinsicInsets.bottom
var frames: [Int: CGRect] = [:]
let measureView = ComponentHostView<ImageCache>()
let imageCache = ImageCache()
for i in 0 ..< self.months.count {
let monthSize = measureView.update(
transition: .immediate,
component: AnyComponent(MonthComponent(
context: self.context,
model: self.months[i],
foregroundColor: .black,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
navigateToDay: { _ in
}
)),
environment: {
imageCache
},
containerSize: CGSize(width: layout.size.width, height: 10000.0
))
let monthFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: monthSize)
contentHeight += monthSize.height
if i != self.months.count {
contentHeight += 16.0
}
frames[i] = monthFrame
}
self.scrollLayout = (layout.size.width, contentHeight, frames)
self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationHeight))
self.scrollView.contentSize = CGSize(width: layout.size.width, height: contentHeight)
self.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: layout.intrinsicInsets.bottom, left: 0.0, bottom: 0.0, right: layout.size.width - 3.0 - 6.0)
return true
}
func updateMonthViews() {
guard let (width, _, frames) = self.scrollLayout else {
return
}
let visibleRect = self.scrollView.bounds.insetBy(dx: 0.0, dy: -200.0)
var validMonths = Set<Int>()
for i in 0 ..< self.months.count {
guard let monthFrame = frames[i] else {
continue
}
if !visibleRect.intersects(monthFrame) {
continue
}
validMonths.insert(i)
let monthView: ComponentHostView<ImageCache>
if let current = self.monthViews[i] {
monthView = current
} else {
monthView = ComponentHostView()
monthView.layer.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
self.monthViews[i] = monthView
self.scrollView.addSubview(monthView)
}
let _ = monthView.update(
transition: .immediate,
component: AnyComponent(MonthComponent(
context: self.context,
model: self.months[i],
foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
navigateToDay: { [weak self] timestamp in
guard let strongSelf = self else {
return
}
strongSelf.navigateToDay(timestamp)
}
)),
environment: {
self.imageCache
},
containerSize: CGSize(width: width, height: 10000.0
))
monthView.frame = monthFrame
}
var removeMonths: [Int] = []
for (index, view) in self.monthViews {
if !validMonths.contains(index) {
view.removeFromSuperview()
removeMonths.append(index)
}
}
for index in removeMonths {
self.monthViews.removeValue(forKey: index)
}
}
private func reloadMediaInfo() {
let peerId = self.peerId
let months = self.months
let _ = (self.context.account.postbox.transaction { transaction -> [Int: [Int: DayMedia]] in
var updatedMedia: [Int: [Int: DayMedia]] = [:]
for i in 0 ..< months.count {
for day in 0 ..< months[i].numberOfDays {
let dayTimestamp = Int32(months[i].firstDay.timeIntervalSince1970) + 24 * 60 * 60 * Int32(day)
let nextDayTimestamp = Int32(months[i].firstDay.timeIntervalSince1970) + 24 * 60 * 60 * Int32(day - 1)
if let message = transaction.firstMessageInRange(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .photoOrVideo, timestampMax: dayTimestamp, timestampMin: nextDayTimestamp - 1) {
/*if message.timestamp < nextDayTimestamp {
continue
}*/
if updatedMedia[i] == nil {
updatedMedia[i] = [:]
}
mediaLoop: for media in message.media {
switch media {
case _ as TelegramMediaImage, _ as TelegramMediaFile:
updatedMedia[i]![day] = DayMedia(message: EngineMessage(message), media: EngineMedia(media))
break mediaLoop
default:
break
}
}
}
}
}
return updatedMedia
}
|> deliverOnMainQueue).start(next: { [weak self] updatedMedia in
guard let strongSelf = self else {
return
}
for (monthIndex, mediaByDay) in updatedMedia {
strongSelf.months[monthIndex].mediaByDay = mediaByDay
}
strongSelf.updateMonthViews()
})
}
}
private var node: Node {
return self.displayNode as! Node
}
private let context: AccountContext
private let peerId: PeerId
private let initialTimestamp: Int32
private let navigateToDay: (CalendarMessageScreen, Int32) -> Void
public init(context: AccountContext, peerId: PeerId, initialTimestamp: Int32, navigateToDay: @escaping (CalendarMessageScreen, Int32) -> Void) {
self.context = context
self.peerId = peerId
self.initialTimestamp = initialTimestamp
self.navigateToDay = navigateToDay
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData))
self.navigationPresentation = .modal
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(dismissPressed)), animated: false)
//TODO:localize
self.navigationItem.setTitle("Jump to Date", animated: false)
}
required public init(coder aDecoder: NSCoder) {
preconditionFailure()
}
@objc private func dismissPressed() {
self.dismiss()
}
override public func loadDisplayNode() {
self.displayNode = Node(context: self.context, peerId: self.peerId, initialTimestamp: self.initialTimestamp, navigateToDay: { [weak self] timestamp in
guard let strongSelf = self else {
return
}
strongSelf.navigateToDay(strongSelf, timestamp)
})
self.displayNodeDidLoad()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.node.containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
}
}