[WIP] Chat Import

This commit is contained in:
Ali 2021-01-22 01:34:37 +04:00
parent b3740d69b3
commit 428766c75b
10 changed files with 441 additions and 8 deletions

View File

@ -22,6 +22,7 @@ public final class ChatImportActivityScreen: ViewController {
private let animationNode: AnimatedStickerNode
private let radialStatus: RadialStatusNode
private let radialCheck: RadialStatusNode
private let radialStatusBackground: ASImageNode
private let radialStatusText: ImmediateTextNode
private let progressText: ImmediateTextNode
@ -46,6 +47,7 @@ public final class ChatImportActivityScreen: ViewController {
self.animationNode = AnimatedStickerNode()
self.radialStatus = RadialStatusNode(backgroundNodeColor: .clear)
self.radialCheck = RadialStatusNode(backgroundNodeColor: .clear)
self.radialStatusBackground = ASImageNode()
self.radialStatusBackground.isUserInteractionEnabled = false
self.radialStatusBackground.displaysAsynchronously = false
@ -90,6 +92,7 @@ public final class ChatImportActivityScreen: ViewController {
self.addSubnode(self.animationNode)
self.addSubnode(self.radialStatusBackground)
self.addSubnode(self.radialStatus)
self.addSubnode(self.radialCheck)
self.addSubnode(self.radialStatusText)
self.addSubnode(self.progressText)
self.addSubnode(self.statusText)
@ -159,6 +162,8 @@ public final class ChatImportActivityScreen: ViewController {
self.animationNode.updateLayout(size: iconSize)
self.radialStatus.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - radialStatusSize.width) / 2.0), y: hideIcon ? contentOriginY : (contentOriginY + iconSize.height + maxIconStatusSpacing)), size: radialStatusSize)
let checkSize: CGFloat = 130.0
self.radialCheck.frame = CGRect(origin: CGPoint(x: self.radialStatus.frame.minX + floor((self.radialStatus.frame.width - checkSize) / 2.0), y: self.radialStatus.frame.minY + floor((self.radialStatus.frame.height - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize))
self.radialStatusBackground.frame = self.radialStatus.frame
self.radialStatusText.frame = CGRect(origin: CGPoint(x: self.radialStatus.frame.minX + floor((self.radialStatus.frame.width - radialStatusTextSize.width) / 2.0), y: self.radialStatus.frame.minY + floor((self.radialStatus.frame.height - radialStatusTextSize.height) / 2.0)), size: radialStatusTextSize)
@ -187,6 +192,18 @@ public final class ChatImportActivityScreen: ViewController {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
self.radialStatus.transitionToState(.progress(color: self.presentationData.theme.list.itemAccentColor, lineWidth: 6.0, value: self.totalProgress, cancelEnabled: false), animated: animated, synchronous: true, completion: {})
if isDone {
self.radialCheck.transitionToState(.progress(color: .clear, lineWidth: 6.0, value: self.totalProgress, cancelEnabled: false), animated: false, synchronous: true, completion: {})
self.radialCheck.transitionToState(.check(self.presentationData.theme.list.itemAccentColor), animated: animated, synchronous: true, completion: {})
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.2, curve: .easeInOut)
} else {
transition = .immediate
}
transition.updateAlpha(node: self.radialStatusText, alpha: 0.0)
}
}
}
}

View File

