Improve message editing

This commit is contained in:
Ali 2019-12-30 03:51:42 +04:00
parent 8f2e148ee5
commit 192604e5f6
48 changed files with 1934 additions and 1474 deletions

View File

@ -5240,3 +5240,5 @@ Any member of this group will be able to see messages in the channel.";
"StickerPackActionInfo.RemovedTitle" = "Stickers Removed";
"StickerPackActionInfo.ArchivedTitle" = "Stickers Archived";
"StickerPackActionInfo.RemovedText" = "%@ is no longer in your stickers.";
"Conversation.ContextMenuCancelEditing" = "Cancel Editing";

View File

@ -2,6 +2,6 @@
@interface TGPhotoVideoEditor : NSObject
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion;
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
@end

View File

@ -10,7 +10,7 @@
@implementation TGPhotoVideoEditor
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSString *)caption entities:(NSArray *)entities withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item recipientName:(NSString *)recipientName completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
{
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];
id<LegacyComponentsContext> windowContext = [windowManager context];
@ -78,13 +78,15 @@
if (completion != nil)
completion(item.asset, editingContext);
[UIView animateWithDuration:0.3f delay:0.0f options:(7 << 16) animations:^
[strongController dismissWhenReadyAnimated:true];
/*[UIView animateWithDuration:0.3f delay:0.0f options:(7 << 16) animations:^
{
strongController.view.frame = CGRectOffset(strongController.view.frame, 0, strongController.view.frame.size.height);
} completion:^(__unused BOOL finished)
{
[strongController dismiss];
}];
}];*/
};
galleryController.beginTransitionIn = ^UIView *(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView)
@ -107,6 +109,9 @@
if ([window isKindOfClass:[TGOverlayControllerWindow class]])
[window dismiss];
}
if (dismissed) {
dismissed();
}
};
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:galleryController];

View File

