mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Improve message editing
This commit is contained in:
parent
8f2e148ee5
commit
192604e5f6
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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])))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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] {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user