@ -106,6 +106,373 @@ static void generate_public_key(unsigned char key[32], id<EncryptionProvider> pr
}
}
typedef enum {
HelloGenerationCommandInvalid = 0,
HelloGenerationCommandString = 1,
HelloGenerationCommandZero = 2,
HelloGenerationCommandRandom = 3,
HelloGenerationCommandDomain = 4,
HelloGenerationCommandGrease = 5,
HelloGenerationCommandKey = 6,
HelloGenerationCommandPushLengthPosition = 7,
HelloGenerationCommandPopLengthPosition = 8
} HelloGenerationCommand;
typedef struct {
int position;
} HelloParseState;
static HelloGenerationCommand parseCommand(NSString *string, HelloParseState *state) {
if (state->position + 1 >= string.length) {
return HelloGenerationCommandInvalid;
}
unichar c = [string characterAtIndex:state->position];
state->position += 1;
if (c == 'S') {
return HelloGenerationCommandString;
} else if (c == 'Z') {
return HelloGenerationCommandZero;
} else if (c == 'R') {
return HelloGenerationCommandRandom;
} else if (c == 'D') {
return HelloGenerationCommandDomain;
} else if (c == 'G') {
return HelloGenerationCommandGrease;
} else if (c == 'K') {
return HelloGenerationCommandKey;
} else if (c == '[') {
return HelloGenerationCommandPushLengthPosition;
} else if (c == ']') {
return HelloGenerationCommandPopLengthPosition;
} else {
return HelloGenerationCommandInvalid;
}
}
static bool parseSpace(NSString *string, HelloParseState *state) {
if (state->position + 1 >= string.length) {
return false;
}
bool hadSpace = false;
while (true) {
unichar c = [string characterAtIndex:state->position];
state->position += 1;
if (c == ' ') {
hadSpace = true;
} else {
if (hadSpace) {
return true;
} else {
return false;
}
}
}
return true;
}
static bool parseEndlineOrEnd(NSString *string, HelloParseState *state) {
if (state->position == string.length) {
return true;
} else if (state->position + 1 >= string.length) {
return false;
} else {
unichar c = [string characterAtIndex:state->position];
state->position += 1;
return c == '\n';
}
}
static bool parseHexByte(unichar c, uint8_t *output) {
if (c >= '0' && c <= '9') {
*output = (uint8_t)(c - '0');
} else if (c >= 'a' && c <= 'f') {
*output = (uint8_t)(c - 'a');
} else if (c >= 'A' && c <= 'F') {
*output = (uint8_t)(c - 'A');
} else {
return false;
}
return true;
}
static NSData *parseHexStringArgument(NSString *string, HelloParseState *state) {
if (state->position >= string.length) {
return nil;
}
NSMutableData *data = [[NSMutableData alloc] init];
while (true) {
if (state->position == string.length) {
return data;
}
unichar c = [string characterAtIndex:state->position];
state->position += 1;
if (c == '\\') {
if (state->position >= string.length) {
return nil;
}
c = [string characterAtIndex:state->position];
state->position += 1;
if (c == 'x') {
if (state->position >= string.length) {
return nil;
}
unichar d1 = [string characterAtIndex:state->position];
state->position += 1;
if (state->position >= string.length) {
return nil;
}
unichar d0 = [string characterAtIndex:state->position];
state->position += 1;
uint8_t c1 = 0;
if (!parseHexByte(d1, &c1)) {
return nil;
}
uint8_t c0 = 0;
if (!parseHexByte(d0, &c0)) {
return nil;
}
uint8_t byteValue = (c1 << 4) | c0;
[data appendBytes:&byteValue length:1];
} else {
return nil;
}
} else if (c == '\n') {
return data;
} else {
return nil;
}
}
return nil;
}
static bool parseIntArgument(NSString *string, HelloParseState *state, int *output) {
if (state->position >= string.length) {
return false;
}
int value = 0;
while (true) {
if (state->position == string.length) {
*output = value;
return true;
}
unichar c = [string characterAtIndex:state->position];
state->position += 1;
if (c == '\n') {
*output = value;
return true;
} else if (c >= '0' && c <= '9') {
value *= 10;
value += c;
} else {
return false;
}
}
return false;
}
static NSData *executeGenerationCode(id<EncryptionProvider> provider, NSData *domain) {
NSString *code = @"S \"\\x16\\x03\\x01\\x02\\x00\\x01\\x00\\x01\\xfc\\x03\\x03\\n"
"Z 32"
"S \"\\x20\"\n"
"R 32\n"
"S \"\\x00\\x36\"\n"
"G 0\n"
"S \"\\x13\\x01\\x13\\x02\\x13\\x03\\xc0\\x2c\\xc0\\x2b\\xcc\\xa9\\xc0\\x30\\xc0\\x2f\\xcc\\xa8\\xc0\\x24\\xc0\\x23\\xc0\\x0a\\xc0\\x09\\xc0\\x28\\xc0\\x27\\xc0\\x14\\xc0\\x13\\x00\\x9d\\x00\\x9c\\x00\\x3d\\x00\\x3c\\x00\\x35\\x00\\x2f\\xc0\\x08\\xc0\\x12\\x00\\x0a\\x01\\x00\\x01\\x7d\"\n"
"G 2\n"
"S \"\\x00\\x00\\x00\\x00\"\n"
"[\n"
"[\n"
"S \"\\x00\"\n"
"[\n"
"D\n"
"]\n"
"]\n"
"]\n"
"S \"\\x00\\x17\\x00\\x00\\xff\\x01\\x00\\x01\\x00\\x00\\x0a\\x00\\x0c\\x00\\x0a\"\n"
"G 4\n"
"S \"\\x00\\x1d\\x00\\x17\\x00\\x18\\x00\\x19\\x00\\x0b\\x00\\x02\\x01\\x00\\x00\\x10\\x00\\x0e\\x00\\x0c\\x02\\x68\\x32\\x08\\x68\\x74\\x74\\x70\\x2f\\x31\\x2e\\x31\\x00\\x05\\x00\\x05\\x01\\x00\\x00\\x00\\x00\\x00\\x0d\\x00\\x18\\x00\\x16\\x04\\x03\\x08\\x04\\x04\\x01\\x05\\x03\\x02\\x03\\x08\\x05\\x08\\x05\\x05\\x01\\x08\\x06\\x06\\x01\\x02\\x01\\x00\\x12\\x00\\x00\\x00\\x33\\x00\\x2b\\x00\\x29\"\n"
"G 4\n"
"S \"\\x00\\x01\\x00\\x00\\x1d\\x00\\x20\"\n"
"K\n"
"S \"\\x00\\x2d\\x00\\x02\\x01\\x01\\x00\\x2b\\x00\\x0b\\x0a\"\n"
"G 6\n"
"S \"\\x03\\x04\\x03\\x03\\x03\\x02\\x03\\x01\"\n"
"G 3\n"
"S \"\\x00\\x01\\x00\\x00\\x15\"";
int greaseCount = 8;
NSMutableData *greaseData = [[NSMutableData alloc] initWithLength:greaseCount];
uint8_t *greaseBytes = (uint8_t *)greaseData.mutableBytes;
int result;
result = SecRandomCopyBytes(nil, greaseData.length, greaseData.mutableBytes);
for (int i = 0; i < greaseData.length; i++) {
uint8_t c = greaseBytes[i];
c = (c & 0xf0) | 0x0a;
greaseBytes[i] = c;
}
for (int i = 1; i < greaseData.length; i += 2) {
if (greaseBytes[i] == greaseBytes[i - 1]) {
greaseBytes[i] &= 0x10;
}
}
NSMutableData *resultData = [[NSMutableData alloc] init];
NSMutableArray<NSNumber *> *lengthStack = [[NSMutableArray alloc] init];
HelloParseState state;
state.position = 0;
while (true) {
if (state.position >= code.length) {
break;
} else {
HelloGenerationCommand command = parseCommand(code, &state);
switch (command) {
case HelloGenerationCommandString: {
if (!parseSpace(code, &state)) {
return nil;
}
NSData *data = parseHexStringArgument(code, &state);
if (data == nil) {
return nil;
}
[resultData appendData:data];
break;
}
case HelloGenerationCommandZero: {
if (!parseSpace(code, &state)) {
return false;
}
int zeroLength = 0;
if (!parseIntArgument(code, &state, &zeroLength)) {
return nil;
}
NSMutableData *zeroData = [[NSMutableData alloc] initWithLength:zeroLength];
[resultData appendData:zeroData];
break;
}
case HelloGenerationCommandRandom: {
if (!parseSpace(code, &state)) {
return nil;
}
int randomLength = 0;
if (!parseIntArgument(code, &state, &randomLength)) {
return nil;
}
NSMutableData *randomData = [[NSMutableData alloc] initWithLength:randomLength];
int randomResult = SecRandomCopyBytes(kSecRandomDefault, randomLength, randomData.mutableBytes);
if (randomResult != errSecSuccess) {
return nil;
}
[resultData appendData:randomData];
break;
}
case HelloGenerationCommandDomain: {
[resultData appendData:domain];
if (!parseEndlineOrEnd(code, &state)) {
return nil;
}
break;
}
case HelloGenerationCommandGrease: {
if (!parseSpace(code, &state)) {
return nil;
}
int greaseIndex = 0;
if (!parseIntArgument(code, &state, &greaseIndex)) {
return nil;
}
if (greaseIndex < 0 || greaseIndex >= greaseCount) {
return nil;
}
[resultData appendBytes:&greaseBytes[greaseIndex] length:1];
[resultData appendBytes:&greaseBytes[greaseIndex] length:1];
break;
}
case HelloGenerationCommandKey: {
if (!parseEndlineOrEnd(code, &state)) {
return nil;
}
NSMutableData *key = [[NSMutableData alloc] initWithLength:32];
generate_public_key(key.mutableBytes, provider);
[resultData appendData:key];
break;
}
case HelloGenerationCommandPushLengthPosition: {
if (!parseEndlineOrEnd(code, &state)) {
return nil;
}
[lengthStack addObject:@(resultData.length)];
NSMutableData *zeroData = [[NSMutableData alloc] initWithLength:2];
[resultData appendData:zeroData];
break;
}
case HelloGenerationCommandPopLengthPosition: {
if (!parseEndlineOrEnd(code, &state)) {
return nil;
}
if (lengthStack.count == 0) {
return nil;
}
int position = [lengthStack[lengthStack.count - 1] intValue];
uint16_t calculatedLength = resultData.length - 2 - position;
((uint8_t *)resultData.mutableBytes)[position] = ((uint8_t *)&calculatedLength)[1];
((uint8_t *)resultData.mutableBytes)[position + 1] = ((uint8_t *)&calculatedLength)[0];
[lengthStack removeLastObject];
break;
}
case HelloGenerationCommandInvalid: {
return nil;
}
default: {
return nil;
}
}
}
}
int paddingLengthPosition = (int)resultData.length;
[lengthStack addObject:@(resultData.length)];
NSMutableData *zeroData = [[NSMutableData alloc] initWithLength:2];
[resultData appendData:zeroData];
while (resultData.length < 517) {
uint8_t zero = 0;
[resultData appendBytes:&zero length:1];
}
uint16_t calculatedLength = resultData.length - 2 - paddingLengthPosition;
((uint8_t *)resultData.mutableBytes)[paddingLengthPosition] = ((uint8_t *)&calculatedLength)[1];
((uint8_t *)resultData.mutableBytes)[paddingLengthPosition + 1] = ((uint8_t *)&calculatedLength)[0];
return resultData;
}
@interface MTTcpConnectionData : NSObject
@property (nonatomic, strong, readonly) NSString *ip;

