mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
[WIP] Chat Import
This commit is contained in:
parent
fe491b6831
commit
85402a67cb
35 changed files with 3759 additions and 3454 deletions
|
|
@ -5903,3 +5903,4 @@ Sorry for the inconvenience.";
|
|||
"Common.Save" = "Save";
|
||||
|
||||
"ChatList.HeaderImportIntoAnExistingGroup" = "OR IMPORT INTO AN EXISTING GROUP";
|
||||
"Conversation.ImportedMessageHint" = "The messages was imported from another app. We can't guarantee it's real.";
|
||||
|
|
|
|||
18
submodules/ChatHistoryImportTasks/BUILD
Normal file
18
submodules/ChatHistoryImportTasks/BUILD
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ChatHistoryImportTasks",
|
||||
module_name = "ChatHistoryImportTasks",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import Foundation
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import SwiftSignalKit
|
||||
|
||||
|
|
@ -20,6 +20,7 @@ swift_library(
|
|||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/RadialStatusNode:RadialStatusNode",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/ChatHistoryImportTasks:ChatHistoryImportTasks",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -201,9 +201,11 @@ public final class ChatImportActivityScreen: ViewController {
|
|||
private let peerId: PeerId
|
||||
private let archive: Archive
|
||||
private let mainEntry: TempBoxFile
|
||||
private let mainEntrySize: Int
|
||||
private let otherEntries: [(Entry, String, ChatHistoryImport.MediaType)]
|
||||
private let totalBytes: Int
|
||||
|
||||
private var pendingEntries = Set<String>()
|
||||
private var pendingEntries: [String: (Int, Float)] = [:]
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
|
|
@ -222,7 +224,21 @@ public final class ChatImportActivityScreen: ViewController {
|
|||
self.mainEntry = mainEntry
|
||||
self.otherEntries = otherEntries
|
||||
|
||||
self.pendingEntries = Set(otherEntries.map { $0.1 })
|
||||
if let size = fileSize(self.mainEntry.path) {
|
||||
self.mainEntrySize = size
|
||||
} else {
|
||||
self.mainEntrySize = 0
|
||||
}
|
||||
|
||||
for (entry, fileName, _) in otherEntries {
|
||||
self.pendingEntries[fileName] = (entry.uncompressedSize, 0.0)
|
||||
}
|
||||
|
||||
var totalBytes: Int = self.mainEntrySize
|
||||
for entry in self.otherEntries {
|
||||
totalBytes += entry.0.uncompressedSize
|
||||
}
|
||||
self.totalBytes = totalBytes
|
||||
|
||||
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
|
|
@ -253,14 +269,7 @@ public final class ChatImportActivityScreen: ViewController {
|
|||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
var totalBytes: Int = 0
|
||||
if let size = fileSize(self.mainEntry.path) {
|
||||
totalBytes += size
|
||||
}
|
||||
for entry in self.otherEntries {
|
||||
totalBytes += entry.0.uncompressedSize
|
||||
}
|
||||
self.displayNode = Node(controller: self, context: self.context, totalBytes: totalBytes)
|
||||
self.displayNode = Node(controller: self, context: self.context, totalBytes: self.totalBytes)
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
|
@ -298,8 +307,8 @@ public final class ChatImportActivityScreen: ViewController {
|
|||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { session -> Signal<String, ImportError> in
|
||||
var importSignal: Signal<String, ImportError> = .single("")
|
||||
|> mapToSignal { session -> Signal<(String, Float), ImportError> in
|
||||
var importSignal: Signal<(String, Float), ImportError> = .single(("", 0.0))
|
||||
|
||||
for (entry, fileName, mediaType) in otherEntries {
|
||||
let unpackedFile = Signal<TempBoxFile, ImportError> { subscriber in
|
||||
|
|
@ -315,14 +324,14 @@ public final class ChatImportActivityScreen: ViewController {
|
|||
return EmptyDisposable
|
||||
}
|
||||
let uploadedMedia = unpackedFile
|
||||
|> mapToSignal { tempFile -> Signal<String, ImportError> in
|
||||
|> mapToSignal { tempFile -> Signal<(String, Float), ImportError> in
|
||||
return ChatHistoryImport.uploadMedia(account: context.account, session: session, file: tempFile, fileName: fileName, type: mediaType)
|
||||
|> mapError { _ -> ImportError in
|
||||
return .generic
|
||||
}
|
||||
|> map { _ -> String in
|
||||
|> map { progress -> (String, Float) in
|
||||
return (fileName, progress)
|
||||
}
|
||||
|> then(.single(fileName))
|
||||
}
|
||||
|
||||
importSignal = importSignal
|
||||
|
|
@ -334,19 +343,28 @@ public final class ChatImportActivityScreen: ViewController {
|
|||
|> mapError { _ -> ImportError in
|
||||
return .generic
|
||||
}
|
||||
|> map { _ -> String in
|
||||
|> map { _ -> (String, Float) in
|
||||
})
|
||||
|
||||
return importSignal
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] fileName in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] (fileName, progress) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.pendingEntries.remove(fileName)
|
||||
|
||||
if let (fileSize, _) = strongSelf.pendingEntries[fileName] {
|
||||
strongSelf.pendingEntries[fileName] = (fileSize, progress)
|
||||
}
|
||||
|
||||
var totalDoneBytes = strongSelf.mainEntrySize
|
||||
for (_, sizeAndProgress) in strongSelf.pendingEntries {
|
||||
totalDoneBytes += Int(Float(sizeAndProgress.0) * sizeAndProgress.1)
|
||||
}
|
||||
|
||||
var totalProgress: CGFloat = 1.0
|
||||
if !strongSelf.otherEntries.isEmpty {
|
||||
totalProgress = CGFloat(strongSelf.otherEntries.count - strongSelf.pendingEntries.count) / CGFloat(strongSelf.otherEntries.count)
|
||||
totalProgress = CGFloat(totalDoneBytes) / CGFloat(strongSelf.totalBytes)
|
||||
}
|
||||
strongSelf.controllerNode.updateProgress(totalProgress: totalProgress, isDone: false, animated: true)
|
||||
}, error: { [weak self] _ in
|
||||
|
|
|
|||
|
|
@ -460,6 +460,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[649453030] = { return Api.messages.MessageEditData.parse_messageEditData($0) }
|
||||
dict[-886477832] = { return Api.LabeledPrice.parse_labeledPrice($0) }
|
||||
dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) }
|
||||
dict[-1919636670] = { return Api.messages.HistoryImportParsed.parse_historyImportParsed($0) }
|
||||
dict[-618540889] = { return Api.InputSecureValue.parse_inputSecureValue($0) }
|
||||
dict[-170029155] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) }
|
||||
dict[1722786150] = { return Api.help.DeepLinkInfo.parse_deepLinkInfoEmpty($0) }
|
||||
|
|
@ -1193,6 +1194,8 @@ public struct Api {
|
|||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.ChatFull:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.HistoryImportParsed:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.InputSecureValue:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.DiscussionMessage:
|
||||
|
|
|
|||
|
|
@ -977,6 +977,44 @@ public struct messages {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum HistoryImportParsed: TypeConstructorDescription {
|
||||
case historyImportParsed(flags: Int32, title: String?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .historyImportParsed(let flags, let title):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1919636670)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .historyImportParsed(let flags, let title):
|
||||
return ("historyImportParsed", [("flags", flags), ("title", title)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_historyImportParsed(_ reader: BufferReader) -> HistoryImportParsed? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: String?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_2 = parseString(reader) }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.messages.HistoryImportParsed.historyImportParsed(flags: _1!, title: _2)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum DiscussionMessage: TypeConstructorDescription {
|
||||
case discussionMessage(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, chats: [Api.Chat], users: [Api.User])
|
||||
|
|
|
|||
|
|
@ -4080,6 +4080,20 @@ public extension Api {
|
|||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func checkHistoryImport(importHead: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.HistoryImportParsed>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1140726259)
|
||||
serializeString(importHead, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "messages.checkHistoryImport", parameters: [("importHead", importHead)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImportParsed? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.HistoryImportParsed?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImportParsed
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct channels {
|
||||
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set<Pe
|
|||
}
|
||||
} else {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
if let _ = peer as? TelegramChannel {
|
||||
if let channel = peer as? TelegramChannel, channel.participationStatus != .member {
|
||||
if let notificationSettings = transaction.getPeerNotificationSettings(peerId) {
|
||||
peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings)
|
||||
Logger.shared.log("State", "Peer \(peerId) (\(peer.debugDisplayTitle) has no stored inclusion, using synthesized one")
|
||||
|
|
@ -447,7 +447,8 @@ private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set<Pe
|
|||
}
|
||||
}
|
||||
|
||||
return AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedMessageIds: associatedMessageIds, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
|
||||
var state = AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedMessageIds: associatedMessageIds, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
|
||||
return state
|
||||
}
|
||||
|
||||
func initialStateWithUpdateGroups(postbox: Postbox, groups: [UpdateGroup]) -> Signal<AccountMutableState, NoError> {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,36 @@ public enum ChatHistoryImport {
|
|||
case generic
|
||||
}
|
||||
|
||||
//messages.historyImportParsed flags:# pm:flags.0?true group:flags.1?true title:flags.1?string = messages.HistoryImportParsed;
|
||||
public enum ParsedInfo {
|
||||
case privateChat(title: String?)
|
||||
case group(title: String?)
|
||||
}
|
||||
|
||||
public enum GetInfoError {
|
||||
case generic
|
||||
case parseError
|
||||
}
|
||||
|
||||
public static func getInfo(account: Account, header: String) -> Signal<ParsedInfo, GetInfoError> {
|
||||
return account.network.request(Api.functions.messages.checkHistoryImport(importHead: header))
|
||||
|> mapError { _ -> GetInfoError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<ParsedInfo, GetInfoError> in
|
||||
switch result {
|
||||
case let .historyImportParsed(flags, title):
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
return .single(.privateChat(title: title))
|
||||
} else if (flags & (1 << 1)) != 0 {
|
||||
return .single(.group(title: title))
|
||||
} else {
|
||||
return .fail(.parseError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func initSession(account: Account, peerId: PeerId, file: TempBoxFile, mediaCount: Int32) -> Signal<Session, InitImportError> {
|
||||
return multipartUpload(network: account.network, postbox: account.postbox, source: .tempFile(file), encrypt: false, tag: nil, hintFileSize: nil, hintFileIsLarge: false)
|
||||
|> mapError { _ -> InitImportError in
|
||||
|
|
@ -63,12 +93,12 @@ public enum ChatHistoryImport {
|
|||
case generic
|
||||
}
|
||||
|
||||
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, fileName: String, type: MediaType) -> Signal<Never, UploadMediaError> {
|
||||
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, fileName: String, type: MediaType) -> Signal<Float, UploadMediaError> {
|
||||
return multipartUpload(network: account.network, postbox: account.postbox, source: .tempFile(file), encrypt: false, tag: nil, hintFileSize: nil, hintFileIsLarge: false)
|
||||
|> mapError { _ -> UploadMediaError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, UploadMediaError> in
|
||||
|> mapToSignal { result -> Signal<Float, UploadMediaError> in
|
||||
let inputMedia: Api.InputMedia
|
||||
switch result {
|
||||
case let .inputFile(inputFile):
|
||||
|
|
@ -91,8 +121,8 @@ public enum ChatHistoryImport {
|
|||
}
|
||||
inputMedia = .inputMediaUploadedDocument(flags: 0, file: inputFile, thumb: nil, mimeType: mimeType, attributes: attributes, stickers: nil, ttlSeconds: nil)
|
||||
}
|
||||
case .progress:
|
||||
return .complete()
|
||||
case let .progress(value):
|
||||
return .single(value)
|
||||
case .inputSecretFile:
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
|
@ -100,8 +130,8 @@ public enum ChatHistoryImport {
|
|||
|> mapError { _ -> UploadMediaError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, UploadMediaError> in
|
||||
return .complete()
|
||||
|> mapToSignal { result -> Signal<Float, UploadMediaError> in
|
||||
return .single(1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -215,6 +215,7 @@ swift_library(
|
|||
"//Telegram:GeneratedSources",
|
||||
"//third-party/ZIPFoundation:ZIPFoundation",
|
||||
"//submodules/ChatImportUI:ChatImportUI",
|
||||
"//submodules/ChatHistoryImportTasks:ChatHistoryImportTasks",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Chat/ImportedMessageTooltipIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/ImportedMessageTooltipIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_imported.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/ImportedMessageTooltipIcon.imageset/ic_imported.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/ImportedMessageTooltipIcon.imageset/ic_imported.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -371,6 +371,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
private var isEmbeddedTitleContentHidden = false
|
||||
|
||||
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
|
||||
|
||||
private weak var currentImportMessageTooltip: UndoOverlayController?
|
||||
|
||||
public override var customData: Any? {
|
||||
return self.chatLocation
|
||||
|
|
@ -2208,6 +2210,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
strongSelf.present(MessageReactionListController(context: strongSelf.context, messageId: message.id, initialReactions: initialReactions), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}, displayImportedMessageTooltip: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let _ = strongSelf.currentImportMessageTooltip {
|
||||
} else {
|
||||
let controller = UndoOverlayController(presentationData: strongSelf.presentationData, content: .importedMessage(text: strongSelf.presentationData.strings.Conversation_ImportedMessageHint), elevatedLayout: false, action: { _ in return false })
|
||||
strongSelf.currentImportMessageTooltip = controller
|
||||
strongSelf.present(controller, in: .current)
|
||||
}
|
||||
}, displaySwipeToReplyHint: { [weak self] in
|
||||
if let strongSelf = self, let validLayout = strongSelf.validLayout, min(validLayout.size.width, validLayout.size.height) > 320.0 {
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .swipeToReply(title: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintTitle, text: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintText), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ public final class ChatControllerInteraction {
|
|||
let performTextSelectionAction: (UInt32, NSAttributedString, TextSelectionAction) -> Void
|
||||
let updateMessageLike: (MessageId, Bool) -> Void
|
||||
let openMessageReactions: (MessageId) -> Void
|
||||
let displayImportedMessageTooltip: (ASDisplayNode) -> Void
|
||||
let displaySwipeToReplyHint: () -> Void
|
||||
let dismissReplyMarkupMessage: (Message) -> Void
|
||||
let openMessagePollResults: (MessageId, Data) -> Void
|
||||
|
|
@ -192,6 +193,7 @@ public final class ChatControllerInteraction {
|
|||
performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void,
|
||||
updateMessageLike: @escaping (MessageId, Bool) -> Void,
|
||||
openMessageReactions: @escaping (MessageId) -> Void,
|
||||
displayImportedMessageTooltip: @escaping (ASDisplayNode) -> Void,
|
||||
displaySwipeToReplyHint: @escaping () -> Void,
|
||||
dismissReplyMarkupMessage: @escaping (Message) -> Void,
|
||||
openMessagePollResults: @escaping (MessageId, Data) -> Void,
|
||||
|
|
@ -271,6 +273,7 @@ public final class ChatControllerInteraction {
|
|||
self.performTextSelectionAction = performTextSelectionAction
|
||||
self.updateMessageLike = updateMessageLike
|
||||
self.openMessageReactions = openMessageReactions
|
||||
self.displayImportedMessageTooltip = displayImportedMessageTooltip
|
||||
self.displaySwipeToReplyHint = displaySwipeToReplyHint
|
||||
self.dismissReplyMarkupMessage = dismissReplyMarkupMessage
|
||||
self.openMessagePollResults = openMessagePollResults
|
||||
|
|
@ -320,6 +323,7 @@ public final class ChatControllerInteraction {
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -1168,6 +1168,17 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
actionButtonsNode.removeFromSupernode()
|
||||
strongSelf.actionButtonsNode = nil
|
||||
}
|
||||
|
||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.dateAndStatusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
||||
}
|
||||
} else {
|
||||
strongSelf.dateAndStatusNode.pressed = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,6 +319,17 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
} else {
|
||||
strongSelf.avatarNode.setCustomLetters(customLetters)
|
||||
}
|
||||
|
||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.dateAndStatusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
||||
}
|
||||
} else {
|
||||
strongSelf.dateAndStatusNode.pressed = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -167,8 +167,24 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||
private var theme: ChatPresentationThemeData?
|
||||
private var layoutSize: CGSize?
|
||||
|
||||
private var tapGestureRecognizer: UITapGestureRecognizer?
|
||||
|
||||
var openReactions: (() -> Void)?
|
||||
var openReplies: (() -> Void)?
|
||||
var pressed: (() -> Void)? {
|
||||
didSet {
|
||||
if self.pressed != nil {
|
||||
if self.tapGestureRecognizer == nil {
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
self.tapGestureRecognizer = tapGestureRecognizer
|
||||
self.view.addGestureRecognizer(tapGestureRecognizer)
|
||||
}
|
||||
} else if let tapGestureRecognizer = self.tapGestureRecognizer{
|
||||
self.tapGestureRecognizer = nil
|
||||
self.view.removeGestureRecognizer(tapGestureRecognizer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override init() {
|
||||
self.dateNode = TextNode()
|
||||
|
|
@ -180,6 +196,12 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||
self.addSubnode(self.dateNode)
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.pressed?()
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int, _ isPinned: Bool) -> (CGSize, (Bool) -> Void) {
|
||||
let dateLayout = TextNode.asyncLayout(self.dateNode)
|
||||
|
||||
|
|
@ -868,6 +890,11 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||
return reactionButtonNode.view
|
||||
}
|
||||
}
|
||||
if self.pressed != nil {
|
||||
if self.bounds.contains(point) {
|
||||
return self.view
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
let _ = item.controllerInteraction.requestMessageUpdate(item.message.id)
|
||||
}
|
||||
}
|
||||
|
||||
self.interactiveFileNode.displayImportedTooltip = { [weak self] sourceNode in
|
||||
if let strongSelf = self, let item = strongSelf.item {
|
||||
let _ = item.controllerInteraction.displayImportedMessageTooltip(sourceNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||
var toggleSelection: (Bool) -> Void = { _ in }
|
||||
var activateLocalContent: () -> Void = { }
|
||||
var requestUpdateLayout: (Bool) -> Void = { _ in }
|
||||
var displayImportedTooltip: (ASDisplayNode) -> Void = { _ in }
|
||||
|
||||
private var context: AccountContext?
|
||||
private var message: Message?
|
||||
|
|
@ -729,6 +730,17 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
strongSelf.updateStatus(animated: isAnimated)
|
||||
|
||||
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.dateAndStatusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.displayImportedTooltip(strongSelf.dateAndStatusNode)
|
||||
}
|
||||
} else {
|
||||
strongSelf.dateAndStatusNode.pressed = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -469,6 +469,17 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||
if let telegramFile = updatedFile, previousAutomaticDownload != automaticDownload, automaticDownload {
|
||||
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: item.context, message: item.message, file: telegramFile, userInitiated: false).start())
|
||||
}
|
||||
|
||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.dateAndStatusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
||||
}
|
||||
} else {
|
||||
strongSelf.dateAndStatusNode.pressed = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -426,6 +426,17 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
strongSelf.pinNode.frame = CGRect(origin: CGPoint(x: imageFrame.minX + floor((imageFrame.size.width - pinSize.width) / 2.0), y: imageFrame.minY + floor(imageFrame.size.height * 0.5 - 10.0 - pinSize.height / 2.0)), size: pinSize)
|
||||
|
||||
pinApply()
|
||||
|
||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.dateAndStatusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
||||
}
|
||||
} else {
|
||||
strongSelf.dateAndStatusNode.pressed = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -308,6 +308,17 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
selectionNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.dateAndStatusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
||||
}
|
||||
} else {
|
||||
strongSelf.dateAndStatusNode.pressed = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -750,6 +750,17 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||
actionButtonsNode.removeFromSupernode()
|
||||
strongSelf.actionButtonsNode = nil
|
||||
}
|
||||
|
||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.dateAndStatusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
|
||||
}
|
||||
} else {
|
||||
strongSelf.dateAndStatusNode.pressed = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -407,6 +407,17 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
}
|
||||
strongSelf.textAccessibilityOverlayNode.frame = textFrame
|
||||
strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
|
||||
|
||||
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
|
||||
strongSelf.statusNode.pressed = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.statusNode)
|
||||
}
|
||||
} else {
|
||||
strongSelf.statusNode.pressed = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -442,6 +442,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -2006,6 +2006,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -381,6 +381,12 @@ public class ShareRootControllerImpl {
|
|||
context.account.resetStateManagement()
|
||||
}
|
||||
|
||||
/*if let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as? UIApplication {
|
||||
let selector = NSSelectorFromString("openURL:")
|
||||
let url = URL(string: "tg://open")!
|
||||
application.perform(selector, with: url)
|
||||
}*/
|
||||
|
||||
if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, inputItems.count == 1, let item = inputItems[0] as? NSExtensionItem, let attachments = item.attachments {
|
||||
for attachment in attachments {
|
||||
if attachment.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
|
||||
|
|
@ -413,16 +419,6 @@ public class ShareRootControllerImpl {
|
|||
let stickerRegex = try! NSRegularExpression(pattern: "[\\d]+-STICKER-.*?\\.webp")
|
||||
let voiceRegex = try! NSRegularExpression(pattern: "[\\d]+-AUDIO-.*?\\.opus")
|
||||
|
||||
let groupVerificationRegexList = [
|
||||
try! NSRegularExpression(pattern: "created this group"),
|
||||
try! NSRegularExpression(pattern: "created group “(.*?)”"),
|
||||
]
|
||||
let groupCreationRegexList = [
|
||||
try! NSRegularExpression(pattern: "created group “(.*?)”"),
|
||||
try! NSRegularExpression(pattern: "] (.*?): Messages and calls are end-to-end encrypted")
|
||||
]
|
||||
|
||||
var groupTitle: String?
|
||||
var otherEntries: [(Entry, String, ChatHistoryImport.MediaType)] = []
|
||||
|
||||
var mainFile: TempBoxFile?
|
||||
|
|
@ -435,29 +431,6 @@ public class ShareRootControllerImpl {
|
|||
let tempFile = TempBox.shared.tempFile(fileName: entryPath)
|
||||
if entryPath == "_chat.txt" {
|
||||
let _ = try archive.extract(entry, to: URL(fileURLWithPath: tempFile.path))
|
||||
if let fileContents = try? String(contentsOfFile: tempFile.path) {
|
||||
let fullRange = NSRange(fileContents.startIndex ..< fileContents.endIndex, in: fileContents)
|
||||
var isGroup = false
|
||||
for regex in groupVerificationRegexList {
|
||||
if let _ = regex.firstMatch(in: fileContents, options: [], range: fullRange) {
|
||||
isGroup = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if isGroup {
|
||||
for regex in groupCreationRegexList {
|
||||
if groupTitle != nil {
|
||||
break
|
||||
}
|
||||
if let match = regex.firstMatch(in: fileContents, options: [], range: fullRange) {
|
||||
let range = match.range(at: 1)
|
||||
if let mappedRange = Range(range, in: fileContents) {
|
||||
groupTitle = String(fileContents[mappedRange])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mainFile = tempFile
|
||||
} else {
|
||||
let entryFileName = (entryPath as NSString).lastPathComponent
|
||||
|
|
@ -481,173 +454,205 @@ public class ShareRootControllerImpl {
|
|||
}
|
||||
} catch {
|
||||
}
|
||||
if let mainFile = mainFile {
|
||||
if let groupTitle = groupTitle {
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
|
||||
|
||||
//TODO:localize
|
||||
var attemptSelectionImpl: ((Peer) -> Void)?
|
||||
var createNewGroupImpl: (() -> Void)?
|
||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroups, .onlyManageable, .excludeDisabled, .doNotSearchMessages], hasContactSelector: false, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}, createNewGroup: {
|
||||
createNewGroupImpl?()
|
||||
}, pretendPresentedInModal: true))
|
||||
|
||||
controller.customDismiss = {
|
||||
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||
}
|
||||
|
||||
controller.peerSelected = { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}
|
||||
|
||||
controller.navigationPresentation = .default
|
||||
|
||||
let beginWithPeer: (PeerId) -> Void = { peerId in
|
||||
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
|
||||
|
||||
if let mainFile = mainFile, let mainFileText = try? String(contentsOf: URL(fileURLWithPath: mainFile.path)) {
|
||||
let mainFileHeader: String
|
||||
if mainFileText.count < 1000 {
|
||||
mainFileHeader = mainFileText
|
||||
} else {
|
||||
mainFileHeader = String(mainFileText[mainFileText.startIndex ..< mainFileText.index(mainFileText.startIndex, offsetBy: 1000)])
|
||||
}
|
||||
|
||||
let _ = (ChatHistoryImport.getInfo(account: context.account, header: mainFileHeader)
|
||||
|> deliverOnMainQueue).start(next: { parseInfo in
|
||||
switch parseInfo {
|
||||
case let .group(groupTitle):
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
|
||||
|
||||
//TODO:localize
|
||||
var attemptSelectionImpl: ((Peer) -> Void)?
|
||||
var createNewGroupImpl: (() -> Void)?
|
||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroups, .onlyManageable, .excludeDisabled, .doNotSearchMessages], hasContactSelector: false, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}, createNewGroup: {
|
||||
createNewGroupImpl?()
|
||||
}, pretendPresentedInModal: true))
|
||||
|
||||
controller.customDismiss = {
|
||||
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))
|
||||
}
|
||||
|
||||
attemptSelectionImpl = { peer in
|
||||
var errorText: String?
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if channel.flags.contains(.isCreator) || channel.adminRights != nil {
|
||||
} else {
|
||||
errorText = "You need to be an admin of the group to import messages into it."
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
switch group.role {
|
||||
case .creator:
|
||||
break
|
||||
default:
|
||||
errorText = "You need to be an admin of the group to import messages into it."
|
||||
}
|
||||
} else {
|
||||
errorText = "You can't import history into this group."
|
||||
}
|
||||
|
||||
if let errorText = errorText {
|
||||
controller.peerSelected = { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}
|
||||
|
||||
controller.navigationPresentation = .default
|
||||
|
||||
let beginWithPeer: (PeerId) -> Void = { peerId in
|
||||
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
|
||||
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))
|
||||
}
|
||||
|
||||
attemptSelectionImpl = { peer in
|
||||
var errorText: String?
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if channel.flags.contains(.isCreator) || channel.adminRights != nil {
|
||||
} else {
|
||||
errorText = "You need to be an admin of the group to import messages into it."
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
switch group.role {
|
||||
case .creator:
|
||||
break
|
||||
default:
|
||||
errorText = "You need to be an admin of the group to import messages into it."
|
||||
}
|
||||
} else {
|
||||
errorText = "You can't import history into this group."
|
||||
}
|
||||
|
||||
if let errorText = errorText {
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
})])
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
} else {
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let text: String
|
||||
if let groupTitle = groupTitle {
|
||||
text = "Are you sure you want to import messages from **\(groupTitle)** into **\(peer.debugDisplayTitle)**?"
|
||||
} else {
|
||||
text = "Are you sure you want to import messages into **\(peer.debugDisplayTitle)**?"
|
||||
}
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Import Messages", text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
}), TextAlertAction(type: .defaultAction, title: "Import", action: {
|
||||
beginWithPeer(peer.id)
|
||||
})], parseMarkdown: true)
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
}
|
||||
}
|
||||
|
||||
createNewGroupImpl = {
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
})])
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
} else {
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Import Messages", text: "Are you sure you want to import messages from **\(groupTitle)** into **\(peer.debugDisplayTitle)**?", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
}), TextAlertAction(type: .defaultAction, title: "Import", action: {
|
||||
beginWithPeer(peer.id)
|
||||
let resolvedGroupTitle: String
|
||||
if let groupTitle = groupTitle {
|
||||
resolvedGroupTitle = groupTitle
|
||||
} else {
|
||||
resolvedGroupTitle = "Group"
|
||||
}
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Create Group and Import Messages", text: "Are you sure you want to create group **\(resolvedGroupTitle)** and import messages from another messaging app?", actions: [TextAlertAction(type: .defaultAction, title: "Create and Import", action: {
|
||||
var signal: Signal<PeerId?, NoError> = createSupergroup(account: context.account, title: resolvedGroupTitle, description: nil, isForHistoryImport: true)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<PeerId?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||
if let strongSelf = self {
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
}
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.15, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.start()
|
||||
|
||||
signal = signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).start(next: { peerId in
|
||||
if let peerId = peerId {
|
||||
beginWithPeer(peerId)
|
||||
} else {
|
||||
//TODO:localize
|
||||
}
|
||||
})
|
||||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
})], parseMarkdown: true)
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
}
|
||||
}
|
||||
|
||||
createNewGroupImpl = {
|
||||
|
||||
navigationController.viewControllers = [controller]
|
||||
strongSelf.mainWindow?.present(navigationController, on: .root)
|
||||
case let .privateChat(title):
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Create Group and Import Messages", text: "Are you sure you want to create group **\(groupTitle)** and import messages from another messaging app?", actions: [TextAlertAction(type: .defaultAction, title: "Create and Import", action: {
|
||||
var signal: Signal<PeerId?, NoError> = createSupergroup(account: context.account, title: groupTitle, description: nil, isForHistoryImport: true)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<PeerId?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||
if let strongSelf = self {
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
}
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.15, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.start()
|
||||
|
||||
signal = signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).start(next: { peerId in
|
||||
if let peerId = peerId {
|
||||
beginWithPeer(peerId)
|
||||
} else {
|
||||
//TODO:localize
|
||||
}
|
||||
})
|
||||
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
})], parseMarkdown: true)
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
}
|
||||
|
||||
navigationController.viewControllers = [controller]
|
||||
strongSelf.mainWindow?.present(navigationController, on: .root)
|
||||
} else {
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
|
||||
|
||||
//TODO:localize
|
||||
var attemptSelectionImpl: ((Peer) -> Void)?
|
||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled, .doNotSearchMessages], hasChatListSelector: false, hasContactSelector: true, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}, pretendPresentedInModal: true))
|
||||
|
||||
controller.customDismiss = {
|
||||
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||
}
|
||||
|
||||
controller.peerSelected = { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}
|
||||
|
||||
controller.navigationPresentation = .default
|
||||
|
||||
let beginWithPeer: (PeerId) -> Void = { peerId in
|
||||
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
|
||||
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
|
||||
|
||||
//TODO:localize
|
||||
var attemptSelectionImpl: ((Peer) -> Void)?
|
||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled, .doNotSearchMessages], hasChatListSelector: false, hasContactSelector: true, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}, pretendPresentedInModal: true))
|
||||
|
||||
controller.customDismiss = {
|
||||
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))
|
||||
}
|
||||
|
||||
controller.peerSelected = { peer in
|
||||
attemptSelectionImpl?(peer)
|
||||
}
|
||||
|
||||
controller.navigationPresentation = .default
|
||||
|
||||
let beginWithPeer: (PeerId) -> Void = { peerId in
|
||||
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
|
||||
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))
|
||||
}
|
||||
|
||||
attemptSelectionImpl = { [weak controller] peer in
|
||||
controller?.inProgress = true
|
||||
let _ = (ChatHistoryImport.checkPeerImport(account: context.account, peerId: peer.id)
|
||||
|> deliverOnMainQueue).start(error: { error in
|
||||
controller?.inProgress = false
|
||||
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let errorText: String
|
||||
switch error {
|
||||
case .generic:
|
||||
errorText = presentationData.strings.Login_UnknownError
|
||||
case .userIsNotMutualContact:
|
||||
errorText = "You can only import messages into private chats with users who added you in their contact list."
|
||||
}
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
})])
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
}, completed: {
|
||||
controller?.inProgress = false
|
||||
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let text: String
|
||||
if let title = title {
|
||||
text = "Are you sure you want to import messages from **\(title)** into the chat with **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))**?"
|
||||
} else {
|
||||
text = "Are you sure you want to import messages into the chat with **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))**?"
|
||||
}
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Import Messages", text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
}), TextAlertAction(type: .defaultAction, title: "Import", action: {
|
||||
beginWithPeer(peer.id)
|
||||
})], parseMarkdown: true)
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
})
|
||||
}
|
||||
|
||||
navigationController.viewControllers = [controller]
|
||||
strongSelf.mainWindow?.present(navigationController, on: .root)
|
||||
}
|
||||
|
||||
attemptSelectionImpl = { [weak controller] peer in
|
||||
controller?.inProgress = true
|
||||
let _ = (ChatHistoryImport.checkPeerImport(account: context.account, peerId: peer.id)
|
||||
|> deliverOnMainQueue).start(error: { error in
|
||||
controller?.inProgress = false
|
||||
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let errorText: String
|
||||
switch error {
|
||||
case .generic:
|
||||
errorText = presentationData.strings.Login_UnknownError
|
||||
case .userIsNotMutualContact:
|
||||
errorText = "You can only import messages into private chats with users who added you in their contact list."
|
||||
}
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
})])
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
}, completed: {
|
||||
controller?.inProgress = false
|
||||
|
||||
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Import Messages", text: "Are you sure you want to import messages into the chat with **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))**?", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
}), TextAlertAction(type: .defaultAction, title: "Import", action: {
|
||||
beginWithPeer(peer.id)
|
||||
})], parseMarkdown: true)
|
||||
strongSelf.mainWindow?.present(controller, on: .root)
|
||||
})
|
||||
}
|
||||
|
||||
navigationController.viewControllers = [controller]
|
||||
strongSelf.mainWindow?.present(navigationController, on: .root)
|
||||
}
|
||||
}, error: { _ in
|
||||
beginShare()
|
||||
})
|
||||
} else {
|
||||
beginShare()
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1245,6 +1245,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public enum UndoOverlayContent {
|
|||
case invitedToVoiceChat(context: AccountContext, peer: Peer, text: String)
|
||||
case linkCopied(text: String)
|
||||
case banned(text: String)
|
||||
case importedMessage(text: String)
|
||||
}
|
||||
|
||||
public enum UndoOverlayAction {
|
||||
|
|
|
|||
|
|
@ -210,6 +210,18 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
self.textNode.maximumNumberOfLines = 2
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .importedMessage(text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode?.displayWithoutProcessing = true
|
||||
self.iconNode?.displaysAsynchronously = false
|
||||
self.iconNode?.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/ImportedMessageTooltipIcon"), color: .white)
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = nil
|
||||
self.animatedStickerNode = nil
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .chatAddedToFolder(chatTitle, folderTitle):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
|
|
@ -495,7 +507,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||
switch content {
|
||||
case .removedChat:
|
||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned:
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage:
|
||||
break
|
||||
case .dice:
|
||||
self.panelWrapperNode.clipsToBounds = true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue