diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 6d1350d3e0..6827a53ae3 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1069,6 +1069,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } + let _ = self.context.engine.privacy.updateGlobalPrivacySettings().start() let _ = (combineLatest( ApplicationSpecificNotice.displayChatListArchiveTooltip(accountManager: self.context.sharedContext.accountManager), self.context.engine.data.get( @@ -3280,6 +3281,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } func openArchiveMoreMenu(sourceView: UIView, gesture: ContextGesture?) { + let _ = self.context.engine.privacy.updateGlobalPrivacySettings().start() + let _ = ( self.context.engine.messages.chatList(group: .archive, count: 10) |> take(1) |> deliverOnMainQueue).start(next: { [weak self] archiveChatList in diff --git a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift index 003629e279..d43de75134 100644 --- a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift +++ b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift @@ -134,6 +134,8 @@ final class ChatListEmptyNode: ASDisplayNode { self.animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.animationTapGesture(_:)))) if case .archive = subject { + let _ = self.context.engine.privacy.updateGlobalPrivacySettings().start() + self.archiveSettingsDisposable = (context.engine.data.subscribe( TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy() ) diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 7f22b292db..3f9fdd1095 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -38,6 +38,18 @@ private final class TextNodeEmbeddedItem { } } +private final class TextNodeAttachment { + let range: NSRange + let frame: CGRect + let attachment: UIImage + + init(range: NSRange, frame: CGRect, attachment: UIImage) { + self.range = range + self.frame = frame + self.attachment = attachment + } +} + public struct TextRangeRectEdge: Equatable { public var x: CGFloat public var y: CGFloat @@ -59,8 +71,9 @@ private final class TextNodeLine { let spoilers: [TextNodeSpoiler] let spoilerWords: [TextNodeSpoiler] let embeddedItems: [TextNodeEmbeddedItem] + let attachments: [TextNodeAttachment] - init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool, strikethroughs: [TextNodeStrikethrough], spoilers: [TextNodeSpoiler], spoilerWords: [TextNodeSpoiler], embeddedItems: [TextNodeEmbeddedItem]) { + init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool, strikethroughs: [TextNodeStrikethrough], spoilers: [TextNodeSpoiler], spoilerWords: [TextNodeSpoiler], embeddedItems: [TextNodeEmbeddedItem], attachments: [TextNodeAttachment]) { self.line = line self.frame = frame self.range = range @@ -69,6 +82,7 @@ private final class TextNodeLine { self.spoilers = spoilers self.spoilerWords = spoilerWords self.embeddedItems = embeddedItems + self.attachments = attachments } } @@ -1084,6 +1098,7 @@ open class TextNode: ASDisplayNode { var spoilers: [TextNodeSpoiler] = [] var spoilerWords: [TextNodeSpoiler] = [] var embeddedItems: [TextNodeEmbeddedItem] = [] + var attachments: [TextNodeAttachment] = [] var lineConstrainedWidth = constrainedSize.width var lineConstrainedWidthDelta: CGFloat = 0.0 @@ -1159,6 +1174,24 @@ open class TextNode: ASDisplayNode { embeddedItems.append(TextNodeEmbeddedItem(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent), item: item)) } + func addAttachment(attachment: UIImage, line: CTLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int, rightInset: CGFloat = 0.0) { + var secondaryLeftOffset: CGFloat = 0.0 + let rawLeftOffset = CTLineGetOffsetForStringIndex(line, startIndex, &secondaryLeftOffset) + var leftOffset = floor(rawLeftOffset) + if !rawLeftOffset.isEqual(to: secondaryLeftOffset) { + leftOffset = floor(secondaryLeftOffset) + } + + var secondaryRightOffset: CGFloat = 0.0 + let rawRightOffset = CTLineGetOffsetForStringIndex(line, endIndex, &secondaryRightOffset) + var rightOffset = ceil(rawRightOffset) + if !rawRightOffset.isEqual(to: secondaryRightOffset) { + rightOffset = ceil(secondaryRightOffset) + } + + attachments.append(TextNodeAttachment(range: NSMakeRange(startIndex, endIndex - startIndex), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent), attachment: attachment)) + } + var isLastLine = false if maximumNumberOfLines != 0 && lines.count == maximumNumberOfLines - 1 && lineCharacterCount > 0 { isLastLine = true @@ -1307,6 +1340,14 @@ open class TextNode: ASDisplayNode { addEmbeddedItem(item: embeddedItem, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) } } + + if let attachment = attributes[NSAttributedString.Key.attachment] as? UIImage { + var ascent: CGFloat = 0.0 + var descent: CGFloat = 0.0 + CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) + + addAttachment(attachment: attachment, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) + } } } @@ -1328,7 +1369,7 @@ open class TextNode: ASDisplayNode { } } - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: embeddedItems)) + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: embeddedItems, attachments: attachments)) break } else { if lineCharacterCount > 0 { @@ -1398,6 +1439,14 @@ open class TextNode: ASDisplayNode { addEmbeddedItem(item: embeddedItem, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) } } + + if let attachment = attributes[NSAttributedString.Key.attachment] as? UIImage { + var ascent: CGFloat = 0.0 + var descent: CGFloat = 0.0 + CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) + + addAttachment(attachment: attachment, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) + } } let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) @@ -1418,7 +1467,7 @@ open class TextNode: ASDisplayNode { } } - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: embeddedItems)) + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: embeddedItems, attachments: attachments)) } else { if !lines.isEmpty { layoutSize.height += fontLineSpacing @@ -1854,6 +1903,7 @@ open class TextView: UIView { var strikethroughs: [TextNodeStrikethrough] = [] var spoilers: [TextNodeSpoiler] = [] var spoilerWords: [TextNodeSpoiler] = [] + var attachments: [TextNodeAttachment] = [] var lineConstrainedWidth = constrainedSize.width var lineConstrainedWidthDelta: CGFloat = 0.0 @@ -1911,6 +1961,24 @@ open class TextView: UIView { spoilerWords.append(TextNodeSpoiler(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent))) } + func addAttachment(attachment: UIImage, line: CTLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int, rightInset: CGFloat = 0.0) { + var secondaryLeftOffset: CGFloat = 0.0 + let rawLeftOffset = CTLineGetOffsetForStringIndex(line, startIndex, &secondaryLeftOffset) + var leftOffset = floor(rawLeftOffset) + if !rawLeftOffset.isEqual(to: secondaryLeftOffset) { + leftOffset = floor(secondaryLeftOffset) + } + + var secondaryRightOffset: CGFloat = 0.0 + let rawRightOffset = CTLineGetOffsetForStringIndex(line, endIndex, &secondaryRightOffset) + var rightOffset = ceil(rawRightOffset) + if !rawRightOffset.isEqual(to: secondaryRightOffset) { + rightOffset = ceil(secondaryRightOffset) + } + + attachments.append(TextNodeAttachment(range: NSMakeRange(startIndex, endIndex - startIndex), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent), attachment: attachment)) + } + var isLastLine = false if maximumNumberOfLines != 0 && lines.count == maximumNumberOfLines - 1 && lineCharacterCount > 0 { isLastLine = true @@ -2006,7 +2074,14 @@ open class TextView: UIView { strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) } else if let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle { headIndent = paragraphStyle.headIndent + } + + if let attachment = attributes[NSAttributedString.Key.attachment] as? UIImage { + var ascent: CGFloat = 0.0 + var descent: CGFloat = 0.0 + CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) + addAttachment(attachment: attachment, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) } } } @@ -2029,7 +2104,7 @@ open class TextView: UIView { } } - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: [])) + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: [], attachments: attachments)) break } else { if lineCharacterCount > 0 { @@ -2089,6 +2164,14 @@ open class TextView: UIView { } else if let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle { headIndent = paragraphStyle.headIndent } + + if let attachment = attributes[NSAttributedString.Key.attachment] as? UIImage { + var ascent: CGFloat = 0.0 + var descent: CGFloat = 0.0 + CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) + + addAttachment(attachment: attachment, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) + } } let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) @@ -2109,7 +2192,7 @@ open class TextView: UIView { } } - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: [])) + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: [], attachments: attachments)) } else { if !lines.isEmpty { layoutSize.height += fontLineSpacing @@ -2229,30 +2312,47 @@ open class TextView: UIView { context.clip(to: clipRects) } - let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray - if glyphRuns.count != 0 { - for run in glyphRuns { - let run = run as! CTRun - let glyphCount = CTRunGetGlyphCount(run) - CTRunDraw(run, context, CFRangeMake(0, glyphCount)) + if line.attachments.isEmpty { + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + if glyphRuns.count != 0 { + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + CTRunDraw(run, context, CFRangeMake(0, glyphCount)) + } + } + } else { + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + if glyphRuns.count != 0 { + for run in glyphRuns { + let run = run as! CTRun + + let stringRange = CTRunGetStringRange(run) + if line.attachments.contains(where: { $0.range.contains(stringRange.location) }) { + continue + } + + let glyphCount = CTRunGetGlyphCount(run) + CTRunDraw(run, context, CFRangeMake(0, glyphCount)) + } } } -// if !line.strikethroughs.isEmpty { -// for strikethrough in line.strikethroughs { -// var textColor: UIColor? -// layout.attributedString?.enumerateAttributes(in: NSMakeRange(line.range.location, line.range.length), options: []) { attributes, range, _ in -// if range == strikethrough.range, let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor { -// textColor = color -// } -// } -// if let textColor = textColor { -// context.setFillColor(textColor.cgColor) -// } -// let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY) -// context.fill(CGRect(x: frame.minX, y: frame.minY - 5.0, width: frame.width, height: 1.0)) -// } -// } + for attachment in line.attachments { + let image = attachment.attachment + var textColor: UIColor? + layout.attributedString?.enumerateAttributes(in: attachment.range, options: []) { attributes, range, _ in + if let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor { + textColor = color + } + } + if let textColor { + if let tintedImage = generateTintedImage(image: image, color: textColor) { + let imageRect = CGRect(origin: CGPoint(x: attachment.frame.midX - tintedImage.size.width * 0.5, y: attachment.frame.midY - tintedImage.size.height * 0.5 + 1.0), size: tintedImage.size).offsetBy(dx: lineFrame.minX, dy: lineFrame.minY) + context.draw(tintedImage.cgImage!, in: imageRect) + } + } + } if !line.spoilers.isEmpty { if layout.displaySpoilers { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift index 0097036dcb..b0a02f8dc8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift @@ -25,6 +25,10 @@ public extension TelegramEngine { return _internal_requestAccountPrivacySettings(account: self.account) } + public func updateGlobalPrivacySettings() -> Signal { + return _internal_updateGlobalPrivacySettings(account: self.account) + } + public func updateAccountAutoArchiveChats(value: Bool) -> Signal { return _internal_updateAccountAutoArchiveChats(account: self.account, value: value) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift index 42e3c22efd..040c33c17c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift @@ -3,6 +3,36 @@ import Postbox import TelegramApi import SwiftSignalKit +func _internal_updateGlobalPrivacySettings(account: Account) -> Signal { + return account.network.request(Api.functions.account.getGlobalPrivacySettings()) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> Void in + guard let result = result else { + return + } + let globalSettings: GlobalPrivacySettings + switch result { + case let .globalPrivacySettings(flags): + let automaticallyArchiveAndMuteNonContacts = (flags & (1 << 0)) != 0 + let keepArchivedUnmuted = (flags & (1 << 1)) != 0 + let keepArchivedFolders = (flags & (1 << 2)) != 0 + globalSettings = GlobalPrivacySettings( + automaticallyArchiveAndMuteNonContacts: automaticallyArchiveAndMuteNonContacts, + keepArchivedUnmuted: keepArchivedUnmuted, + keepArchivedFolders: keepArchivedFolders + ) + } + updateGlobalPrivacySettings(transaction: transaction, { _ in + return globalSettings + }) + } + |> ignoreValues + } +} func _internal_requestAccountPrivacySettings(account: Account) -> Signal { let lastSeenPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyStatusTimestamp)) diff --git a/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoContentComponent.swift b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoContentComponent.swift index a5020116f7..9c009633e5 100644 --- a/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoContentComponent.swift +++ b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/Sources/ArchiveInfoContentComponent.swift @@ -57,6 +57,8 @@ public final class ArchiveInfoContentComponent: Component { private let title = ComponentView() private let mainText = ComponentView() + private var chevronImage: UIImage? + private var items: [Item] = [] private var component: ArchiveInfoContentComponent? @@ -151,34 +153,43 @@ public final class ArchiveInfoContentComponent: Component { contentHeight += 16.0 let text: String + //TODO:localize if component.settings.keepArchivedUnmuted { - text = "Archived chats will remain in the Archive when you receive a new message. [Tap to change 〉]()" + text = "Archived chats will remain in the Archive when you receive a new message. [Tap to change >]()" } else { - text = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. [Tap to change 〉]()" + text = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. [Tap to change >]()" + } + + let mainText = NSMutableAttributedString() + mainText.append(parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes( + body: MarkdownAttributeSet( + font: Font.regular(15.0), + textColor: component.theme.list.itemSecondaryTextColor + ), + bold: MarkdownAttributeSet( + font: Font.semibold(15.0), + textColor: component.theme.list.itemSecondaryTextColor + ), + link: MarkdownAttributeSet( + font: Font.regular(15.0), + textColor: component.theme.list.itemAccentColor, + additionalAttributes: [:] + ), + linkAttribute: { attributes in + return ("URL", "") + } + ))) + if self.chevronImage == nil { + self.chevronImage = UIImage(bundleImageName: "Settings/TextArrowRight") + } + if let range = mainText.string.range(of: ">"), let chevronImage = self.chevronImage { + mainText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: mainText.string)) } - //TODO:localize let mainTextSize = self.mainText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .markdown(text: text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet( - font: Font.regular(15.0), - textColor: component.theme.list.itemSecondaryTextColor - ), - bold: MarkdownAttributeSet( - font: Font.semibold(15.0), - textColor: component.theme.list.itemSecondaryTextColor - ), - link: MarkdownAttributeSet( - font: Font.regular(15.0), - textColor: component.theme.list.itemAccentColor, - additionalAttributes: [:] - ), - linkAttribute: { attributes in - return ("URL", "") - } - )), + text: .plain(mainText), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.2, diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift index 69c720d610..b1edf8c56f 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift @@ -815,6 +815,8 @@ public final class StoryPeerListItemComponent: Component { self?.indicatorShapeSeenLayer.lineWidth = initialLineWidth self?.indicatorShapeSeenLayer.animateShapeLineWidth(from: targetLineWidth, to: initialLineWidth, duration: 0.15) }) + + HapticFeedback().success() } let titleSize = self.title.update( diff --git a/submodules/TelegramUI/Images.xcassets/Settings/TextArrowRight.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/TextArrowRight.imageset/Contents.json new file mode 100644 index 0000000000..53575b0ae5 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/TextArrowRight.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "chevron.right.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/TextArrowRight.imageset/chevron.right.svg b/submodules/TelegramUI/Images.xcassets/Settings/TextArrowRight.imageset/chevron.right.svg new file mode 100644 index 0000000000..4b0f638649 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/TextArrowRight.imageset/chevron.right.svg @@ -0,0 +1,11 @@ + + + + + + + + +