@ -26,6 +26,7 @@ static_library(
"//submodules/MimeTypes:MimeTypes",
"//submodules/LocalMediaResources:LocalMediaResources",
"//submodules/SearchPeerMembers:SearchPeerMembers",
"//submodules/SaveToCameraRoll:SaveToCameraRoll",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -10,6 +10,7 @@ import TelegramPresentationData
import DeviceAccess
import AccountContext
import LegacyUI
import SaveToCameraRoll
public func defaultVideoPresetForContext(_ context: AccountContext) -> TGMediaVideoConversionPreset {
var networkType: NetworkType = .wifi
@ -52,17 +53,13 @@ public func defaultVideoPresetForContext(_ context: AccountContext) -> TGMediaVi
}
}
public struct LegacyAttachmentMenuMediaEditing: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let imageOrVideo = LegacyAttachmentMenuMediaEditing(rawValue: 1 << 0)
public enum LegacyAttachmentMenuMediaEditing {
case none
case imageOrVideo(AnyMediaReference?)
case file
}
public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void) -> TGMenuSheetController {
public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController {
let defaultVideoPreset = defaultVideoPresetForContext(context)
UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0")
@ -81,11 +78,19 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO
var editing = false
var canSendImageOrVideo = false
var canEditCurrent = false
if let editMediaOptions = editMediaOptions, editMediaOptions.contains(.imageOrVideo) {
var canEditFile = false
var editCurrentMedia: AnyMediaReference?
if let editMediaOptions = editMediaOptions {
switch editMediaOptions {
case .none:
break
case let .imageOrVideo(anyReference):
editCurrentMedia = anyReference
case .file:
canEditFile = true
}
canSendImageOrVideo = true
editing = true
canEditCurrent = true
} else {
canSendImageOrVideo = true
}
@ -165,7 +170,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO
}
if !editing {
let fileItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_File, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: {[weak controller] in
let fileItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_File, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
controller?.dismiss(animated: true)
openFileGallery()
})!
@ -173,14 +178,113 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO
underlyingViews.append(fileItem)
}
if canEditCurrent {
let fileItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_File, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: {[weak controller] in
if canEditFile {
let fileItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_File, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
controller?.dismiss(animated: true)
openFileGallery()
})!
itemViews.append(fileItem)
}
if let editCurrentMedia = editCurrentMedia {
let title: String
if editCurrentMedia.media is TelegramMediaImage {
title = presentationData.strings.Conversation_EditingMessageMediaEditCurrentPhoto
} else {
title = presentationData.strings.Conversation_EditingMessageMediaEditCurrentVideo
}
let editCurrentItem = TGMenuSheetButtonItemView(title: title, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
controller?.dismiss(animated: true)
let _ = (fetchMediaData(context: context, postbox: context.account.postbox, mediaReference: editCurrentMedia)
|> deliverOnMainQueue).start(next: { (value, isImage) in
guard case let .data(data) = value, data.complete else {
return
}
let item: TGMediaEditableItem & TGMediaSelectableItem
if let image = UIImage(contentsOfFile: data.path) {
item = TGCameraCapturedPhoto(existing: image)
} else {
item = TGCameraCapturedVideo(url: URL(fileURLWithPath: data.path))
}
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
legacyController.statusBar.statusBarStyle = .Ignore
legacyController.controllerLoaded = { [weak legacyController] in
legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true
}
let emptyController = LegacyEmptyController(context: legacyController.context)!
emptyController.navigationBarShouldBeHidden = true
let navigationController = makeLegacyNavigationController(rootController: emptyController)
navigationController.setNavigationBarHidden(true, animated: false)
legacyController.bind(controller: navigationController)
var hasTimer = false
var hasSilentPosting = false
if peer.id != context.account.peerId {
if peer is TelegramUser {
hasTimer = true
}
hasSilentPosting = true
}
let recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
legacyController.enableSizeClassSignal = true
let presentationDisposable = context.sharedContext.presentationData.start(next: { [weak legacyController] presentationData in
if let legacyController = legacyController, let controller = legacyController.legacyController as? TGMenuSheetController {
controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme)
}
})
legacyController.disposables.add(presentationDisposable)
present(legacyController, nil)
TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: "", entities: [], withItem: item, recipientName: recipientName, completion: { result, editingContext in
let intent: TGMediaAssetsControllerIntent = TGMediaAssetsControllerSendMediaIntent
let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: result as! TGMediaSelectableItem, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: legacyAssetPickerItemGenerator())
sendMessagesWithSignals(signals, false, 0)
/*
[TGCameraController resultSignalsForSelectionContext:nil editingContext:editingContext currentItem:result storeAssets:false saveEditedPhotos:false descriptionGenerator:^id(id result, NSString *caption, NSArray *entities, NSString *hash)
{
__strong TGModernConversationController *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
NSDictionary *desc = [strongSelf _descriptionForItem:result caption:caption entities:entities hash:hash allowRemoteCache:allowRemoteCache];
return [strongSelf _descriptionForReplacingMedia:desc message:message];
}]]
*/
//let signals = TGMediaAssetsController.resultSignals(for: nil, editingContext: editingContext, intent: intent, currentItem: result, storeAssets: true, useMediaCache: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: saveEditedPhotos)
//sendMessagesWithSignals(signals, silentPosting, scheduleTime)
}, dismissed: { [weak legacyController] in
legacyController?.dismiss()
})
})
/*
bool allowRemoteCache = [strongSelf->_companion controllerShouldCacheServerAssets];
[TGPhotoVideoEditor presentWithContext:[TGLegacyComponentsContext shared] controller:strongSelf caption:text entities:entities withItem:item recipientName:[strongSelf->_companion title] completion:^(id result, TGMediaEditingContext *editingContext)
{
[strongSelf _asyncProcessMediaAssetSignals:[TGCameraController resultSignalsForSelectionContext:nil editingContext:editingContext currentItem:result storeAssets:false saveEditedPhotos:false descriptionGenerator:^id(id result, NSString *caption, NSArray *entities, NSString *hash)
{
__strong TGModernConversationController *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
NSDictionary *desc = [strongSelf _descriptionForItem:result caption:caption entities:entities hash:hash allowRemoteCache:allowRemoteCache];
return [strongSelf _descriptionForReplacingMedia:desc message:message];
}]];
[strongSelf endMessageEditing:true];
}];
*/
})!
itemViews.append(editCurrentItem)
}
if editMediaOptions == nil {
let locationItem = TGMenuSheetButtonItemView(title: presentationData.strings.Conversation_Location, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
controller?.dismiss(animated: true)

View File

@ -219,7 +219,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?
asFile = true
}
let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString
let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path
if let url = url, let previewImage = dict["previewImage"] as? UIImage {
let dimensions = previewImage.pixelSize()

View File

@ -10,12 +10,12 @@ import MobileCoreServices
import DeviceAccess
import AccountContext
private enum SaveToCameraRollState {
public enum FetchMediaDataState {
case progress(Float)
case data(MediaResourceData)
}
private func fetchMediaData(context: AccountContext, postbox: Postbox, mediaReference: AnyMediaReference) -> Signal<(SaveToCameraRollState, Bool), NoError> {
public func fetchMediaData(context: AccountContext, postbox: Postbox, mediaReference: AnyMediaReference) -> Signal<(FetchMediaDataState, Bool), NoError> {
var resource: MediaResource?
var isImage = true
var fileExtension: String?
@ -46,7 +46,7 @@ private func fetchMediaData(context: AccountContext, postbox: Postbox, mediaRefe
}
if let resource = resource {
let fetchedData: Signal<SaveToCameraRollState, NoError> = Signal { subscriber in
let fetchedData: Signal<FetchMediaDataState, NoError> = Signal { subscriber in
let fetched = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: mediaReference.resourceReference(resource)).start()
let status = postbox.mediaBox.resourceStatus(resource).start(next: { status in
switch status {

View File

@ -834,6 +834,7 @@ public class Account {
public private(set) var callSessionManager: CallSessionManager!
public private(set) var viewTracker: AccountViewTracker!
public private(set) var pendingMessageManager: PendingMessageManager!
public private(set) var pendingUpdateMessageManager: PendingUpdateMessageManager!
public private(set) var messageMediaPreuploadManager: MessageMediaPreuploadManager!
private(set) var mediaReferenceRevalidationContext: MediaReferenceRevalidationContext!
private var peerInputActivityManager: PeerInputActivityManager!
@ -918,6 +919,7 @@ public class Account {
self.messageMediaPreuploadManager = MessageMediaPreuploadManager()
self.mediaReferenceRevalidationContext = MediaReferenceRevalidationContext()
self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, accountPeerId: peerId, auxiliaryMethods: auxiliaryMethods, stateManager: self.stateManager, localInputActivityManager: self.localInputActivityManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.mediaReferenceRevalidationContext)
self.pendingUpdateMessageManager = PendingUpdateMessageManager(postbox: postbox, network: network, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext)
self.network.loggedOut = { [weak self] in
Logger.shared.log("Account", "network logged out")
@ -1246,4 +1248,5 @@ public func setupAccount(_ account: Account, fetchCachedResourceRepresentation:
account.transformOutgoingMessageMedia = transformOutgoingMessageMedia
account.pendingMessageManager.transformOutgoingMessageMedia = transformOutgoingMessageMedia
account.pendingUpdateMessageManager.transformOutgoingMessageMedia = transformOutgoingMessageMedia
}

View File

@ -57,7 +57,8 @@ public final class AccountStateManager {
private let shouldKeepOnlinePresence: Signal<Bool, NoError>
private let peerInputActivityManager: PeerInputActivityManager
private let auxiliaryMethods: AccountAuxiliaryMethods
let auxiliaryMethods: AccountAuxiliaryMethods
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
private var updateService: UpdateMessageService?
private let updateServiceDisposable = MetaDisposable()

View File

@ -5,7 +5,7 @@ import SwiftSignalKit
import SyncCore
func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox) {
func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force: Bool) {
if let fromImage = from as? TelegramMediaImage, let toImage = to as? TelegramMediaImage {
let fromSmallestRepresentation = smallestImageRepresentation(fromImage.representations)
if let fromSmallestRepresentation = fromSmallestRepresentation, let toSmallestRepresentation = smallestImageRepresentation(toImage.representations) {
@ -23,7 +23,7 @@ func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox) {
if let fromPreview = smallestImageRepresentation(fromFile.previewRepresentations), let toPreview = smallestImageRepresentation(toFile.previewRepresentations) {
postbox.mediaBox.moveResourceData(from: fromPreview.resource.id, to: toPreview.resource.id)
}
if (fromFile.size == toFile.size || fromFile.resource.size == toFile.resource.size) && fromFile.mimeType == toFile.mimeType {
if (force || fromFile.size == toFile.size || fromFile.resource.size == toFile.resource.size) && fromFile.mimeType == toFile.mimeType {
postbox.mediaBox.moveResourceData(from: fromFile.resource.id, to: toFile.resource.id)
}
}
@ -152,7 +152,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
}
if let fromMedia = currentMessage.media.first, let toMedia = media.first {
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox)
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox, force: false)
}
if forwardInfo == nil {
@ -311,7 +311,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
}
if let fromMedia = currentMessage.media.first, let toMedia = media.first {
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox)
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox, force: false)
}
if storeForwardInfo == nil {

View File

@ -36,4 +36,8 @@ public final class ChatUpdatingMessageMedia: Equatable {
}
return true
}
func withProgress(_ progress: Float) -> ChatUpdatingMessageMedia {
return ChatUpdatingMessageMedia(text: self.text, entities: self.entities, disableUrlPreview: self.disableUrlPreview, media: self.media, progress: progress)
}
}

View File

@ -1415,7 +1415,7 @@ private func sendMessage(auxiliaryMethods: AccountAuxiliaryMethods, postbox: Pos
}
if let toMedia = toMedia {
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox)
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox, force: false)
}
}

View File

@ -0,0 +1,178 @@
import Foundation
import SwiftSignalKit
import SyncCore
import Postbox
private final class PendingUpdateMessageContext {
var value: ChatUpdatingMessageMedia
let disposable: Disposable
init(value: ChatUpdatingMessageMedia, disposable: Disposable) {
self.value = value
self.disposable = disposable
}
}
private final class PendingUpdateMessageManagerImpl {
let queue: Queue
let postbox: Postbox
let network: Network
let stateManager: AccountStateManager
let messageMediaPreuploadManager: MessageMediaPreuploadManager
let mediaReferenceRevalidationContext: MediaReferenceRevalidationContext
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
private var updatingMessageMediaValue: [MessageId: ChatUpdatingMessageMedia] = [:] {
didSet {
if self.updatingMessageMediaValue != oldValue {
self.updatingMessageMediaPromise.set(.single(self.updatingMessageMediaValue))
}
}
}
private let updatingMessageMediaPromise = Promise<[MessageId: ChatUpdatingMessageMedia]>()
var updatingMessageMedia: Signal<[MessageId: ChatUpdatingMessageMedia], NoError> {
return self.updatingMessageMediaPromise.get()
}
private var contexts: [MessageId: PendingUpdateMessageContext] = [:]
private let errorsPipe = ValuePipe<(MessageId, RequestEditMessageError)>()
var errors: Signal<(MessageId, RequestEditMessageError), NoError> {
return self.errorsPipe.signal()
}
init(queue: Queue, postbox: Postbox, network: Network, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext) {
self.queue = queue
self.postbox = postbox
self.network = network
self.stateManager = stateManager
self.messageMediaPreuploadManager = messageMediaPreuploadManager
self.mediaReferenceRevalidationContext = mediaReferenceRevalidationContext
self.updatingMessageMediaPromise.set(.single(self.updatingMessageMediaValue))
}
deinit {
for (_, context) in self.contexts {
context.disposable.dispose()
}
}
private func updateValues() {
self.updatingMessageMediaValue = self.contexts.mapValues { context in
return context.value
}
}
func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool) {
if let context = self.contexts[messageId] {
self.contexts.removeValue(forKey: messageId)
context.disposable.dispose()
}
let disposable = MetaDisposable()
let context = PendingUpdateMessageContext(value: ChatUpdatingMessageMedia(text: text, entities: entities, disableUrlPreview: disableUrlPreview, media: media, progress: 0.0), disposable: disposable)
self.contexts[messageId] = context
let queue = self.queue
disposable.set((requestEditMessage(postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, disableUrlPreview: disableUrlPreview, scheduleTime: nil)
|> deliverOn(self.queue)).start(next: { [weak self, weak context] value in
queue.async {
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[messageId], context === initialContext {
switch value {
case .done:
strongSelf.contexts.removeValue(forKey: messageId)
context.disposable.dispose()
strongSelf.updateValues()
case let .progress(progress):
context.value = context.value.withProgress(progress)
strongSelf.updateValues()
}
}
}
}, error: { [weak self, weak context] error in
queue.async {
guard let strongSelf = self, let initialContext = context else {
return
}
if let context = strongSelf.contexts[messageId], context === initialContext {
strongSelf.contexts.removeValue(forKey: messageId)
context.disposable.dispose()
strongSelf.updateValues()
}
strongSelf.errorsPipe.putNext((messageId, error))
}
}))
}
func cancel(messageId: MessageId) {
if let context = self.contexts[messageId] {
self.contexts.removeValue(forKey: messageId)
context.disposable.dispose()
self.updateValues()
}
}
}
public final class PendingUpdateMessageManager {
private let queue = Queue()
private let impl: QueueLocalObject<PendingUpdateMessageManagerImpl>
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia? {
didSet {
let transformOutgoingMessageMedia = self.transformOutgoingMessageMedia
self.impl.with { impl in
impl.transformOutgoingMessageMedia = transformOutgoingMessageMedia
}
}
}
public var updatingMessageMedia: Signal<[MessageId: ChatUpdatingMessageMedia], NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.updatingMessageMedia.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
public var errors: Signal<(MessageId, RequestEditMessageError), NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.errors.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
init(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext) {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return PendingUpdateMessageManagerImpl(queue: queue, postbox: postbox, network: network, stateManager: stateManager, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext)
})
}
public func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute? = nil, disableUrlPreview: Bool = false) {
self.impl.with { impl in
impl.add(messageId: messageId, text: text, media: media, entities: entities, disableUrlPreview: disableUrlPreview)
}
}
public func cancel(messageId: MessageId) {
self.impl.with { impl in
impl.cancel(messageId: messageId)
}
}
}

View File

@ -27,10 +27,14 @@ public enum RequestEditMessageError {
}
public func requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute? = nil, disableUrlPreview: Bool = false, scheduleTime: Int32? = nil) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
return requestEditMessageInternal(account: account, messageId: messageId, text: text, media: media, entities: entities, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: false)
return requestEditMessage(postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime)
}
func requestEditMessage(postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
return requestEditMessageInternal(postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: false)
|> `catch` { error -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> in
if case .invalidReference = error {
return requestEditMessageInternal(account: account, messageId: messageId, text: text, media: media, entities: entities, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: true)
return requestEditMessageInternal(postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: true)
} else {
return .fail(error)
}
@ -45,34 +49,34 @@ public func requestEditMessage(account: Account, messageId: MessageId, text: Str
}
}
private func requestEditMessageInternal(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, forceReupload: Bool) -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> {
private func requestEditMessageInternal(postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, forceReupload: Bool) -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> {
let uploadedMedia: Signal<PendingMessageUploadedContentResult?, NoError>
switch media {
case .keep:
uploadedMedia = .single(.progress(0.0))
|> then(.single(nil))
case let .update(media):
let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
let augmentedMedia = augmentMediaWithReference(media)
return mediaContentToUpload(network: account.network, postbox: account.postbox, auxiliaryMethods: account.auxiliaryMethods, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, revalidationContext: account.mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveAttribute: nil, messageId: nil, attributes: [])
}
if let uploadSignal = generateUploadSignal(forceReupload) {
uploadedMedia = .single(.progress(0.027))
|> then(uploadSignal)
|> map { result -> PendingMessageUploadedContentResult? in
switch result {
case let .progress(value):
return .progress(max(value, 0.027))
case let .content(content):
return .content(content)
}
case .keep:
uploadedMedia = .single(.progress(0.0))
|> then(.single(nil))
case let .update(media):
let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
let augmentedMedia = augmentMediaWithReference(media)
return mediaContentToUpload(network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveAttribute: nil, messageId: nil, attributes: [])
}
if let uploadSignal = generateUploadSignal(forceReupload) {
uploadedMedia = .single(.progress(0.027))
|> then(uploadSignal)
|> map { result -> PendingMessageUploadedContentResult? in
switch result {
case let .progress(value):
return .progress(max(value, 0.027))
case let .content(content):
return .content(content)
}
|> `catch` { _ -> Signal<PendingMessageUploadedContentResult?, NoError> in
return .single(nil)
}
} else {
uploadedMedia = .single(nil)
}
|> `catch` { _ -> Signal<PendingMessageUploadedContentResult?, NoError> in
return .single(nil)
}
} else {
uploadedMedia = .single(nil)
}
}
return uploadedMedia
|> mapError { _ -> RequestEditMessageInternalError in return .error(.generic) }
@ -86,7 +90,7 @@ private func requestEditMessageInternal(account: Account, messageId: MessageId,
pendingMediaContent = content.content
}
}
return account.postbox.transaction { transaction -> (Peer?, Message?, SimpleDictionary<PeerId, Peer>) in
return postbox.transaction { transaction -> (Peer?, Message?, SimpleDictionary<PeerId, Peer>) in
guard let message = transaction.getMessage(messageId) else {
return (nil, nil, SimpleDictionary())
}
@ -155,7 +159,7 @@ private func requestEditMessageInternal(account: Account, messageId: MessageId,
flags |= Int32(1 << 15)
}
return account.network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime))
return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime))
|> map { result -> Api.Updates? in
return result
}
@ -176,16 +180,58 @@ private func requestEditMessageInternal(account: Account, messageId: MessageId,
}
|> mapToSignal { result -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> in
if let result = result {
return account.postbox.transaction { transaction -> RequestEditMessageResult in
return postbox.transaction { transaction -> RequestEditMessageResult in
var toMedia: Media?
if let message = result.messages.first.flatMap({ StoreMessage(apiMessage: $0) }) {
toMedia = message.media.first
}
if case let .update(fromMedia) = media, let toMedia = toMedia {
applyMediaResourceChanges(from: fromMedia.media, to: toMedia, postbox: account.postbox)
applyMediaResourceChanges(from: fromMedia.media, to: toMedia, postbox: postbox, force: true)
}
account.stateManager.addUpdates(result)
switch result {
case let .updates(updates, users, chats, _, _):
for update in updates {
switch update {
case .updateEditMessage(let message, _, _), .updateNewMessage(let message, _, _), .updateEditChannelMessage(let message, _, _), .updateNewChannelMessage(let message, _, _):
var peers: [Peer] = []
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)
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated in updated })
if let message = StoreMessage(apiMessage: message), case let .Id(id) = message.id {
transaction.updateMessage(id, update: { previousMessage in
var updatedFlags = message.flags
var updatedLocalTags = message.localTags
if previousMessage.localTags.contains(.OutgoingLiveLocation) {
updatedLocalTags.insert(.OutgoingLiveLocation)
}
if previousMessage.flags.contains(.Incoming) {
updatedFlags.insert(.Incoming)
} else {
updatedFlags.remove(.Incoming)
}
return .update(message.withUpdatedLocalTags(updatedLocalTags).withUpdatedFlags(updatedFlags))
})
}
default:
break
}
}
default:
break
}
stateManager.addUpdates(result)
return .done(true)
}

