mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
191eb527c5
@ -326,7 +326,7 @@ private let gradientColors: [NSArray] = [
|
||||
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
|
||||
]
|
||||
|
||||
private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId: Int64, letters: [String]) -> UIImage? {
|
||||
private func avatarViewLettersImage(size: CGSize, peerId: PeerId, letters: [String]) -> UIImage? {
|
||||
UIGraphicsBeginImageContextWithOptions(size, false, 2.0)
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
|
||||
@ -334,7 +334,12 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
|
||||
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||
context?.clip()
|
||||
|
||||
let colorIndex = abs(Int(accountPeerId + peerId))
|
||||
let colorIndex: Int
|
||||
if peerId.namespace == .max {
|
||||
colorIndex = 0
|
||||
} else {
|
||||
colorIndex = abs(Int(clamping: peerId.id._internalGetInt64Value()))
|
||||
}
|
||||
|
||||
let colorsArray = gradientColors[colorIndex % gradientColors.count]
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
@ -368,11 +373,11 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId:
|
||||
return image
|
||||
}
|
||||
|
||||
private func avatarImage(path: String?, peerId: Int64, accountPeerId: Int64, letters: [String], size: CGSize) -> UIImage {
|
||||
private func avatarImage(path: String?, peerId: PeerId, letters: [String], size: CGSize) -> UIImage {
|
||||
if let path = path, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||
return roundImage
|
||||
} else {
|
||||
return avatarViewLettersImage(size: size, peerId: peerId, accountPeerId: accountPeerId, letters: letters)!
|
||||
return avatarViewLettersImage(size: size, peerId: peerId, letters: letters)!
|
||||
}
|
||||
}
|
||||
|
||||
@ -394,48 +399,27 @@ private func storeTemporaryImage(path: String) -> String {
|
||||
private func peerAvatar(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer) -> INImage? {
|
||||
if let resource = smallestImageRepresentation(peer.profileImageRepresentations)?.resource, let path = mediaBox.completedResourcePath(resource) {
|
||||
let cachedPath = mediaBox.cachedRepresentationPathForId(resource.id.stringRepresentation, representationId: "intents.png", keepDuration: .shortLived)
|
||||
if let _ = fileSize(cachedPath), let data = try? Data(contentsOf: URL(fileURLWithPath: cachedPath), options: .alwaysMapped) {
|
||||
do {
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
if let _ = fileSize(cachedPath) {
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} else {
|
||||
let image = avatarImage(path: path, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
||||
let image = avatarImage(path: path, peerId: peer.id, letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
||||
if let data = image.pngData() {
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
||||
}
|
||||
do {
|
||||
//let data = try Data(contentsOf: URL(fileURLWithPath: cachedPath), options: .alwaysMapped)
|
||||
//return INImage(imageData: data)
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
}
|
||||
}
|
||||
|
||||
let cachedPath = mediaBox.cachedRepresentationPathForId("lettersAvatar-\(peer.displayLetters.joined(separator: ","))", representationId: "intents.png", keepDuration: .shortLived)
|
||||
if let _ = fileSize(cachedPath) {
|
||||
do {
|
||||
//let data = try Data(contentsOf: URL(fileURLWithPath: cachedPath), options: [])
|
||||
//return INImage(imageData: data)
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} else {
|
||||
let image = avatarImage(path: nil, peerId: peer.id.toInt64(), accountPeerId: accountPeerId.toInt64(), letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
||||
let image = avatarImage(path: nil, peerId: peer.id, letters: peer.displayLetters, size: CGSize(width: 50.0, height: 50.0))
|
||||
if let data = image.pngData() {
|
||||
let _ = try? data.write(to: URL(fileURLWithPath: cachedPath), options: .atomic)
|
||||
}
|
||||
do {
|
||||
//let data = try Data(contentsOf: URL(fileURLWithPath: cachedPath), options: .alwaysMapped)
|
||||
//return INImage(imageData: data)
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,8 +46,8 @@ public func messageMediaFileCancelInteractiveFetch(context: AccountContext, mess
|
||||
context.fetchManager.cancelInteractiveFetches(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource)
|
||||
}
|
||||
|
||||
public func messageMediaImageInteractiveFetched(context: AccountContext, message: Message, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
|
||||
return messageMediaImageInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), image: image, resource: resource, range: range, userInitiated: true, priority: .userInitiated, storeToDownloadsPeerType: storeToDownloadsPeerType)
|
||||
public func messageMediaImageInteractiveFetched(context: AccountContext, message: Message, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, userInitiated: Bool = true, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
|
||||
return messageMediaImageInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), image: image, resource: resource, range: range, userInitiated: userInitiated, priority: .userInitiated, storeToDownloadsPeerType: storeToDownloadsPeerType)
|
||||
}
|
||||
|
||||
public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, image: TelegramMediaImage, resource: MediaResource, range: Range<Int>? = nil, userInitiated: Bool, priority: FetchManagerPriority, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Void, NoError> {
|
||||
|
@ -268,6 +268,8 @@ private final class DayComponent: Component {
|
||||
private var action: (() -> Void)?
|
||||
private var currentMedia: DayMedia?
|
||||
|
||||
private(set) var index: MessageIndex?
|
||||
|
||||
init() {
|
||||
self.button = HighlightableButton()
|
||||
self.highlightView = UIImageView()
|
||||
@ -295,6 +297,7 @@ private final class DayComponent: Component {
|
||||
|
||||
func update(component: DayComponent, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
|
||||
self.action = component.action
|
||||
self.index = component.media?.message.index
|
||||
|
||||
let diameter = min(availableSize.width, availableSize.height)
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - diameter) / 2.0), y: floor((availableSize.height - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter))
|
||||
@ -601,16 +604,18 @@ public final class CalendarMessageScreen: ViewController {
|
||||
private final class Node: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let initialTimestamp: Int32
|
||||
private let navigateToDay: (Int32) -> Void
|
||||
private let previewDay: (MessageIndex, ASDisplayNode, CGRect, ContextGesture) -> Void
|
||||
|
||||
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>] = [:]
|
||||
private let contextGestureContainerNode: ContextControllerSourceNode
|
||||
|
||||
private let imageCache = ImageCache()
|
||||
|
||||
@ -622,14 +627,20 @@ public final class CalendarMessageScreen: ViewController {
|
||||
private var isLoadingMoreDisposable: Disposable?
|
||||
private var stateDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (Int32) -> Void) {
|
||||
private weak var currentGestureDayView: DayComponent.View?
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (Int32) -> Void, previewDay: @escaping (MessageIndex, ASDisplayNode, CGRect, ContextGesture) -> Void) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.initialTimestamp = initialTimestamp
|
||||
self.calendarSource = calendarSource
|
||||
self.navigateToDay = navigateToDay
|
||||
self.previewDay = previewDay
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.contextGestureContainerNode = ContextControllerSourceNode()
|
||||
|
||||
self.scrollView = Scroller()
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
@ -644,6 +655,75 @@ public final class CalendarMessageScreen: ViewController {
|
||||
|
||||
super.init()
|
||||
|
||||
self.contextGestureContainerNode.shouldBegin = { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let result = strongSelf.contextGestureContainerNode.view.hitTest(point, with: nil) as? HighlightableButton else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let dayView = result.superview as? DayComponent.View else {
|
||||
return false
|
||||
}
|
||||
|
||||
strongSelf.currentGestureDayView = dayView
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
self.contextGestureContainerNode.customActivationProgress = { [weak self] progress, update in
|
||||
guard let strongSelf = self, let currentGestureDayView = strongSelf.currentGestureDayView else {
|
||||
return
|
||||
}
|
||||
let itemLayer = currentGestureDayView.layer
|
||||
|
||||
let targetContentRect = CGRect(origin: CGPoint(), size: itemLayer.bounds.size)
|
||||
|
||||
let scaleSide = itemLayer.bounds.width
|
||||
let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide)
|
||||
let currentScale = 1.0 * (1.0 - progress) + minScale * progress
|
||||
|
||||
let originalCenterOffsetX: CGFloat = itemLayer.bounds.width / 2.0 - targetContentRect.midX
|
||||
let scaledCenterOffsetX: CGFloat = originalCenterOffsetX * currentScale
|
||||
|
||||
let originalCenterOffsetY: CGFloat = itemLayer.bounds.height / 2.0 - targetContentRect.midY
|
||||
let scaledCenterOffsetY: CGFloat = originalCenterOffsetY * currentScale
|
||||
|
||||
let scaleMidX: CGFloat = scaledCenterOffsetX - originalCenterOffsetX
|
||||
let scaleMidY: CGFloat = scaledCenterOffsetY - originalCenterOffsetY
|
||||
|
||||
switch update {
|
||||
case .update:
|
||||
let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
|
||||
itemLayer.sublayerTransform = sublayerTransform
|
||||
case .begin:
|
||||
let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
|
||||
itemLayer.sublayerTransform = sublayerTransform
|
||||
case .ended:
|
||||
let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0)
|
||||
let previousTransform = itemLayer.sublayerTransform
|
||||
itemLayer.sublayerTransform = sublayerTransform
|
||||
|
||||
itemLayer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
self.contextGestureContainerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self, let currentGestureDayView = strongSelf.currentGestureDayView else {
|
||||
return
|
||||
}
|
||||
strongSelf.currentGestureDayView = nil
|
||||
|
||||
currentGestureDayView.isUserInteractionEnabled = false
|
||||
currentGestureDayView.isUserInteractionEnabled = true
|
||||
|
||||
if let index = currentGestureDayView.index {
|
||||
strongSelf.previewDay(index, strongSelf, currentGestureDayView.convert(currentGestureDayView.bounds, to: strongSelf.view), gesture)
|
||||
}
|
||||
}
|
||||
|
||||
let calendar = Calendar(identifier: .gregorian)
|
||||
|
||||
let baseDate = Date()
|
||||
@ -651,8 +731,6 @@ public final class CalendarMessageScreen: ViewController {
|
||||
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
|
||||
@ -660,10 +738,18 @@ public final class CalendarMessageScreen: ViewController {
|
||||
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
|
||||
}
|
||||
|
||||
let firstDayTimestamp = Int32(monthModel.firstDay.timeIntervalSince1970)
|
||||
let lastDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(monthModel.numberOfDays)
|
||||
|
||||
if let minTimestamp = calendarSource.minTimestamp, minTimestamp > lastDayTimestamp {
|
||||
break
|
||||
}
|
||||
|
||||
if monthModel.year < 2013 {
|
||||
break
|
||||
}
|
||||
@ -676,19 +762,11 @@ public final class CalendarMessageScreen: ViewController {
|
||||
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.addSubnode(self.contextGestureContainerNode)
|
||||
self.contextGestureContainerNode.view.addSubview(self.scrollView)
|
||||
|
||||
self.isLoadingMoreDisposable = (self.calendarSource.isLoadingMore
|
||||
|> distinctUntilChanged
|
||||
@ -722,20 +800,38 @@ public final class CalendarMessageScreen: ViewController {
|
||||
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 isFirstLayout {
|
||||
let initialDate = Date(timeIntervalSince1970: TimeInterval(self.initialTimestamp))
|
||||
var initialMonthIndex: Int?
|
||||
|
||||
if self.months.count > 1 {
|
||||
for i in 0 ..< self.months.count - 1 {
|
||||
if initialDate >= self.months[i].firstDay {
|
||||
initialMonthIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if contentOffset > self.scrollView.contentSize.height - self.scrollView.bounds.height {
|
||||
contentOffset = self.scrollView.contentSize.height - self.scrollView.bounds.height
|
||||
|
||||
if isFirstLayout, let initialMonthIndex = initialMonthIndex, let frame = self.scrollLayout?.frames[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)
|
||||
}
|
||||
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffset), animated: false)
|
||||
}
|
||||
|
||||
updateMonthViews()
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
self.contextGestureContainerNode.cancelGesture()
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let indicator = scrollView.value(forKey: "_verticalScrollIndicator") as? UIView {
|
||||
indicator.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
|
||||
@ -784,7 +880,8 @@ public final class CalendarMessageScreen: ViewController {
|
||||
|
||||
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.contextGestureContainerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationHeight))
|
||||
self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), 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)
|
||||
|
||||
@ -861,19 +958,11 @@ public final class CalendarMessageScreen: ViewController {
|
||||
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 firstDayTimestamp = Int32(self.months[i].firstDay.timeIntervalSince1970)
|
||||
|
||||
let dayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day)
|
||||
let nextDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day - 1)
|
||||
|
||||
@ -901,57 +990,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
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]] = [:]
|
||||
|
||||
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()
|
||||
})*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -964,13 +1003,15 @@ public final class CalendarMessageScreen: ViewController {
|
||||
private let calendarSource: SparseMessageCalendar
|
||||
private let initialTimestamp: Int32
|
||||
private let navigateToDay: (CalendarMessageScreen, Int32) -> Void
|
||||
private let previewDay: (MessageIndex, ASDisplayNode, CGRect, ContextGesture) -> Void
|
||||
|
||||
public init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (CalendarMessageScreen, Int32) -> Void) {
|
||||
public init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (CalendarMessageScreen, Int32) -> Void, previewDay: @escaping (MessageIndex, ASDisplayNode, CGRect, ContextGesture) -> Void) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.calendarSource = calendarSource
|
||||
self.initialTimestamp = initialTimestamp
|
||||
self.navigateToDay = navigateToDay
|
||||
self.previewDay = previewDay
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -997,7 +1038,7 @@ public final class CalendarMessageScreen: ViewController {
|
||||
return
|
||||
}
|
||||
strongSelf.navigateToDay(strongSelf, timestamp)
|
||||
})
|
||||
}, previewDay: self.previewDay)
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
@ -229,7 +229,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati
|
||||
let intent: TGMediaAssetsControllerIntent = asFiles ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent
|
||||
|
||||
var hasHeic = false
|
||||
var allItems = carouselItem.selectionContext.selectedItems() ?? []
|
||||
var allItems = carouselItem.selectionContext?.selectedItems() ?? []
|
||||
if let currentItem = currentItem {
|
||||
allItems.append(currentItem)
|
||||
}
|
||||
|
@ -1699,13 +1699,41 @@ public func standaloneChatMessagePhotoInteractiveFetched(account: Account, photo
|
||||
}
|
||||
}
|
||||
|
||||
public func chatMessagePhotoInteractiveFetched(context: AccountContext, photoReference: ImageMediaReference, displayAtSize: Int?, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<FetchResourceSourceType, FetchResourceError> {
|
||||
public func chatMessagePhotoInteractiveFetched(context: AccountContext, photoReference: ImageMediaReference, displayAtSize: Int?, storeToDownloadsPeerType: MediaAutoDownloadPeerType?) -> Signal<Never, NoError> {
|
||||
if let largestRepresentation = largestRepresentationForPhoto(photoReference.media) {
|
||||
var fetchRange: (Range<Int>, MediaBoxFetchPriority)?
|
||||
if let displayAtSize = displayAtSize, let range = representationFetchRangeForDisplayAtSize(representation: largestRepresentation, dimension: displayAtSize) {
|
||||
fetchRange = (range, .default)
|
||||
}
|
||||
|
||||
|
||||
/*switch photoReference {
|
||||
case let .message(message, _):
|
||||
if let id = message.id {
|
||||
let ranges: IndexSet
|
||||
if let (range, _) = fetchRange {
|
||||
ranges = IndexSet(integersIn: range)
|
||||
} else {
|
||||
ranges = IndexSet(integersIn: 0 ..< Int(Int32.max) as Range<Int>)
|
||||
}
|
||||
return context.fetchManager.interactivelyFetched(
|
||||
category: .image,
|
||||
location: .chat(id.peerId),
|
||||
locationKey: .messageId(id),
|
||||
mediaReference: photoReference.abstract,
|
||||
resourceReference: photoReference.resourceReference(largestRepresentation.resource),
|
||||
ranges: ranges,
|
||||
statsCategory: .image,
|
||||
elevatedPriority: false,
|
||||
userInitiated: false,
|
||||
priority: .userInitiated,
|
||||
storeToDownloadsPeerType: nil
|
||||
)
|
||||
|> ignoreValues
|
||||
}
|
||||
default:
|
||||
break
|
||||
}*/
|
||||
|
||||
return fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: photoReference.resourceReference(largestRepresentation.resource), range: fetchRange, statsCategory: .image, reportResultStatus: true)
|
||||
|> mapToSignal { type -> Signal<FetchResourceSourceType, FetchResourceError> in
|
||||
if case .remote = type, let peerType = storeToDownloadsPeerType {
|
||||
@ -1717,6 +1745,10 @@ public func chatMessagePhotoInteractiveFetched(context: AccountContext, photoRef
|
||||
}
|
||||
return .single(type)
|
||||
}
|
||||
|> ignoreValues
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .never()
|
||||
}
|
||||
|
47
submodules/Postbox/Sources/DeletedMessagesView.swift
Normal file
47
submodules/Postbox/Sources/DeletedMessagesView.swift
Normal file
@ -0,0 +1,47 @@
|
||||
import Foundation
|
||||
|
||||
final class MutableDeletedMessagesView: MutablePostboxView {
|
||||
let peerId: PeerId
|
||||
var currentDeletedMessages: [MessageId] = []
|
||||
|
||||
init(peerId: PeerId) {
|
||||
self.peerId = peerId
|
||||
}
|
||||
|
||||
func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool {
|
||||
var updated = false
|
||||
if let operations = transaction.currentOperationsByPeerId[self.peerId] {
|
||||
var testMessageIds: [MessageId] = []
|
||||
for operation in operations {
|
||||
switch operation {
|
||||
case let .Remove(indices):
|
||||
for (index, _) in indices {
|
||||
testMessageIds.append(index.id)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.currentDeletedMessages.removeAll()
|
||||
for id in testMessageIds {
|
||||
if !postbox.messageHistoryIndexTable.exists(id) {
|
||||
self.currentDeletedMessages.append(id)
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
||||
func immutableView() -> PostboxView {
|
||||
return DeletedMessagesView(self)
|
||||
}
|
||||
}
|
||||
|
||||
public final class DeletedMessagesView: PostboxView {
|
||||
public let currentDeletedMessages: [MessageId]
|
||||
|
||||
init(_ view: MutableDeletedMessagesView) {
|
||||
self.currentDeletedMessages = view.currentDeletedMessages
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ public enum PostboxViewKey: Hashable {
|
||||
case historyTagInfo(peerId: PeerId, tag: MessageTags)
|
||||
case topChatMessage(peerIds: [PeerId])
|
||||
case contacts(accountPeerId: PeerId?, includePresences: Bool)
|
||||
case deletedMessages(peerId: PeerId)
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
@ -106,6 +107,8 @@ public enum PostboxViewKey: Hashable {
|
||||
hasher.combine(peerIds)
|
||||
case .contacts:
|
||||
hasher.combine(16)
|
||||
case let .deletedMessages(peerId):
|
||||
hasher.combine(peerId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,6 +300,12 @@ public enum PostboxViewKey: Hashable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .deletedMessages(peerId):
|
||||
if case .deletedMessages(peerId) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -365,5 +374,7 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost
|
||||
return MutableTopChatMessageView(postbox: postbox, peerIds: Set(peerIds))
|
||||
case let .contacts(accountPeerId, includePresences):
|
||||
return MutableContactPeersView(postbox: postbox, accountPeerId: accountPeerId, includePresences: includePresences)
|
||||
case let .deletedMessages(peerId):
|
||||
return MutableDeletedMessagesView(peerId: peerId)
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ swift_library(
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/AnimationUI:AnimationUI",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -3,6 +3,7 @@ import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
|
||||
private final class NullActionClass: NSObject, CAAction {
|
||||
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
||||
@ -129,8 +130,7 @@ private final class Shimmer {
|
||||
if let image = self.image {
|
||||
layer.contents = image.cgImage
|
||||
|
||||
let shiftedContentsRect = CGRect(origin: CGPoint(x: frame.minX / containerSize.width, y: frame.minY / containerSize.height), size: CGSize(width: frame.width / containerSize.width, height: frame.height / containerSize.height))
|
||||
let _ = shiftedContentsRect
|
||||
let shiftedContentsRect = CGRect(origin: CGPoint(x: 0.0, y: frame.minY / containerSize.height), size: CGSize(width: 1.0, height: frame.height / containerSize.height))
|
||||
layer.contentsRect = shiftedContentsRect
|
||||
|
||||
if layer.animation(forKey: "shimmer") == nil {
|
||||
@ -140,7 +140,7 @@ private final class Shimmer {
|
||||
animation.isAdditive = true
|
||||
animation.repeatCount = .infinity
|
||||
animation.duration = 0.8
|
||||
animation.beginTime = 1.0
|
||||
animation.beginTime = layer.convertTime(1.0, from: nil)
|
||||
layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
@ -429,6 +429,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
private let scrollView: UIScrollView
|
||||
private let shimmer: Shimmer
|
||||
|
||||
var theme: PresentationTheme
|
||||
|
||||
var layout: Layout?
|
||||
var items: Items?
|
||||
var visibleItems: [AnyHashable: VisibleItem] = [:]
|
||||
@ -446,7 +448,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
let coveringOffsetUpdated: (Viewport, ContainedViewLayoutTransition) -> Void
|
||||
|
||||
init(zoomLevel: ZoomLevel, maybeLoadHoleAnchor: @escaping (HoleAnchor, HoleLocation) -> Void, coveringOffsetUpdated: @escaping (Viewport, ContainedViewLayoutTransition) -> Void) {
|
||||
init(theme: PresentationTheme, zoomLevel: ZoomLevel, maybeLoadHoleAnchor: @escaping (HoleAnchor, HoleLocation) -> Void, coveringOffsetUpdated: @escaping (Viewport, ContainedViewLayoutTransition) -> Void) {
|
||||
self.theme = theme
|
||||
self.zoomLevel = zoomLevel
|
||||
self.maybeLoadHoleAnchor = maybeLoadHoleAnchor
|
||||
self.coveringOffsetUpdated = coveringOffsetUpdated
|
||||
@ -596,6 +599,23 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
func visualItem(at index: Int) -> SparseItemGridDisplayItem? {
|
||||
guard let items = self.items, !items.items.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let item = items.item(at: index) else {
|
||||
return nil
|
||||
}
|
||||
for (id, visibleItem) in self.visibleItems {
|
||||
if id == item.id {
|
||||
return visibleItem
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func item(at point: CGPoint) -> Item? {
|
||||
guard let items = self.items, !items.items.isEmpty else {
|
||||
return nil
|
||||
@ -749,6 +769,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
let visibleRange = layout.visibleItemRange(for: visibleBounds, count: items.count)
|
||||
for index in visibleRange.minIndex ... visibleRange.maxIndex {
|
||||
if let item = items.item(at: index) {
|
||||
let itemFrame = layout.frame(at: index)
|
||||
|
||||
let itemLayer: VisibleItem
|
||||
if let current = self.visibleItems[item.id] {
|
||||
itemLayer = current
|
||||
@ -777,7 +799,6 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
itemLayer.shimmerLayer = placeholderLayer
|
||||
}
|
||||
|
||||
let itemFrame = layout.frame(at: index)
|
||||
placeholderLayer.frame = itemFrame
|
||||
self.shimmer.update(colors: shimmerColors, layer: placeholderLayer, containerSize: layout.containerLayout.size, frame: itemFrame.offsetBy(dx: 0.0, dy: -visibleBounds.minY))
|
||||
placeholderLayer.update(size: itemFrame.size)
|
||||
@ -788,7 +809,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
validIds.insert(item.id)
|
||||
|
||||
itemLayer.frame = layout.frame(at: index)
|
||||
itemLayer.frame = itemFrame
|
||||
} else {
|
||||
let placeholderLayer: SparseItemGridShimmerLayer
|
||||
if self.visiblePlaceholders.count > usedPlaceholderCount {
|
||||
@ -834,6 +855,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
} else if let view = item.view {
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
item.shimmerLayer?.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
@ -931,6 +953,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
contentOffset: self.scrollView.bounds.minY,
|
||||
isScrolling: self.scrollView.isDragging || self.scrollView.isDecelerating,
|
||||
dateString: dateString ?? "",
|
||||
theme: self.theme,
|
||||
transition: .immediate
|
||||
)
|
||||
}
|
||||
@ -1058,6 +1081,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
private var tapRecognizer: UITapGestureRecognizer?
|
||||
private var pinchRecognizer: UIPinchGestureRecognizer?
|
||||
|
||||
private var theme: PresentationTheme
|
||||
private var containerLayout: ContainerLayout?
|
||||
private var items: Items?
|
||||
|
||||
@ -1082,7 +1106,9 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
public var cancelExternalContentGestures: (() -> Void)?
|
||||
|
||||
override public init() {
|
||||
public init(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.scrollingArea = SparseItemGridScrollingArea()
|
||||
|
||||
super.init()
|
||||
@ -1135,7 +1161,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
let progress = (scale - interactiveState.initialScale) / (interactiveState.targetScale - interactiveState.initialScale)
|
||||
var replacedTransition = false
|
||||
//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(width: containerLayout.size.width, startingAt: boundaryViewport.zoomLevel)
|
||||
@ -1176,7 +1202,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
let restoreScrollPosition: (y: CGFloat, index: Int)? = (anchorItemFrame.minY, nextAnchorItemIndex)
|
||||
|
||||
let nextViewport = Viewport(zoomLevel: nextZoomLevel, maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
let nextViewport = Viewport(theme: self.theme, zoomLevel: nextZoomLevel, maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1226,7 +1252,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
let restoreScrollPosition: (y: CGFloat, index: Int)? = (anchorItemFrame.minY, anchorItem.index)
|
||||
let anchorItemIndex = anchorItem.index
|
||||
|
||||
let nextViewport = Viewport(zoomLevel: nextZoomLevel, maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
let nextViewport = Viewport(theme: self.theme, zoomLevel: nextZoomLevel, maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1285,7 +1311,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func update(size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets, lockScrollingAtTop: Bool, fixedItemHeight: CGFloat?, items: Items, synchronous: Bool) {
|
||||
public func update(size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets, lockScrollingAtTop: Bool, fixedItemHeight: CGFloat?, items: Items, theme: PresentationTheme, synchronous: Bool) {
|
||||
self.theme = theme
|
||||
let containerLayout = ContainerLayout(size: size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, lockScrollingAtTop: lockScrollingAtTop, fixedItemHeight: fixedItemHeight)
|
||||
self.containerLayout = containerLayout
|
||||
self.items = items
|
||||
@ -1295,7 +1322,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.pinchRecognizer?.isEnabled = fixedItemHeight == nil && !lockScrollingAtTop
|
||||
|
||||
if self.currentViewport == nil {
|
||||
let currentViewport = Viewport(zoomLevel: self.initialZoomLevel ?? ZoomLevel(rawValue: 3), maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
let currentViewport = Viewport(theme: self.theme, zoomLevel: self.initialZoomLevel ?? ZoomLevel(rawValue: 3), maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1377,7 +1404,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.currentViewport = nil
|
||||
previousViewport.removeFromSupernode()
|
||||
|
||||
let currentViewport = Viewport(zoomLevel: level, maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
let currentViewport = Viewport(theme: self.theme, zoomLevel: level, maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1468,6 +1495,13 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
return currentViewport.visualItem(at: point)
|
||||
}
|
||||
|
||||
public func item(at index: Int) -> SparseItemGridDisplayItem? {
|
||||
guard let currentViewport = self.currentViewport else {
|
||||
return nil
|
||||
}
|
||||
return currentViewport.visualItem(at: index)
|
||||
}
|
||||
|
||||
public func scrollToItem(at index: Int) {
|
||||
guard let currentViewport = self.currentViewport else {
|
||||
return
|
||||
|
@ -5,6 +5,7 @@ import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import AnimationUI
|
||||
import TelegramPresentationData
|
||||
|
||||
public final class MultilineText: Component {
|
||||
public let text: String
|
||||
@ -474,7 +475,7 @@ private final class SparseItemGridScrollingIndicatorComponent: CombinedComponent
|
||||
component: Text(
|
||||
text: context.component.dateString,
|
||||
font: Font.medium(13.0),
|
||||
color: .black
|
||||
color: context.component.foregroundColor
|
||||
),
|
||||
availableSize: CGSize(width: 200.0, height: 100.0),
|
||||
transition: .immediate
|
||||
@ -482,7 +483,7 @@ private final class SparseItemGridScrollingIndicatorComponent: CombinedComponent
|
||||
|
||||
let rect = rect.update(
|
||||
component: ShadowRoundedRectangle(
|
||||
color: .white
|
||||
color: context.component.backgroundColor
|
||||
),
|
||||
availableSize: CGSize(width: text.size.width + 26.0, height: 32.0),
|
||||
transition: .immediate
|
||||
@ -646,6 +647,8 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
|
||||
public var displayTooltip: DisplayTooltip?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
override public init() {
|
||||
self.dateIndicator = ComponentHostView<Empty>()
|
||||
self.lineIndicator = ComponentHostView<Empty>()
|
||||
@ -781,9 +784,11 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
contentOffset: CGFloat,
|
||||
isScrolling: Bool,
|
||||
dateString: String,
|
||||
theme: PresentationTheme,
|
||||
transition: ContainedViewLayoutTransition
|
||||
) {
|
||||
self.containerSize = containerSize
|
||||
self.theme = theme
|
||||
|
||||
if self.dateIndicator.alpha.isZero {
|
||||
let transition: ContainedViewLayoutTransition = .immediate
|
||||
@ -797,9 +802,9 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
let indicatorSize = self.dateIndicator.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(SparseItemGridScrollingIndicatorComponent(
|
||||
backgroundColor: .white,
|
||||
backgroundColor: theme.list.itemHighlightedBackgroundColor,
|
||||
shadowColor: .black,
|
||||
foregroundColor: .black,
|
||||
foregroundColor: theme.list.itemPrimaryTextColor,
|
||||
dateString: dateString
|
||||
)),
|
||||
environment: {},
|
||||
@ -858,7 +863,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
}
|
||||
|
||||
private func updateLineIndicator(transition: ContainedViewLayoutTransition) {
|
||||
guard let indicatorPosition = self.indicatorPosition, let scrollIndicatorHeight = self.scrollIndicatorHeight else {
|
||||
guard let indicatorPosition = self.indicatorPosition, let scrollIndicatorHeight = self.scrollIndicatorHeight, let theme = self.theme else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -873,7 +878,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
let _ = self.lineIndicator.update(
|
||||
transition: mappedTransition,
|
||||
component: AnyComponent(RoundedRectangle(
|
||||
color: UIColor(white: 0.0, alpha: 0.3)
|
||||
color: theme.list.scrollIndicatorColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: lineIndicatorSize
|
||||
|
@ -11,6 +11,15 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable {
|
||||
return peer
|
||||
}
|
||||
}
|
||||
|
||||
public var id: MessageId? {
|
||||
switch content {
|
||||
case .none:
|
||||
return nil
|
||||
case let .message(_, id, _, _, _):
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
public var timestamp: Int32? {
|
||||
switch content {
|
||||
|
@ -74,6 +74,8 @@ public final class SparseMessageList {
|
||||
private var topSection: TopSection?
|
||||
private var topItemsDisposable = MetaDisposable()
|
||||
|
||||
private var deletedMessagesDisposable: Disposable?
|
||||
|
||||
private var sparseItems: SparseItems?
|
||||
private var sparseItemsDisposable: Disposable?
|
||||
|
||||
@ -159,12 +161,24 @@ public final class SparseMessageList {
|
||||
strongSelf.updateState()
|
||||
}
|
||||
})
|
||||
|
||||
self.deletedMessagesDisposable = (account.postbox.combinedView(keys: [.deletedMessages(peerId: peerId)])
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] views in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let view = views.views[.deletedMessages(peerId: peerId)] as? DeletedMessagesView else {
|
||||
return
|
||||
}
|
||||
strongSelf.processDeletedMessages(ids: view.currentDeletedMessages)
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.topItemsDisposable.dispose()
|
||||
self.sparseItemsDisposable?.dispose()
|
||||
self.loadHoleDisposable.dispose()
|
||||
self.deletedMessagesDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func resetTopSection() {
|
||||
@ -188,162 +202,37 @@ public final class SparseMessageList {
|
||||
}))
|
||||
}
|
||||
|
||||
private func processDeletedMessages(ids: [MessageId]) {
|
||||
if let sparseItems = self.sparseItems {
|
||||
let idsSet = Set(ids)
|
||||
|
||||
var removeIndices: [Int] = []
|
||||
for i in 0 ..< sparseItems.items.count {
|
||||
switch sparseItems.items[i] {
|
||||
case let .anchor(id, _, message):
|
||||
if message != nil, idsSet.contains(id) {
|
||||
removeIndices.append(i)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !removeIndices.isEmpty {
|
||||
for index in removeIndices.reversed() {
|
||||
self.sparseItems?.items.remove(at: index)
|
||||
}
|
||||
|
||||
self.updateState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadMoreFromTopSection() {
|
||||
self.topSectionItemRequestCount += 100
|
||||
self.resetTopSection()
|
||||
}
|
||||
|
||||
func loadPlaceholders(ids: [MessageId]) {
|
||||
var loadGlobalIds: [MessageId] = []
|
||||
var loadChannelIds: [PeerId: [MessageId]] = [:]
|
||||
for id in ids {
|
||||
if self.loadingPlaceholders[id] != nil {
|
||||
continue
|
||||
}
|
||||
self.loadingPlaceholders[id] = MetaDisposable()
|
||||
if id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
loadGlobalIds.append(id)
|
||||
} else if id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
if loadChannelIds[id.peerId] == nil {
|
||||
loadChannelIds[id.peerId] = []
|
||||
}
|
||||
loadChannelIds[id.peerId]!.append(id)
|
||||
}
|
||||
}
|
||||
|
||||
var loadSignals: [Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError>] = []
|
||||
let account = self.account
|
||||
|
||||
if !loadGlobalIds.isEmpty {
|
||||
loadSignals.append(self.account.postbox.transaction { transaction -> [Api.InputMessage] in
|
||||
var result: [Api.InputMessage] = []
|
||||
for id in loadGlobalIds {
|
||||
let inputMessage: Api.InputMessage = .inputMessageID(id: id.id)
|
||||
result.append(inputMessage)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|> mapToSignal { inputMessages -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in
|
||||
return account.network.request(Api.functions.messages.getMessages(id: inputMessages))
|
||||
|> map { result -> (messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) in
|
||||
switch result {
|
||||
case let .messages(messages, chats, users):
|
||||
return (messages, chats, users)
|
||||
case let .messagesSlice(_, _, _, _, messages, chats, users):
|
||||
return (messages, chats, users)
|
||||
case let .channelMessages(_, _, _, _, messages, chats, users):
|
||||
return (messages, chats, users)
|
||||
case .messagesNotModified:
|
||||
return ([], [], [])
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in
|
||||
return .single(([], [], []))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if !loadChannelIds.isEmpty {
|
||||
for (channelId, ids) in loadChannelIds {
|
||||
loadSignals.append(self.account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
return transaction.getPeer(channelId).flatMap(apiInputChannel)
|
||||
}
|
||||
|> mapToSignal { inputChannel -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .single(([], [], []))
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.channels.getMessages(channel: inputChannel, id: ids.map { Api.InputMessage.inputMessageID(id: $0.id) }))
|
||||
|> map { result -> (messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) in
|
||||
switch result {
|
||||
case let .messages(messages, chats, users):
|
||||
return (messages, chats, users)
|
||||
case let .messagesSlice(_, _, _, _, messages, chats, users):
|
||||
return (messages, chats, users)
|
||||
case let .channelMessages(_, _, _, _, messages, chats, users):
|
||||
return (messages, chats, users)
|
||||
case .messagesNotModified:
|
||||
return ([], [], [])
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in
|
||||
return .single(([], [], []))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (combineLatest(queue: self.queue, loadSignals)
|
||||
|> mapToSignal { messageLists -> Signal<[Message], NoError> in
|
||||
return account.postbox.transaction { transaction -> [Message] in
|
||||
var parsedMessages: [StoreMessage] = []
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
|
||||
for (messages, chats, users) in messageLists {
|
||||
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 result: [Message] = []
|
||||
for parsedMessage in parsedMessages {
|
||||
switch parsedMessage.id {
|
||||
case let .Id(id):
|
||||
if let message = transaction.getMessage(id) {
|
||||
result.append(message)
|
||||
}
|
||||
case .Partial:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] messages in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
for message in messages {
|
||||
strongSelf.loadedPlaceholders[message.id] = message
|
||||
}
|
||||
if strongSelf.sparseItems != nil {
|
||||
for i in 0 ..< strongSelf.sparseItems!.items.count {
|
||||
switch strongSelf.sparseItems!.items[i] {
|
||||
case let .anchor(id, timestamp, _):
|
||||
if let message = strongSelf.loadedPlaceholders[id] {
|
||||
strongSelf.sparseItems!.items[i] = .anchor(id: id, timestamp: timestamp, message: message)
|
||||
}
|
||||
case .range:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.updateState()
|
||||
})
|
||||
}
|
||||
|
||||
func loadHole(anchor: MessageId, direction: LoadHoleDirection, completion: @escaping () -> Void) {
|
||||
let loadingHole = LoadingHole(anchor: anchor, direction: direction)
|
||||
if self.loadingHole == loadingHole {
|
||||
@ -811,12 +700,23 @@ public final class SparseMessageCalendar {
|
||||
private let queue: Queue
|
||||
private let impl: QueueLocalObject<Impl>
|
||||
|
||||
public var minTimestamp: Int32?
|
||||
private var disposable: Disposable?
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
self.disposable = self.state.start(next: { [weak self] state in
|
||||
self?.minTimestamp = state.minTimestamp
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
public var state: Signal<State, NoError> {
|
||||
|
@ -1814,7 +1814,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
strongSelf.openPeerMention(mention)
|
||||
strongSelf.openPeerMention(mention, sourceMessageId: message?.id)
|
||||
}
|
||||
}),
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
|
||||
@ -9423,7 +9423,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}, recognizedQRCode: { [weak self] code in
|
||||
if let strongSelf = self {
|
||||
if let (host, port, username, password, secret) = parseProxyUrl(code) {
|
||||
strongSelf.openResolved(ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret))
|
||||
strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil)
|
||||
}
|
||||
}
|
||||
}, presentSchedulePicker: { [weak self] done in
|
||||
@ -12019,7 +12019,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}
|
||||
|
||||
private func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default) {
|
||||
private func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default, sourceMessageId: MessageId? = nil) {
|
||||
let _ = self.presentVoiceMessageDiscardAlert(action: {
|
||||
let disposable: MetaDisposable
|
||||
if let resolvePeerByNameDisposable = self.resolvePeerByNameDisposable {
|
||||
@ -12070,7 +12070,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
navigation = .chat(textInputState: nil, subject: nil, peekData: nil)
|
||||
}
|
||||
}
|
||||
strongSelf.openResolved(.peer(peer.id, navigation))
|
||||
strongSelf.openResolved(result: .peer(peer.id, navigation), sourceMessageId: sourceMessageId)
|
||||
} else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
@ -12378,7 +12378,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}))
|
||||
}
|
||||
|
||||
private func openResolved(_ result: ResolvedUrl) {
|
||||
private func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?) {
|
||||
self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(updatedPresentationData: self.updatedPresentationData), navigationController: self.effectiveNavigationController, openPeer: { [weak self] peerId, navigation in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -12387,7 +12387,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case let .chat(_, subject, peekData):
|
||||
if case .peer(peerId) = strongSelf.chatLocation {
|
||||
if let subject = subject, case let .message(messageId, _, timecode) = subject {
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, timecode))
|
||||
strongSelf.navigateToMessage(from: sourceMessageId, to: .id(messageId, timecode))
|
||||
}
|
||||
} else if let navigationController = strongSelf.effectiveNavigationController {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), subject: subject, keepStack: .always, peekData: peekData))
|
||||
@ -12488,7 +12488,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, present: { [weak self] c in
|
||||
self?.present(c, in: .window(.root))
|
||||
}, openResolved: { [weak self] resolved in
|
||||
self?.openResolved(resolved)
|
||||
self?.openResolved(result: resolved, sourceMessageId: message?.id)
|
||||
})
|
||||
}, performAction: true)
|
||||
}
|
||||
|
@ -285,7 +285,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
|
||||
return { presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize in
|
||||
let isPreview = presentationData.isPreview
|
||||
let fontSize: CGFloat = floor(presentationData.fontSize.baseDisplaySize * 15.0 / 17.0)
|
||||
let fontSize: CGFloat
|
||||
if message.adAttribute != nil {
|
||||
fontSize = floor(presentationData.fontSize.baseDisplaySize)
|
||||
} else {
|
||||
fontSize = floor(presentationData.fontSize.baseDisplaySize * 15.0 / 17.0)
|
||||
}
|
||||
|
||||
let titleFont = Font.semibold(fontSize)
|
||||
let textFont = Font.regular(fontSize)
|
||||
|
@ -1104,19 +1104,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if isPreview {
|
||||
needShareButton = false
|
||||
}
|
||||
if item.content.firstMessage.adAttribute != nil {
|
||||
let isAd = item.content.firstMessage.adAttribute != nil
|
||||
if isAd {
|
||||
needShareButton = false
|
||||
}
|
||||
|
||||
var tmpWidth: CGFloat
|
||||
if allowFullWidth {
|
||||
tmpWidth = baseWidth
|
||||
if needShareButton {
|
||||
if needShareButton || isAd {
|
||||
tmpWidth -= 38.0
|
||||
}
|
||||
} else {
|
||||
tmpWidth = layoutConstants.bubble.maximumWidthFill.widthFor(baseWidth)
|
||||
if needShareButton && tmpWidth + 32.0 > baseWidth {
|
||||
if (needShareButton || isAd) && tmpWidth + 32.0 > baseWidth {
|
||||
tmpWidth = baseWidth - 32.0
|
||||
}
|
||||
}
|
||||
@ -2580,7 +2581,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
strongSelf.insertSubnode(shareButtonNode, belowSubnode: strongSelf.messageAccessibilityArea)
|
||||
shareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
} else if let shareButtonNode = strongSelf.shareButtonNode {
|
||||
strongSelf.shareButtonNode = nil
|
||||
shareButtonNode.removeFromSupernode()
|
||||
|
@ -613,10 +613,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
|
||||
updatedFetchControls = FetchControls(fetch: { manual in
|
||||
if let strongSelf = self {
|
||||
if !manual {
|
||||
strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(context: context, photoReference: .message(message: MessageReference(message), media: image), displayAtSize: isSecretMedia ? nil : 600, storeToDownloadsPeerType: storeToDownloadsPeerType).start())
|
||||
} else if let representation = largestRepresentationForPhoto(image) {
|
||||
strongSelf.fetchDisposable.set(messageMediaImageInteractiveFetched(context: context, message: message, image: image, resource: representation.resource, range: representationFetchRangeForDisplayAtSize(representation: representation, dimension: isSecretMedia ? nil : 600), storeToDownloadsPeerType: storeToDownloadsPeerType).start())
|
||||
if let representation = largestRepresentationForPhoto(image) {
|
||||
strongSelf.fetchDisposable.set(messageMediaImageInteractiveFetched(context: context, message: message, image: image, resource: representation.resource, range: representationFetchRangeForDisplayAtSize(representation: representation, dimension: isSecretMedia ? nil : 600), userInitiated: manual, storeToDownloadsPeerType: storeToDownloadsPeerType).start())
|
||||
}
|
||||
}
|
||||
}, cancel: {
|
||||
@ -816,6 +814,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
let arguments = TransformImageArguments(corners: corners, imageSize: drawingSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: isInlinePlayableVideo ? .fill(.black) : .blurBackground, emptyColor: emptyColor, custom: patternArguments)
|
||||
|
||||
let imageFrame = CGRect(origin: CGPoint(x: -arguments.insets.left, y: -arguments.insets.top), size: arguments.drawingSize).ensuredValid
|
||||
let cleanImageFrame = CGRect(origin: imageFrame.origin, size: CGSize(width: imageFrame.width - arguments.corners.extendedEdges.right, height: imageFrame.height))
|
||||
|
||||
let imageApply = imageLayout(arguments)
|
||||
|
||||
@ -860,7 +859,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
}
|
||||
statusApply(hasAnimation)
|
||||
|
||||
let dateAndStatusFrame = CGRect(origin: CGPoint(x: imageFrame.width - layoutConstants.image.statusInsets.right - statusSize.width, y: imageFrame.height - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize)
|
||||
let dateAndStatusFrame = CGRect(origin: CGPoint(x: cleanImageFrame.width - layoutConstants.image.statusInsets.right - statusSize.width, y: cleanImageFrame.height - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize)
|
||||
|
||||
strongSelf.dateAndStatusNode.frame = dateAndStatusFrame
|
||||
strongSelf.dateAndStatusNode.bounds = CGRect(origin: CGPoint(), size: dateAndStatusFrame.size)
|
||||
|
@ -99,8 +99,10 @@ private final class FetchManagerStatusContext {
|
||||
if let originalStatus = self.originalStatus {
|
||||
if originalStatus == .Remote && self.hasEntry {
|
||||
return .Fetching(isActive: false, progress: 0.0)
|
||||
} else {
|
||||
} else if self.hasEntry {
|
||||
return originalStatus
|
||||
} else {
|
||||
return .Remote
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
@ -238,7 +240,14 @@ private final class FetchManagerCategoryContext {
|
||||
|
||||
if (previousPriorityKey != nil) != (updatedPriorityKey != nil) {
|
||||
if let statusContext = self.statusContexts[id] {
|
||||
var hasForegroundPriorityKey = false
|
||||
let previousStatus = statusContext.combinedStatus
|
||||
statusContext.hasEntry = self.entries[id] != nil
|
||||
if let combinedStatus = statusContext.combinedStatus, combinedStatus != previousStatus {
|
||||
for f in statusContext.subscribers.copyItems() {
|
||||
f(combinedStatus)
|
||||
}
|
||||
}
|
||||
/*var hasForegroundPriorityKey = false
|
||||
if let updatedPriorityKey = updatedPriorityKey, let topReference = updatedPriorityKey.topReference {
|
||||
switch topReference {
|
||||
case .userInitiated:
|
||||
@ -270,7 +279,7 @@ private final class FetchManagerCategoryContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1384,7 +1384,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
self.stateTag = tagMaskForType(contentType)
|
||||
|
||||
self.contextGestureContainerNode = ContextControllerSourceNode()
|
||||
self.itemGrid = SparseItemGrid()
|
||||
self.itemGrid = SparseItemGrid(theme: self.context.sharedContext.currentPresentationData.with({ $0 }).theme)
|
||||
self.directMediaImageCache = DirectMediaImageCache(account: context.account)
|
||||
|
||||
let useListItems: Bool
|
||||
@ -2160,7 +2160,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
fixedItemHeight = nil
|
||||
}
|
||||
|
||||
self.itemGrid.update(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), lockScrollingAtTop: isScrollingLockedAtTop, fixedItemHeight: fixedItemHeight, items: items, synchronous: wasFirstTime)
|
||||
self.itemGrid.update(size: size, insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), lockScrollingAtTop: isScrollingLockedAtTop, fixedItemHeight: fixedItemHeight, items: items, theme: self.itemGridBinding.chatPresentationData.theme.theme, synchronous: wasFirstTime)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2201,6 +2201,24 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
if let index = previousIndex {
|
||||
self.itemGrid.scrollToItem(at: index)
|
||||
|
||||
if let item = self.itemGrid.item(at: index) {
|
||||
if let layer = item.layer as? ItemLayer {
|
||||
Queue.mainQueue().after(0.1, { [weak layer] in
|
||||
guard let layer = layer else {
|
||||
return
|
||||
}
|
||||
|
||||
let overlayLayer = ListShimmerLayer.OverlayLayer()
|
||||
overlayLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.6).cgColor
|
||||
overlayLayer.frame = layer.bounds
|
||||
layer.addSublayer(overlayLayer)
|
||||
overlayLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.8, delay: 0.3, removeOnCompletion: false, completion: { [weak overlayLayer] _ in
|
||||
overlayLayer?.removeFromSuperlayer()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6027,6 +6027,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
let _ = (context.account.postbox.combinedView(keys: summaryTags.map { tag in
|
||||
return PostboxViewKey.historyTagSummaryView(tag: tag, peerId: peerId, namespace: Namespaces.Message.Cloud)
|
||||
})
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] views in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -6194,7 +6195,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
initialTimestamp = timestamp
|
||||
|
||||
self.controller?.push(CalendarMessageScreen(context: self.context, peerId: self.peerId, calendarSource: calendarSource, initialTimestamp: initialTimestamp, navigateToDay: { [weak self] c, timestamp in
|
||||
var dismissCalendarScreen: (() -> Void)?
|
||||
|
||||
let calendarScreen = 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
|
||||
@ -6207,7 +6210,60 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
pane.scrollToTimestamp(timestamp: timestamp)
|
||||
|
||||
c.dismiss()
|
||||
}))
|
||||
}, previewDay: { [weak self] index, sourceNode, sourceRect, gesture in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
dismissCalendarScreen?()
|
||||
|
||||
guard let strongSelf = self, let controller = strongSelf.controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
chatController: nil,
|
||||
context: strongSelf.context,
|
||||
chatLocation: .peer(strongSelf.peerId),
|
||||
subject: .message(id: index.id, highlight: false, timecode: nil),
|
||||
botStart: nil,
|
||||
updateTextInputState: nil,
|
||||
activateInput: false,
|
||||
keepStack: .never,
|
||||
useExisting: true,
|
||||
purposefulAction: nil,
|
||||
scrollToEndIfExists: false,
|
||||
activateMessageSearch: nil,
|
||||
peekData: nil,
|
||||
peerNearbyData: nil,
|
||||
reportReason: nil,
|
||||
animated: true,
|
||||
options: [],
|
||||
parentGroupId: nil,
|
||||
chatListFilter: nil,
|
||||
changeColors: false,
|
||||
completion: { _ in
|
||||
}
|
||||
))
|
||||
})))
|
||||
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(id: index.id, highlight: false, timecode: nil), botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(items: items)), gesture: gesture)
|
||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||
})
|
||||
|
||||
self.controller?.push(calendarScreen)
|
||||
dismissCalendarScreen = { [weak calendarScreen] in
|
||||
calendarScreen?.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
@ -7427,21 +7483,26 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
weak var sourceNode: ASDisplayNode?
|
||||
let sourceRect: CGRect
|
||||
|
||||
let navigationController: NavigationController? = nil
|
||||
|
||||
let passthroughTouches: Bool = false
|
||||
let passthroughTouches: Bool
|
||||
|
||||
init(controller: ViewController, sourceNode: ASDisplayNode?) {
|
||||
init(controller: ViewController, sourceNode: ASDisplayNode?, sourceRect: CGRect = CGRect(origin: CGPoint(), size: CGSize()), passthroughTouches: Bool = false) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
self.sourceRect = sourceRect
|
||||
self.passthroughTouches = passthroughTouches
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
||||
let sourceNode = self.sourceNode
|
||||
let sourceRect = self.sourceRect
|
||||
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
||||
if let sourceNode = sourceNode {
|
||||
return (sourceNode, sourceNode.bounds)
|
||||
let rect = sourceRect.isEmpty ? sourceNode.bounds : sourceRect
|
||||
return (sourceNode, rect)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user