Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2021-10-26 02:55:15 +04:00
commit 191eb527c5
19 changed files with 475 additions and 319 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -15,6 +15,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/AnimationUI:AnimationUI",
"//submodules/TelegramPresentationData:TelegramPresentationData",
],
visibility = [
"//visibility:public",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {
}
}
}
}
}*/
}
}

View File

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

View File

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