View File

@ -186,6 +186,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private let messageActionCallbackDisposable = MetaDisposable()
private let messageActionUrlAuthDisposable = MetaDisposable()
private let editMessageDisposable = MetaDisposable()
private let editMessageErrorsDisposable = MetaDisposable()
private let enqueueMediaMessageDisposable = MetaDisposable()
private var resolvePeerByNameDisposable: MetaDisposable?
private var shareStatusDisposable: MetaDisposable?
@ -2349,6 +2350,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.messageActionCallbackDisposable.dispose()
self.messageActionUrlAuthDisposable.dispose()
self.editMessageDisposable.dispose()
self.editMessageErrorsDisposable.dispose()
self.enqueueMediaMessageDisposable.dispose()
self.resolvePeerByNameDisposable?.dispose()
self.shareStatusDisposable?.dispose()
@ -2873,10 +2875,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self, let editMessageState = strongSelf.presentationInterfaceState.editMessageState, case let .media(options) = editMessageState.content else {
return
}
strongSelf.presentAttachmentMenu(editMediaOptions: options)
var originalMediaReference: AnyMediaReference?
if let message = message {
for media in message.media {
if let image = media as? TelegramMediaImage {
originalMediaReference = .message(message: MessageReference(message), media: image)
} else if let file = media as? TelegramMediaFile {
if file.isVideo || file.isAnimated {
originalMediaReference = .message(message: MessageReference(message), media: file)
}
}
}
}
strongSelf.presentAttachmentMenu(editMediaOptions: options, editMediaReference: originalMediaReference)
})
} else {
strongSelf.presentAttachmentMenu(editMediaOptions: nil)
strongSelf.presentAttachmentMenu(editMediaOptions: nil, editMediaReference: nil)
}
}
self.chatDisplayNode.paste = { [weak self] data in
@ -3004,7 +3018,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
state = state.updatedEditMessageState(nil)
return state
}, completion: completion)
strongSelf.editMessageDisposable.set(nil)
return
}
@ -3217,41 +3230,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
media = .keep
}
strongSelf.editMessageDisposable.set((requestEditMessage(account: strongSelf.context.account, messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, disableUrlPreview: disableUrlPreview) |> deliverOnMainQueue |> afterDisposed({
editingMessage.set(nil)
})).start(next: { result in
guard let strongSelf = self else {
return
}
switch result {
case let .progress(value):
editingMessage.set(value)
case .done:
editingMessage.set(nil)
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
var state = state
state = state.updatedInterfaceState({ $0.withUpdatedEditMessage(nil) })
state = state.updatedEditMessageState(nil)
return state
})
}
}, error: { error in
guard let strongSelf = self else {
return
}
editingMessage.set(nil)
let text: String
switch error {
case .generic:
text = strongSelf.presentationData.strings.Channel_EditMessageErrorGeneric
case .restricted:
text = strongSelf.presentationData.strings.Group_ErrorSendRestrictedMedia
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}))
strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, disableUrlPreview: disableUrlPreview)
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
var state = state
state = state.updatedInterfaceState({ $0.withUpdatedEditMessage(nil) })
state = state.updatedEditMessageState(nil)
return state
})
}
}, beginMessageSearch: { [weak self] domain, query in
guard let strongSelf = self else {
@ -4569,6 +4555,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
}
self.editMessageErrorsDisposable.set((self.context.account.pendingUpdateMessageManager.errors
|> deliverOnMainQueue).start(next: { [weak self] (_, error) in
guard let strongSelf = self else {
return
}
let text: String
switch error {
case .generic:
text = strongSelf.presentationData.strings.Channel_EditMessageErrorGeneric
case .restricted:
text = strongSelf.presentationData.strings.Group_ErrorSendRestrictedMedia
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
})]), in: .window(.root))
}))
}
}
@ -5426,7 +5429,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
private func presentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?) {
private func presentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) {
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in
let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings) as? GeneratedMediaStoreSettings
return entry ?? GeneratedMediaStoreSettings.defaultSettings
@ -5508,9 +5511,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
let menuEditMediaOptions = editMediaOptions.flatMap { options -> LegacyAttachmentMenuMediaEditing in
var result: LegacyAttachmentMenuMediaEditing = []
var result: LegacyAttachmentMenuMediaEditing = .none
if options.contains(.imageOrVideo) {
result.insert(.imageOrVideo)
result = .imageOrVideo(editMediaReference)
}
return result
}
@ -5619,6 +5622,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
})
}
}, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
})
controller.didDismiss = { [weak legacyController] _ in
legacyController?.dismiss()

View File

@ -217,7 +217,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.inputContextPanelContainer = ChatControllerTitlePanelNodeContainer()
self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), updatingMedia: .single([:]))
self.historyNode = ChatHistoryListNode(context: context, chatLocation: chatLocation, tagMask: nil, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get())
self.historyNode.rotated = true
self.historyNodeContainer = ASDisplayNode()
self.historyNodeContainer.addSubnode(self.historyNode)