View File

@ -104,6 +104,8 @@ public extension Message {
if let peer = self.peers[sourceReference.messageId.peerId] {
return peer
}
} else if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported), let author = forwardInfo.author {
return author
}
return self.author
}

View File

@ -52,9 +52,15 @@ final class ChatMessageAvatarAccessoryItem: ListViewAccessoryItem {
return false
}
if let forwardInfo = self.forwardInfo, let otherForwardInfo = other.forwardInfo {
if forwardInfo.flags.contains(.isImported) == forwardInfo.flags.contains(.isImported) {
if forwardInfo.authorSignature != otherForwardInfo.authorSignature {
return false
if forwardInfo.flags.contains(.isImported) && forwardInfo.flags.contains(.isImported) == forwardInfo.flags.contains(.isImported) {
if let authorSignature = forwardInfo.authorSignature, let otherAuthorSignature = otherForwardInfo.authorSignature {
if authorSignature != otherAuthorSignature {
return false
}
} else if let authorId = forwardInfo.author?.id, let otherAuthorId = other.forwardInfo?.author?.id {
if authorId != otherAuthorId {
return false
}
}
} else {
return false
@ -74,7 +80,9 @@ final class ChatMessageAvatarAccessoryItem: ListViewAccessoryItem {
let node = ChatMessageAvatarAccessoryItemNode()
node.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported) {
if let authorSignature = forwardInfo.authorSignature, !authorSignature.isEmpty {
if let author = forwardInfo.author {
node.setPeer(context: self.context, theme: self.context.sharedContext.currentPresentationData.with({ $0 }).theme, synchronousLoad: synchronous, peer: author, authorOfMessage: self.messageReference, emptyColor: self.emptyColor, controllerInteraction: self.controllerInteraction)
} else if let authorSignature = forwardInfo.authorSignature, !authorSignature.isEmpty {
let components = authorSignature.components(separatedBy: " ")
if !components.isEmpty, !components[0].hasPrefix("+") {
var letters: [String] = []

View File

@ -56,10 +56,16 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
var isFile = false
inner: for media in message.media {
if let _ = media as? TelegramMediaImage {
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
messageWithCaptionToAdd = (message, itemAttributes)
}
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
} else if let file = media as? TelegramMediaFile {
let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil)
if isVideo {
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
messageWithCaptionToAdd = (message, itemAttributes)
}
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
} else {
var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing = .default
@ -1036,6 +1042,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
effectiveAuthor = source
displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil
} else if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.flags.contains(.isImported), let author = forwardInfo.author {
ignoreForward = true
effectiveAuthor = author
displayAuthorInfo = !mergedTop.merged && incoming
} else if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature {
ignoreForward = true
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags())

View File

@ -131,7 +131,11 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs
lhsEffectiveTimestamp = lhsForwardInfo.date
rhsEffectiveTimestamp = rhsForwardInfo.date
sameAuthor = lhsForwardInfo.authorSignature == rhsForwardInfo.authorSignature
if let lhsAuthorId = lhsForwardInfo.author?.id, let rhsAuthorId = rhsForwardInfo.author?.id {
sameAuthor = lhsAuthorId == rhsAuthorId
} else if let lhsAuthorSignature = lhsForwardInfo.authorSignature, let rhsAuthorSignature = rhsForwardInfo.authorSignature {
sameAuthor = lhsAuthorSignature == rhsAuthorSignature
}
}
if lhs.id.peerId.isRepliesOrSavedMessages(accountPeerId: accountPeerId) {

View File

@ -55,7 +55,16 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
let titleFont = Font.medium(fontSize)
let textFont = Font.regular(fontSize)
let titleString = message.effectiveAuthor?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? strings.User_DeletedAccount
var titleString = message.effectiveAuthor?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? strings.User_DeletedAccount
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
if let author = forwardInfo.author {
titleString = author.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)
} else if let authorSignature = forwardInfo.authorSignature {
titleString = authorSignature
}
}
let (textString, isMedia) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: message, strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: context.account.peerId)
let placeholderColor: UIColor = message.effectivelyIncoming(context.account.peerId) ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor

View File

@ -277,8 +277,10 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
if let entities = entities {
attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont)
} else {
} else if !rawText.isEmpty {
attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.primaryTextColor)
} else {
attributedText = NSAttributedString(string: " ", font: textFont, textColor: messageTheme.primaryTextColor)
}
var cutout: TextNodeCutout?
@ -339,6 +341,12 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
}
if attributedText.string.isEmpty, var adjustedStatusFrameValue = adjustedStatusFrame {
adjustedStatusFrameValue.origin.y = 1.0
boundingSize.height = adjustedStatusFrameValue.maxY + 5.0
adjustedStatusFrame = adjustedStatusFrameValue
}
return (boundingSize, { [weak self] animation, _ in
if let strongSelf = self {
strongSelf.item = item

View File

@ -76,7 +76,13 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
let message = messageView.message
var authorName = ""
var text = ""
if let author = message?.effectiveAuthor {
if let forwardInfo = message?.forwardInfo, forwardInfo.flags.contains(.isImported) {
if let author = forwardInfo.author {
authorName = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
} else if let authorSignature = forwardInfo.authorSignature {
authorName = authorSignature
}
} else if let author = message?.effectiveAuthor {
authorName = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
if let message = message {

View File

@ -490,6 +490,7 @@ public class ShareRootControllerImpl {
controller.navigationPresentation = .default
let beginWithPeer: (PeerId) -> Void = { peerId in
navigationController.view.endEditing(true)
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))
@ -607,6 +608,7 @@ public class ShareRootControllerImpl {
controller.navigationPresentation = .default
let beginWithPeer: (PeerId) -> Void = { peerId in
navigationController.view.endEditing(true)
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))