View File

@ -5,7 +5,7 @@ import SyncCore
import TemporaryCachedPeerDataManager
import Emoji
func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, reverse: Bool, groupMessages: Bool, selectedMessages: Set<MessageId>?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, associatedData: ChatMessageItemAssociatedData) -> [ChatHistoryEntry] {
func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, includeUnreadEntry: Bool, includeEmptyEntry: Bool, includeChatInfoEntry: Bool, includeSearchEntry: Bool, reverse: Bool, groupMessages: Bool, selectedMessages: Set<MessageId>?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, associatedData: ChatMessageItemAssociatedData, updatingMedia: [MessageId: ChatUpdatingMessageMedia]) -> [ChatHistoryEntry] {
if historyAppearsCleared {
return []
}
@ -65,7 +65,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
} else {
selection = .none
}
groupBucket.append((entry.message, entry.isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint)))
groupBucket.append((entry.message, entry.isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[entry.message.id])))
} else {
let selection: ChatHistoryMessageSelection
if let selectedMessages = selectedMessages {
@ -73,7 +73,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
} else {
selection = .none
}
entries.append(.MessageEntry(entry.message, presentationData, entry.isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint)))
entries.append(.MessageEntry(entry.message, presentationData, entry.isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[entry.message.id])))
}
} else {
let selection: ChatHistoryMessageSelection
@ -82,7 +82,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
} else {
selection = .none
}
entries.append(.MessageEntry(entry.message, presentationData, entry.isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint)))
entries.append(.MessageEntry(entry.message, presentationData, entry.isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[entry.message.id])))
}
}

View File

@ -37,17 +37,20 @@ public struct ChatMessageEntryAttributes: Equatable {
let rank: CachedChannelAdminRank?
let isContact: Bool
let contentTypeHint: ChatMessageEntryContentType
let updatingMedia: ChatUpdatingMessageMedia?
init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType) {
init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType, updatingMedia: ChatUpdatingMessageMedia?) {
self.rank = rank
self.isContact = isContact
self.contentTypeHint = contentTypeHint
self.updatingMedia = updatingMedia
}
public init() {
self.rank = nil
self.isContact = false
self.contentTypeHint = .generic
self.updatingMedia = nil
}
}

View File

@ -305,7 +305,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode {
}
let associatedData = ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: false)
let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(location: .peer(peerId), view: view, includeUnreadEntry: false, includeEmptyEntry: false, includeChatInfoEntry: false, includeSearchEntry: false, reverse: false, groupMessages: false, selectedMessages: nil, presentationData: chatPresentationData, historyAppearsCleared: false, associatedData: associatedData), associatedData: associatedData, id: id)
let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(location: .peer(peerId), view: view, includeUnreadEntry: false, includeEmptyEntry: false, includeChatInfoEntry: false, includeSearchEntry: false, reverse: false, groupMessages: false, selectedMessages: nil, presentationData: chatPresentationData, historyAppearsCleared: false, associatedData: associatedData, updatingMedia: [:]), associatedData: associatedData, id: id)
let previous = previousView.swap(processedView)
let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: false, chatLocation: .peer(peerId), controllerInteraction: controllerInteraction, scrollPosition: scrollPosition, initialData: nil, keyboardButtonsMessage: nil, cachedData: nil, cachedDataMessages: nil, readStateData: nil, flashIndicators: flashIndicators, updatedMessageSelection: false)

View File

@ -496,7 +496,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private var loadedMessagesFromCachedDataDisposable: Disposable?
public init(context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, NoError>, updatingMedia: Signal<[MessageId: ChatUpdatingMessageMedia], NoError>, mode: ChatHistoryListMode = .bubbles) {
public init(context: AccountContext, chatLocation: ChatLocation, tagMask: MessageTags?, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles) {
self.context = context
self.chatLocation = chatLocation
self.subject = subject
@ -624,6 +624,22 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let nextTransitionVersion = Atomic<Int>(value: 0)
let updatingMedia = context.account.pendingUpdateMessageManager.updatingMessageMedia
|> map { value -> [MessageId: ChatUpdatingMessageMedia] in
var result = value
for id in value.keys {
if case let .peer(peerId) = chatLocation {
if id.peerId != peerId {
result.removeValue(forKey: id)
}
} else {
result.removeValue(forKey: id)
}
}
return result
}
|> distinctUntilChanged
let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue,
historyViewUpdate,
self.chatPresentationDataPromise.get(),
@ -710,7 +726,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, isScheduledMessages: isScheduledMessages)
let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, associatedData: associatedData), associatedData: associatedData, id: id)
let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(location: chatLocation, view: view, includeUnreadEntry: mode == .bubbles, includeEmptyEntry: mode == .bubbles && tagMask == nil, includeChatInfoEntry: mode == .bubbles, includeSearchEntry: includeSearchEntry && tagMask != nil, reverse: reverse, groupMessages: mode == .bubbles, selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, associatedData: associatedData, updatingMedia: updatingMedia), associatedData: associatedData, id: id)
let previousValueAndVersion = previousView.swap((processedView, update.1, selectedMessages))
let previous = previousValueAndVersion?.0
let previousSelectedMessages = previousValueAndVersion?.2
@ -1235,7 +1251,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
public func isMessageVisibleOnScreen(_ id: MessageId) -> Bool {
var result = false
self.forEachItemNode({ itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, item.content.contains(where: { $0.id == id }) {
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, item.content.contains(where: { $0.0.id == id }) {
if self.itemNodeVisibleInsideInsets(itemNode) {
result = true
}
@ -1612,7 +1628,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
var messageItem: ChatMessageItem?
self.forEachItemNode({ itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
for message in item.content {
for (message, _) in item.content {
if message.id == id {
messageItem = item
break

View File

@ -253,8 +253,6 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
return .single([])
}
let dataSignal: Signal<MessageContextMenuData, NoError>
var loadStickerSaveStatus: MediaId?
var loadCopyMediaResource: MediaResource?
var isAction = false
@ -346,20 +344,27 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
return transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
}
dataSignal = combineLatest(loadLimits, loadStickerSaveStatusSignal, loadResourceStatusSignal, context.sharedContext.chatAvailableMessageActions(postbox: context.account.postbox, accountPeerId: context.account.peerId, messageIds: Set(messages.map { $0.id })))
|> map { limitsConfiguration, stickerSaveStatus, resourceStatus, messageActions -> MessageContextMenuData in
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia]), NoError> = combineLatest(
loadLimits,
loadStickerSaveStatusSignal,
loadResourceStatusSignal,
context.sharedContext.chatAvailableMessageActions(postbox: context.account.postbox, accountPeerId: context.account.peerId, messageIds: Set(messages.map { $0.id })),
context.account.pendingUpdateMessageManager.updatingMessageMedia
|> take(1)
)
|> map { limitsConfiguration, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia]) in
var canEdit = false
if !isAction {
let message = messages[0]
canEdit = canEditMessage(context: context, limitsConfiguration: limitsConfiguration, message: message)
}
return MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions)
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia)
}
return dataSignal
|> deliverOnMainQueue
|> map { data -> [ContextMenuItem] in
|> map { data, updatingMessageMedia -> [ContextMenuItem] in
var actions: [ContextMenuItem] = []
if let starStatus = data.starStatus {
@ -688,11 +693,28 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
clearCacheAsDelete = true
}
if (!data.messageActions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty || clearCacheAsDelete) && !isAction {
let title = message.flags.isSending ? chatPresentationInterfaceState.strings.Conversation_ContextMenuCancelSending : chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete
let title: String
var isSending = false
var isEditing = false
if updatingMessageMedia[message.id] != nil {
isSending = true
isEditing = true
title = chatPresentationInterfaceState.strings.Conversation_ContextMenuCancelEditing
} else if message.flags.isSending {
isSending = true
title = chatPresentationInterfaceState.strings.Conversation_ContextMenuCancelSending
} else {
title = chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete
}
actions.append(.action(ContextMenuActionItem(text: title, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: message.flags.isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
return generateTintedImage(image: UIImage(bundleImageName: isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { controller, f in
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
if isEditing {
context.account.pendingUpdateMessageManager.cancel(messageId: message.id)
f(.default)
} else {
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
}
})))
}

View File

@ -268,7 +268,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
self.addSubnode(self.statusNode)
}
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
let textAsyncLayout = TextNode.asyncLayout(self.textNode)
let currentImage = self.media as? TelegramMediaImage
let imageLayout = self.inlineImageNode.asyncLayout()
@ -281,7 +281,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
let currentAdditionalImageBadgeNode = self.additionalImageBadgeNode
return { presentationData, automaticDownloadSettings, associatedData, context, controllerInteraction, message, messageRead, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, constrainedSize in
return { presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, constrainedSize in
let fontSize: CGFloat = floor(presentationData.fontSize.baseDisplaySize * 15.0 / 17.0)
let titleFont = Font.semibold(fontSize)
@ -309,6 +309,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
var contentMode: InteractiveMediaNodeContentMode = preferMediaAspectFilled ? .aspectFill : .aspectFit
var edited = false
if attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
for attribute in message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
@ -407,12 +410,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
if let (media, flags) = mediaAndFlags {
if let file = media as? TelegramMediaFile {
if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 {
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, file, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, file, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else if file.isInstantVideo {
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file)
let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, presentationData: presentationData, associatedData: associatedData), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 212.0, height: 212.0), .bubble, automaticDownload)
let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, presentationData: presentationData, associatedData: associatedData, attributes: attributes), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 212.0, height: 212.0), .bubble, automaticDownload)
initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight
contentInstantVideoSizeAndApply = (videoLayout, apply)
} else if file.isVideo {
@ -438,12 +441,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
}
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, file, automaticDownload, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, file, automaticDownload, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else if file.isSticker || file.isAnimatedSticker {
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, file, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, file, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else {
@ -455,20 +458,20 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
} else {
if message.flags.contains(.Failed) {
statusType = .BubbleOutgoing(.Failed)
} else if message.flags.isSending && !message.isSentOrAcknowledged {
} else if (message.flags.isSending && !message.isSentOrAcknowledged) || attributes.updatingMedia != nil {
statusType = .BubbleOutgoing(.Sending)
} else {
statusType = .BubbleOutgoing(.Sent(read: messageRead))
}
}
let (_, refineLayout) = contentFileLayout(context, presentationData, message, file, automaticDownload, message.effectivelyIncoming(context.account.peerId), false, associatedData.forcedResourceStatus, statusType, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height))
let (_, refineLayout) = contentFileLayout(context, presentationData, message, attributes, file, automaticDownload, message.effectivelyIncoming(context.account.peerId), false, associatedData.forcedResourceStatus, statusType, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height))
refineContentFileLayout = refineLayout
}
} else if let image = media as? TelegramMediaImage {
if !flags.contains(.preferMediaInline) {
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, image, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, image, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else if let dimensions = largestImageRepresentation(image.representations)?.dimensions {
@ -480,11 +483,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
} else if let image = media as? TelegramMediaWebFile {
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, image, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, image, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else if let wallpaper = media as? WallpaperPreviewMedia {
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, wallpaper, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, presentationData.dateTimeFormat, message, attributes, wallpaper, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
if case let .file(_, _, _, _, isTheme, _) = wallpaper.content, isTheme {
@ -546,7 +549,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
} else {
statusType = .BubbleOutgoing(.Failed)
}
} else if message.flags.isSending && !message.isSentOrAcknowledged {
} else if (message.flags.isSending && !message.isSentOrAcknowledged) || attributes.updatingMedia != nil {
if imageMode {
statusType = .ImageOutgoing(.Sending)
} else {

View File

@ -90,14 +90,16 @@ final class ChatMessageBubbleContentItem {
let read: Bool
let presentationData: ChatPresentationData
let associatedData: ChatMessageItemAssociatedData
let attributes: ChatMessageEntryAttributes
init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: Message, read: Bool, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData) {
init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: Message, read: Bool, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData, attributes: ChatMessageEntryAttributes) {
self.context = context
self.controllerInteraction = controllerInteraction
self.message = message
self.read = read
self.presentationData = presentationData
self.associatedData = associatedData
self.attributes = attributes
}
}

View File

@ -23,54 +23,54 @@ import GridMessageSelectionNode
import AppBundle
import Markdown
private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass)] {
var result: [(Message, AnyClass)] = []
private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(Message, AnyClass, ChatMessageEntryAttributes)] {
var result: [(Message, AnyClass, ChatMessageEntryAttributes)] = []
var skipText = false
var messageWithCaptionToAdd: Message?
var messageWithCaptionToAdd: (Message, ChatMessageEntryAttributes)?
var isUnsupportedMedia = false
outer: for message in item.content {
outer: for (message, itemAttributes) in item.content {
for attribute in message.attributes {
if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil {
result.append((message, ChatMessageRestrictedBubbleContentNode.self))
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes))
break outer
}
}
inner: for media in message.media {
if let _ = media as? TelegramMediaImage {
result.append((message, ChatMessageMediaBubbleContentNode.self))
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes))
} else if let file = media as? TelegramMediaFile {
var isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil)
if isVideo {
result.append((message, ChatMessageMediaBubbleContentNode.self))
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes))
} else {
result.append((message, ChatMessageFileBubbleContentNode.self))
result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes))
}
} else if let action = media as? TelegramMediaAction {
if case .phoneCall = action.action {
result.append((message, ChatMessageCallBubbleContentNode.self))
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes))
} else {
result.append((message, ChatMessageActionBubbleContentNode.self))
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes))
}
} else if let _ = media as? TelegramMediaMap {
result.append((message, ChatMessageMapBubbleContentNode.self))
result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes))
} else if let _ = media as? TelegramMediaGame {
skipText = true
result.append((message, ChatMessageGameBubbleContentNode.self))
result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes))
break inner
} else if let _ = media as? TelegramMediaInvoice {
skipText = true
result.append((message, ChatMessageInvoiceBubbleContentNode.self))
result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes))
break inner
} else if let _ = media as? TelegramMediaContact {
result.append((message, ChatMessageContactBubbleContentNode.self))
result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes))
} else if let _ = media as? TelegramMediaExpiredContent {
result.removeAll()
result.append((message, ChatMessageActionBubbleContentNode.self))
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes))
return result
} else if let _ = media as? TelegramMediaPoll {
result.append((message, ChatMessagePollBubbleContentNode.self))
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes))
} else if let _ = media as? TelegramMediaUnsupported {
isUnsupportedMedia = true
}
@ -79,10 +79,10 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
if !message.text.isEmpty || isUnsupportedMedia {
if !skipText {
if case .group = item.content {
messageWithCaptionToAdd = message
messageWithCaptionToAdd = (message, itemAttributes)
skipText = true
} else {
result.append((message, ChatMessageTextBubbleContentNode.self))
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes))
}
} else {
if case .group = item.content {
@ -94,29 +94,29 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
inner: for media in message.media {
if let webpage = media as? TelegramMediaWebpage {
if case .Loaded = webpage.content {
result.append((message, ChatMessageWebpageBubbleContentNode.self))
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes))
}
break inner
}
}
if isUnsupportedMedia {
result.append((message, ChatMessageUnsupportedBubbleContentNode.self))
result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes))
}
}
if let messageWithCaptionToAdd = messageWithCaptionToAdd {
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self))
if let (messageWithCaptionToAdd, itemAttributes) = messageWithCaptionToAdd {
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes))
}
if let additionalContent = item.additionalContent {
switch additionalContent {
case let .eventLogPreviousMessage(previousMessage):
result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self))
result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes()))
case let .eventLogPreviousDescription(previousMessage):
result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self))
result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes()))
case let .eventLogPreviousLink(previousMessage):
result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self))
result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes()))
}
}
@ -806,22 +806,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
var contentPropertiesAndPrepareLayouts: [(Message, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
var addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?
let contentNodeMessagesAndClasses = contentNodeMessagesAndClassesForItem(item)
for (contentNodeMessage, contentNodeClass) in contentNodeMessagesAndClasses {
for (contentNodeMessage, contentNodeClass, attributes) in contentNodeMessagesAndClasses {
var found = false
for (currentMessage, currentClass, supportsMosaic, currentLayout) in currentContentClassesPropertiesAndLayouts {
if currentClass == contentNodeClass && currentMessage.stableId == contentNodeMessage.stableId {
contentPropertiesAndPrepareLayouts.append((contentNodeMessage, supportsMosaic, currentLayout))
contentPropertiesAndPrepareLayouts.append((contentNodeMessage, supportsMosaic, attributes, currentLayout))
found = true
break
}
}
if !found {
let contentNode = (contentNodeClass as! ChatMessageBubbleContentNode.Type).init()
contentPropertiesAndPrepareLayouts.append((contentNodeMessage, contentNode.supportsMosaic, contentNode.asyncLayoutContent()))
contentPropertiesAndPrepareLayouts.append((contentNodeMessage, contentNode.supportsMosaic, attributes, contentNode.asyncLayoutContent()))
if addedContentNodes == nil {
addedContentNodes = []
}
@ -918,7 +918,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
var index = 0
for (message, _, prepareLayout) in contentPropertiesAndPrepareLayouts {
for (message, _, attributes, prepareLayout) in contentPropertiesAndPrepareLayouts {
let topPosition: ChatMessageBubbleRelativePosition
let bottomPosition: ChatMessageBubbleRelativePosition
@ -938,7 +938,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
prepareContentPosition = .linear(top: topPosition, bottom: refinedBottomPosition)
}
let contentItem = ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: message, read: read, presentationData: item.presentationData, associatedData: item.associatedData)
let contentItem = ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: message, read: read, presentationData: item.presentationData, associatedData: item.associatedData, attributes: attributes)
var itemSelection: Bool?
if case .mosaic = prepareContentPosition {
@ -1074,6 +1074,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
let message = item.content.firstMessage
var edited = false
if item.content.firstMessageAttributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
for attribute in message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
@ -1104,7 +1107,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
} else {
if isFailed {
statusType = .ImageOutgoing(.Failed)
} else if message.flags.isSending && !message.isSentOrAcknowledged {
} else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.content.firstMessageAttributes.updatingMedia != nil {
statusType = .ImageOutgoing(.Sending)
} else {
statusType = .ImageOutgoing(.Sent(read: item.read))
@ -1258,7 +1261,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
findRemoved: for i in 0 ..< currentContentClassesPropertiesAndLayouts.count {
let currentMessage = currentContentClassesPropertiesAndLayouts[i].0
let currentClass: AnyClass = currentContentClassesPropertiesAndLayouts[i].1
for (contentNodeMessage, contentNodeClass) in contentNodeMessagesAndClasses {
for (contentNodeMessage, contentNodeClass, _) in contentNodeMessagesAndClasses {
if currentClass == contentNodeClass && currentMessage.stableId == contentNodeMessage.stableId {
continue findRemoved
}
@ -1614,7 +1617,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
replyInfoOriginY: CGFloat,
removedContentNodeIndices: [Int]?,
addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?,
contentNodeMessagesAndClasses: [(Message, AnyClass)],
contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes)],
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)],
mosaicStatusOrigin: CGPoint?,
mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?,
@ -1812,7 +1815,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
var sortedContentNodes: [ChatMessageBubbleContentNode] = []
outer: for (message, nodeClass) in contentNodeMessagesAndClasses {
outer: for (message, nodeClass, _) in contentNodeMessagesAndClasses {
if let addedContentNodes = addedContentNodes {
for (contentNodeMessage, contentNode) in addedContentNodes {
if type(of: contentNode) == nodeClass && contentNodeMessage.stableId == message.stableId {
@ -2743,7 +2746,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
if let highlightedState = item.controllerInteraction.highlightedState {
for message in item.content {
for (message, _) in item.content {
if highlightedState.messageStableId == message.stableId {
highlighted = true
break

View File

@ -142,6 +142,9 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var edited = false
if item.attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
@ -174,7 +177,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
} else {
if item.message.flags.contains(.Failed) {
statusType = .BubbleOutgoing(.Failed)
} else if item.message.flags.isSending && !item.message.isSentOrAcknowledged {
} else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
statusType = .BubbleOutgoing(.Sending)
} else {
statusType = .BubbleOutgoing(.Sent(read: item.read))

View File

@ -26,7 +26,7 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
guard let item = itemNode.item else {
return
}
if item.content.contains(where: { $0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode() {
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode() {
result = ContextControllerTakeViewInfo(contentContainingNode: contentNode, contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
}
}
@ -46,7 +46,7 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
guard let item = itemNode.item else {
return
}
if item.content.contains(where: { $0.stableId == self.message.stableId }) {
if item.content.contains(where: { $0.0.stableId == self.message.stableId }) {
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
}
}

View File

@ -44,7 +44,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
}
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.context, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)

View File

@ -39,7 +39,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
let text: String = item.message.text
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.context, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)

View File

@ -44,7 +44,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
}
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.context, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)

View File

@ -59,7 +59,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
} else {
if item.message.flags.contains(.Failed) {
statusType = .BubbleOutgoing(.Failed)
} else if item.message.flags.isSending && !item.message.isSentOrAcknowledged {
} else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
statusType = .BubbleOutgoing(.Sending)
} else {
statusType = .BubbleOutgoing(.Sent(read: item.read))
@ -71,7 +71,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
let (initialWidth, refineLayout) = interactiveFileLayout(item.context, item.presentationData, item.message, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.context.account.peerId), item.associatedData.isRecentActions, item.associatedData.forcedResourceStatus, statusType, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
let (initialWidth, refineLayout) = interactiveFileLayout(item.context, item.presentationData, item.message, item.attributes, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.context.account.peerId), item.associatedData.isRecentActions, item.associatedData.forcedResourceStatus, statusType, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)

View File

@ -71,7 +71,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.context, item.controllerInteraction, item.message, item.read, title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, constrainedSize)
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)

View File

@ -210,7 +210,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
}
}
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, presentationData: item.presentationData, associatedData: item.associatedData), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free, automaticDownload)
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free, automaticDownload)
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)

View File

@ -186,7 +186,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
}
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))) {
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))) {
let currentFile = self.file
let titleAsyncLayout = TextNode.asyncLayout(self.titleNode)
@ -196,7 +196,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let currentMessage = self.message
return { context, presentationData, message, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize in
return { context, presentationData, message, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize in
return (CGFloat.greatestFiniteMagnitude, { constrainedSize in
let titleFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
let descriptionFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0))
@ -269,6 +269,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if let statusType = dateAndStatusType {
var edited = false
if attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
for attribute in message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
@ -880,12 +883,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize)
}
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageInteractiveFileNode))) {
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageInteractiveFileNode))) {
let currentAsyncLayout = node?.asyncLayout()
return { context, presentationData, message, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize in
return { context, presentationData, message, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize in
var fileNode: ChatMessageInteractiveFileNode
var fileLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void)))
var fileLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void)))
if let node = node, let currentAsyncLayout = currentAsyncLayout {
fileNode = node
@ -895,7 +898,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
fileLayout = fileNode.asyncLayout()
}
let (initialWidth, continueLayout) = fileLayout(context, presentationData, message, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize)
let (initialWidth, continueLayout) = fileLayout(context, presentationData, message, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, constrainedSize)
return (initialWidth, { constrainedSize in
let (finalWidth, finalLayout) = continueLayout(constrainedSize)

View File

@ -239,7 +239,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
case .bubble:
if item.message.flags.contains(.Failed) {
statusType = .BubbleOutgoing(.Failed)
} else if item.message.flags.isSending && !item.message.isSentOrAcknowledged {
} else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
statusType = .BubbleOutgoing(.Sending)
} else {
statusType = .BubbleOutgoing(.Sent(read: item.read))
@ -248,6 +248,9 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
var edited = false
if item.attributes.updatingMedia != nil {
edited = true
}
let sentViaBot = false
var viewCount: Int? = nil
for attribute in item.message.attributes {

View File

@ -79,6 +79,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
private var context: AccountContext?
private var message: Message?
private var attributes: ChatMessageEntryAttributes?
private var media: Media?
private var themeAndStrings: (PresentationTheme, PresentationStrings, String)?
private var sizeCalculation: InteractiveMediaNodeSizeCalculation?
@ -168,7 +169,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
private func progressPressed(canActivate: Bool) {
if let fetchStatus = self.fetchStatus {
if let _ = self.attributes?.updatingMedia {
if let message = self.message {
self.context?.account.pendingUpdateMessageManager.cancel(messageId: message.id)
}
} else if let fetchStatus = self.fetchStatus {
var activateContent = false
if let state = self.statusNode?.state, case .play = state {
activateContent = true
@ -214,7 +219,11 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
@objc func imageTap(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
let point = recognizer.location(in: self.imageNode.view)
if let fetchStatus = self.fetchStatus, case .Local = fetchStatus {
if let _ = self.attributes?.updatingMedia {
if let statusNode = self.statusNode, statusNode.frame.contains(point) {
self.progressPressed(canActivate: true)
}
} else if let fetchStatus = self.fetchStatus, case .Local = fetchStatus {
var videoContentMatch = true
if let content = self.videoContent, case let .message(stableId, mediaId) = content.nativeId {
videoContentMatch = self.message?.stableId == stableId && self.media?.id == mediaId
@ -232,7 +241,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
}
func asyncLayout() -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void))) {
func asyncLayout() -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void))) {
let currentMessage = self.message
let currentMedia = self.media
let imageLayout = self.imageNode.asyncLayout()
@ -245,7 +254,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
let currentAutomaticDownload = self.automaticDownload
let currentAutomaticPlayback = self.automaticPlayback
return { [weak self] context, theme, strings, dateTimeFormat, message, media, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
return { [weak self] context, theme, strings, dateTimeFormat, message, attributes, media, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
var nativeSize: CGSize
let isSecretMedia = message.containsSecretMedia
@ -674,6 +683,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
if let strongSelf = self {
strongSelf.context = context
strongSelf.message = message
strongSelf.attributes = attributes
strongSelf.media = media
strongSelf.wideLayout = wideLayout
strongSelf.themeAndStrings = (theme, strings, dateTimeFormat.decimalSeparator)
@ -904,7 +914,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
private func updateStatus(animated: Bool) {
guard let (theme, strings, decimalSeparator) = self.themeAndStrings, let sizeCalculation = self.sizeCalculation, let message = self.message, var automaticPlayback = self.automaticPlayback, let wideLayout = self.wideLayout else {
guard let (theme, strings, decimalSeparator) = self.themeAndStrings, let sizeCalculation = self.sizeCalculation, let message = self.message, let attributes = self.attributes, var automaticPlayback = self.automaticPlayback, let wideLayout = self.wideLayout else {
return
}
@ -944,7 +954,9 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
var progressRequired = false
if secretBeginTimeAndTimeout?.0 != nil {
if attributes.updatingMedia != nil {
progressRequired = true
} else if secretBeginTimeAndTimeout?.0 != nil {
progressRequired = true
} else if let fetchStatus = self.fetchStatus {
switch fetchStatus {
@ -1017,7 +1029,9 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: string)
}
var animated: Bool = animated
if var fetchStatus = self.fetchStatus {
if let updatingMedia = attributes.updatingMedia {
state = .progress(color: messageTheme.mediaOverlayControlColors.foregroundColor, lineWidth: nil, value: CGFloat(updatingMedia.progress), cancelEnabled: true)
} else if var fetchStatus = self.fetchStatus {
var playerPosition: Int32?
var playerDuration: Int32 = 0
var active = false
@ -1274,12 +1288,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
}
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))) {
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))) {
let currentAsyncLayout = node?.asyncLayout()
return { context, theme, strings, dateTimeFormat, message, media, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
return { context, theme, strings, dateTimeFormat, message, attributes, media, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
var imageNode: ChatMessageInteractiveMediaNode
var imageLayout: (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void)))
var imageLayout: (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void)))
if let node = node, let currentAsyncLayout = currentAsyncLayout {
imageNode = node
@ -1289,7 +1303,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
imageLayout = imageNode.asyncLayout()
}
let (unboundSize, initialWidth, continueLayout) = imageLayout(context, theme, strings, dateTimeFormat, message, media, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode)
let (unboundSize, initialWidth, continueLayout) = imageLayout(context, theme, strings, dateTimeFormat, message, attributes, media, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode)
return (unboundSize, initialWidth, { constrainedSize, automaticPlayback, wideLayout, corners in
let (finalWidth, finalLayout) = continueLayout(constrainedSize, automaticPlayback, wideLayout, corners)

View File

@ -74,7 +74,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.context, item.controllerInteraction, item.message, item.read, title, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, constrainedSize)
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, title, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, constrainedSize)
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)

View File

@ -43,14 +43,23 @@ public enum ChatMessageItemContent: Sequence {
}
}
public func makeIterator() -> AnyIterator<Message> {
var firstMessageAttributes: ChatMessageEntryAttributes {
switch self {
case let .message(message):
return message.attributes
case let .group(messages):
return messages[0].3
}
}
public func makeIterator() -> AnyIterator<(Message, ChatMessageEntryAttributes)> {
var index = 0
return AnyIterator { () -> Message? in
return AnyIterator { () -> (Message, ChatMessageEntryAttributes)? in
switch self {
case let .message(message, _, _, _):
case let .message(message):
if index == 0 {
index += 1
return message
return (message.message, message.attributes)
} else {
index += 1
return nil
@ -59,7 +68,7 @@ public enum ChatMessageItemContent: Sequence {
if index < messages.count {
let currentIndex = index
index += 1
return messages[currentIndex].0
return (messages[currentIndex].0, messages[currentIndex].3)
} else {
return nil
}

View File

@ -172,6 +172,9 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: max(1.0, maxTextWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var edited = false
if item.attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
@ -211,7 +214,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
} else {
if item.message.flags.contains(.Failed) {
statusType = .BubbleOutgoing(.Failed)
} else if item.message.flags.isSending && !item.message.isSentOrAcknowledged {
} else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
statusType = .BubbleOutgoing(.Sending)
} else {
statusType = .BubbleOutgoing(.Sent(read: item.read))
@ -223,7 +226,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
} else {
if item.message.flags.contains(.Failed) {
statusType = .ImageOutgoing(.Failed)
} else if item.message.flags.isSending && !item.message.isSentOrAcknowledged {
} else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
statusType = .ImageOutgoing(.Sending)
} else {
statusType = .ImageOutgoing(.Sent(read: item.read))

View File

@ -70,36 +70,41 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
var automaticPlayback: Bool = false
var contentMode: InteractiveMediaNodeContentMode = .aspectFit
for media in item.message.media {
if let telegramImage = media as? TelegramMediaImage {
selectedMedia = telegramImage
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) {
automaticDownload = .full
}
} else if let telegramFile = media as? TelegramMediaFile {
selectedMedia = telegramFile
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramFile) {
automaticDownload = .full
} else if shouldPredownloadMedia(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramFile) {
automaticDownload = .prefetch
}
if !item.message.containsSecretMedia {
if telegramFile.isAnimated && item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
}
} else if (telegramFile.isVideo && !telegramFile.isAnimated) && item.controllerInteraction.automaticMediaDownloadSettings.autoplayVideos {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
if let updatingMedia = item.attributes.updatingMedia, case let .update(mediaReference) = updatingMedia.media {
selectedMedia = mediaReference.media
}
if selectedMedia == nil {
for media in item.message.media {
if let telegramImage = media as? TelegramMediaImage {
selectedMedia = telegramImage
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) {
automaticDownload = .full
}
} else if let telegramFile = media as? TelegramMediaFile {
selectedMedia = telegramFile
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramFile) {
automaticDownload = .full
} else if shouldPredownloadMedia(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramFile) {
automaticDownload = .prefetch
}
if !item.message.containsSecretMedia {
if telegramFile.isAnimated && item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
}
} else if (telegramFile.isVideo && !telegramFile.isAnimated) && item.controllerInteraction.automaticMediaDownloadSettings.autoplayVideos {
if case .full = automaticDownload {
automaticPlayback = true
} else {
automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil
}
}
}
contentMode = .aspectFill
}
contentMode = .aspectFill
}
}
@ -138,7 +143,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
sizeCalculation = .unconstrained
}
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData.theme.theme, item.presentationData.strings, item.presentationData.dateTimeFormat, item.message, selectedMedia!, automaticDownload, item.associatedData.automaticDownloadPeerType, sizeCalculation, layoutConstants, contentMode)
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData.theme.theme, item.presentationData.strings, item.presentationData.dateTimeFormat, item.message, item.attributes, selectedMedia!, automaticDownload, item.associatedData.automaticDownloadPeerType, sizeCalculation, layoutConstants, contentMode)
let forceFullCorners = false
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 7.0, hidesBackground: .emptyWallpaper, forceFullCorners: forceFullCorners, forceAlignment: .none)
@ -165,6 +170,9 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
let (imageSize, imageApply) = finishLayout(boundingWidth - bubbleInsets.left - bubbleInsets.right)
var edited = false
if item.attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
@ -200,7 +208,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
} else {
if item.message.flags.contains(.Failed) {
statusType = .ImageOutgoing(.Failed)
} else if item.message.flags.isSending && !item.message.isSentOrAcknowledged {
} else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
statusType = .ImageOutgoing(.Sending)
} else {
statusType = .ImageOutgoing(.Sent(read: item.read))

View File

@ -583,6 +583,9 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let textConstrainedSize = CGSize(width: constrainedSize.width - horizontalInset, height: constrainedSize.height)
var edited = false
if item.attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
@ -615,7 +618,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
} else {
if message.flags.contains(.Failed) {
statusType = .BubbleOutgoing(.Failed)
} else if message.flags.isSending && !message.isSentOrAcknowledged {
} else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
statusType = .BubbleOutgoing(.Sending)
} else {
statusType = .BubbleOutgoing(.Sent(read: item.read))

View File

@ -48,6 +48,9 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
let textConstrainedSize = CGSize(width: min(maxTextWidth, constrainedSize.width - horizontalInset), height: constrainedSize.height)
var edited = false
if item.attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
var rawText = ""
for attribute in item.message.attributes {

View File

@ -105,6 +105,9 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let textConstrainedSize = CGSize(width: min(maxTextWidth, constrainedSize.width - horizontalInset), height: constrainedSize.height)
var edited = false
if item.attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
for attribute in item.message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
@ -137,7 +140,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} else {
if message.flags.contains(.Failed) {
statusType = .BubbleOutgoing(.Failed)
} else if message.flags.isSending && !message.isSentOrAcknowledged {
} else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
statusType = .BubbleOutgoing(.Sending)
} else {
statusType = .BubbleOutgoing(.Sent(read: item.read))
@ -178,7 +181,11 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
rawText = item.presentationData.strings.Conversation_UnsupportedMediaPlaceholder
messageEntities = [MessageTextEntity(range: 0..<rawText.count, type: .Italic)]
} else {
rawText = item.message.text
if let updatingMedia = item.attributes.updatingMedia {
rawText = updatingMedia.text
} else {
rawText = item.message.text
}
for attribute in item.message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {

View File

@ -370,7 +370,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.context, item.controllerInteraction, item.message, item.read, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, true, layoutConstants, constrainedSize)
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, true, layoutConstants, constrainedSize)
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)

View File

@ -360,7 +360,12 @@ func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideo
let signal = Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
subscriber.putNext(.reset)
let avAsset = AVURLAsset(url: URL(fileURLWithPath: resource.path))
var filteredPath = resource.path
if filteredPath.hasPrefix("file://") {
filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)])
}
let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath))
var adjustments: TGVideoEditAdjustments?
if let videoAdjustments = resource.adjustments {
if let dict = NSKeyedUnarchiver.unarchiveObject(with: videoAdjustments.data.makeData()) as? [AnyHashable : Any] {

View File

@ -147,7 +147,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
tagMask = .voiceOrInstantVideo
}
self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), updatingMedia: .single([:]), mode: .list(search: false, reversed: currentIsReversed))
self.historyNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: .message(initialMessageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: currentIsReversed))
super.init()
@ -462,7 +462,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
tagMask = .voiceOrInstantVideo
}
let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), updatingMedia: .single([:]), mode: .list(search: false, reversed: self.currentIsReversed))
let historyNode = ChatHistoryListNode(context: self.context, chatLocation: .peer(self.peerId), tagMask: tagMask, subject: .message(messageId), controllerInteraction: self.controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: self.currentIsReversed))
historyNode.preloadPages = true
historyNode.stackFromBottom = true
historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in

View File

@ -29,7 +29,7 @@ private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, context: Ac
}
return node
case .file:
let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .file, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, updatingMedia: .single([:]), mode: .list(search: true, reversed: false))
let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .file, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false))
node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor
node.didEndScrolling = { [weak node] in
guard let node = node else {
@ -40,7 +40,7 @@ private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, context: Ac
node.preloadPages = true
return node
case .music:
let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .music, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, updatingMedia: .single([:]), mode: .list(search: true, reversed: false))
let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .music, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false))
node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor
node.didEndScrolling = { [weak node] in
guard let node = node else {
@ -51,7 +51,7 @@ private func historyNodeImplForMode(_ mode: PeerMediaCollectionMode, context: Ac
node.preloadPages = true
return node
case .webpage:
let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .webPage, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, updatingMedia: .single([:]), mode: .list(search: true, reversed: false))
let node = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: .webPage, subject: messageId.flatMap { .message($0) }, controllerInteraction: controllerInteraction, selectedMessages: selectedMessages, mode: .list(search: true, reversed: false))
node.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor
node.didEndScrolling = { [weak node] in
guard let node = node else {