Update Privacy and Security

This commit is contained in:
Peter 2019-05-18 12:25:04 +02:00
parent 87c91d57b2
commit 775e2cb852
38 changed files with 3690 additions and 3189 deletions

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "blocked.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "faceid.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "sessions.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "touchid.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "2step.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -308,6 +308,7 @@
D0428200200E6A00009DDE36 /* ChatRecentActionsHistoryTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04281FF200E6A00009DDE36 /* ChatRecentActionsHistoryTransition.swift */; }; D0428200200E6A00009DDE36 /* ChatRecentActionsHistoryTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04281FF200E6A00009DDE36 /* ChatRecentActionsHistoryTransition.swift */; };
D0430B001FF4570500A35ADD /* WebController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0430AFF1FF4570500A35ADD /* WebController.swift */; }; D0430B001FF4570500A35ADD /* WebController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0430AFF1FF4570500A35ADD /* WebController.swift */; };
D0430B021FF4584100A35ADD /* WebControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0430B011FF4584100A35ADD /* WebControllerNode.swift */; }; D0430B021FF4584100A35ADD /* WebControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0430B011FF4584100A35ADD /* WebControllerNode.swift */; };
D0439B5B228EC4A00067E026 /* ChatMessagePhoneNumberRequestContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0439B5A228EC4A00067E026 /* ChatMessagePhoneNumberRequestContentNode.swift */; };
D044A0F320BDA05800326FAC /* ThrottledValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D044A0F220BDA05800326FAC /* ThrottledValue.swift */; }; D044A0F320BDA05800326FAC /* ThrottledValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D044A0F220BDA05800326FAC /* ThrottledValue.swift */; };
D044A0FB20BDC40C00326FAC /* CachedChannelAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D044A0FA20BDC40C00326FAC /* CachedChannelAdmins.swift */; }; D044A0FB20BDC40C00326FAC /* CachedChannelAdmins.swift in Sources */ = {isa = PBXBuildFile; fileRef = D044A0FA20BDC40C00326FAC /* CachedChannelAdmins.swift */; };
D045549A21B2F173007A6DD9 /* libturbojpeg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D045549921B2F173007A6DD9 /* libturbojpeg.a */; }; D045549A21B2F173007A6DD9 /* libturbojpeg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D045549921B2F173007A6DD9 /* libturbojpeg.a */; };
@ -1612,6 +1613,7 @@
D042C6891E8DAAB000C863B0 /* ChatItemGalleryFooterContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatItemGalleryFooterContentNode.swift; sourceTree = "<group>"; }; D042C6891E8DAAB000C863B0 /* ChatItemGalleryFooterContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatItemGalleryFooterContentNode.swift; sourceTree = "<group>"; };
D0430AFF1FF4570500A35ADD /* WebController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebController.swift; sourceTree = "<group>"; }; D0430AFF1FF4570500A35ADD /* WebController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebController.swift; sourceTree = "<group>"; };
D0430B011FF4584100A35ADD /* WebControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebControllerNode.swift; sourceTree = "<group>"; }; D0430B011FF4584100A35ADD /* WebControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebControllerNode.swift; sourceTree = "<group>"; };
D0439B5A228EC4A00067E026 /* ChatMessagePhoneNumberRequestContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagePhoneNumberRequestContentNode.swift; sourceTree = "<group>"; };
D044A0F220BDA05800326FAC /* ThrottledValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThrottledValue.swift; sourceTree = "<group>"; }; D044A0F220BDA05800326FAC /* ThrottledValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThrottledValue.swift; sourceTree = "<group>"; };
D044A0FA20BDC40C00326FAC /* CachedChannelAdmins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedChannelAdmins.swift; sourceTree = "<group>"; }; D044A0FA20BDC40C00326FAC /* CachedChannelAdmins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedChannelAdmins.swift; sourceTree = "<group>"; };
D045549921B2F173007A6DD9 /* libturbojpeg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libturbojpeg.a; path = "third-party/libjpeg-turbo/libturbojpeg.a"; sourceTree = "<group>"; }; D045549921B2F173007A6DD9 /* libturbojpeg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libturbojpeg.a; path = "third-party/libjpeg-turbo/libturbojpeg.a"; sourceTree = "<group>"; };
@ -4774,6 +4776,7 @@
D018BE57218C7BD800C02DDC /* ChatMessageDeliveryFailedNode.swift */, D018BE57218C7BD800C02DDC /* ChatMessageDeliveryFailedNode.swift */,
D0AB262821C307D7008F6685 /* ChatMessagePollBubbleContentNode.swift */, D0AB262821C307D7008F6685 /* ChatMessagePollBubbleContentNode.swift */,
099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */, 099529AF21D2123E00805E13 /* ChatMessageUnsupportedBubbleContentNode.swift */,
D0439B5A228EC4A00067E026 /* ChatMessagePhoneNumberRequestContentNode.swift */,
); );
name = Items; name = Items;
sourceTree = "<group>"; sourceTree = "<group>";
@ -5873,6 +5876,7 @@
D0E9BA2B1F0557A600F079A4 /* STPFormEncoder.m in Sources */, D0E9BA2B1F0557A600F079A4 /* STPFormEncoder.m in Sources */,
D01BAA1C1ECC92F700295217 /* CallListViewTransition.swift in Sources */, D01BAA1C1ECC92F700295217 /* CallListViewTransition.swift in Sources */,
D0FBE84F2273395C00B33B52 /* ChatListArchiveInfoItem.swift in Sources */, D0FBE84F2273395C00B33B52 /* ChatListArchiveInfoItem.swift in Sources */,
D0439B5B228EC4A00067E026 /* ChatMessagePhoneNumberRequestContentNode.swift in Sources */,
09F664D021EBCFB900AB7E26 /* WallpaperCropNode.swift in Sources */, 09F664D021EBCFB900AB7E26 /* WallpaperCropNode.swift in Sources */,
D097C26C20DD1EA5007BB4B8 /* OverlayStatusController.swift in Sources */, D097C26C20DD1EA5007BB4B8 /* OverlayStatusController.swift in Sources */,
D0EC6D9E1EB9F58900EBF1C3 /* ChatMessageWebpageBubbleContentNode.swift in Sources */, D0EC6D9E1EB9F58900EBF1C3 /* ChatMessageWebpageBubbleContentNode.swift in Sources */,

View File

@ -198,7 +198,7 @@ public final class CallListController: ViewController {
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak controller, weak self] peer in |> deliverOnMainQueue).start(next: { [weak controller, weak self] peer in
controller?.dismissSearch() controller?.dismissSearch()
if let strongSelf = self, let contactPeer = peer, case let .peer(peer, _) = contactPeer { if let strongSelf = self, let contactPeer = peer, case let .peer(peer, _, _) = contactPeer {
strongSelf.call(peer.id, began: { strongSelf.call(peer.id, began: {
if let strongSelf = self { if let strongSelf = self {
let _ = (strongSelf.context.sharedContext.hasOngoingCall.get() let _ = (strongSelf.context.sharedContext.hasOngoingCall.get()

View File

@ -338,7 +338,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { members in |> deliverOnMainQueue).start(next: { members in
let disabledIds = members?.compactMap({$0.peer.id}) ?? [] let disabledIds = members?.compactMap({$0.peer.id}) ?? []
let contactsController = ContactMultiselectionController(context: context, mode: .peerSelection(searchChatList: false), options: [], filters: [.excludeSelf, .disable(disabledIds)]) let contactsController = ContactMultiselectionController(context: context, mode: .peerSelection(searchChatList: false, searchGroups: false), options: [], filters: [.excludeSelf, .disable(disabledIds)])
addMembersDisposable.set((contactsController.result addMembersDisposable.set((contactsController.result
|> deliverOnMainQueue |> deliverOnMainQueue

View File

@ -200,6 +200,8 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
} }
case .payment: case .payment:
break break
case .urlAuth:
break
} }
} }
} }

View File

@ -4545,7 +4545,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
if let strongSelf = self, let peer = peer { if let strongSelf = self, let peer = peer {
let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError>
switch peer { switch peer {
case let .peer(contact, _): case let .peer(contact, _, _):
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
return return
} }

View File

@ -752,6 +752,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
} }
} }
var contentRequiredValidation = false
for attribute in message.attributes { for attribute in message.attributes {
if attribute is ViewCountMessageAttribute { if attribute is ViewCountMessageAttribute {
if message.id.namespace == Namespaces.Message.Cloud { if message.id.namespace == Namespaces.Message.Cloud {
@ -759,13 +760,18 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
} else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { } else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed {
hasUnconsumedContent = true hasUnconsumedContent = true
} else if let _ = attribute as? ContentRequiresValidationMessageAttribute {
contentRequiredValidation = true
} }
} }
for media in message.media { for media in message.media {
if let _ = media as? TelegramMediaUnsupported { if let _ = media as? TelegramMediaUnsupported {
messageIdsWithUnsupportedMedia.append(message.id) contentRequiredValidation = true
} }
} }
if contentRequiredValidation {
messageIdsWithUnsupportedMedia.append(message.id)
}
if hasUnconsumedMention && !hasUnconsumedContent { if hasUnconsumedMention && !hasUnconsumedContent {
messageIdsWithUnseenPersonalMention.append(message.id) messageIdsWithUnseenPersonalMention.append(message.id)
} }

View File

@ -171,7 +171,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A
let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up
let chatScrollPosition = ChatHistoryViewScrollPosition.index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) let chatScrollPosition = ChatHistoryViewScrollPosition.index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated)
var first = true var first = true
return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: 200, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData) return account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: index, anchorIndex: anchorIndex, count: 100, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData)
|> map { view, updateType, initialData -> ChatHistoryViewUpdate in |> map { view, updateType, initialData -> ChatHistoryViewUpdate in
let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation) let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation)

View File

@ -417,6 +417,8 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st
attributedString = NSAttributedString(string: strings.Notification_PassportValuesSentMessage(message.peers[message.id.peerId]?.compactDisplayTitle ?? "", typesString).0, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_PassportValuesSentMessage(message.peers[message.id.peerId]?.compactDisplayTitle ?? "", typesString).0, font: titleFont, textColor: primaryTextColor)
case .peerJoined: case .peerJoined:
attributedString = addAttributesToStringWithRanges(strings.Notification_Joined(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) attributedString = addAttributesToStringWithRanges(strings.Notification_Joined(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
case .phoneNumberRequest:
attributedString = nil
case .unknown: case .unknown:
attributedString = nil attributedString = nil
} }

View File

@ -29,6 +29,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> [(
} else if let action = media as? TelegramMediaAction { } else if let action = media as? TelegramMediaAction {
if case .phoneCall = action.action { if case .phoneCall = action.action {
result.append((message, ChatMessageCallBubbleContentNode.self)) result.append((message, ChatMessageCallBubbleContentNode.self))
} else if case .phoneNumberRequest = action.action {
result.append((message, ChatMessagePhoneNumberRequestContentNode.self))
} else { } else {
result.append((message, ChatMessageActionBubbleContentNode.self)) result.append((message, ChatMessageActionBubbleContentNode.self))
} }

View File

@ -749,6 +749,8 @@ public class ChatMessageItemView: ListViewItemNode {
} }
case .payment: case .payment:
item.controllerInteraction.openCheckoutOrReceipt(item.message.id) item.controllerInteraction.openCheckoutOrReceipt(item.message.id)
case .urlAuth:
break
} }
} }
} }

View File

@ -0,0 +1,200 @@
import Foundation
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
private let avatarFont: UIFont = UIFont(name: ".SFCompactRounded-Semibold", size: 16.0)!
private let titleFont = Font.medium(14.0)
private let textFont = Font.regular(14.0)
class ChatMessagePhoneNumberRequestContentNode: ChatMessageBubbleContentNode {
private let dateAndStatusNode: ChatMessageDateAndStatusNode
private let textNode: TextNode
private let buttonNode: ChatMessageAttachedContentButtonNode
required init() {
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
self.textNode = TextNode()
self.buttonNode = ChatMessageAttachedContentButtonNode()
super.init()
self.addSubnode(self.textNode)
self.addSubnode(self.buttonNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didLoad() {
super.didLoad()
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
let statusLayout = self.dateAndStatusNode.asyncLayout()
let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode)
return { item, layoutConstants, _, _, constrainedSize in
let text: String
if item.message.effectivelyIncoming(item.context.account.peerId) {
text = "\(item.message.author?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) ?? "") requests your phone number"
} else {
text = "You have requested phone number"
}
let textString = NSAttributedString(string: text, font: textFont, textColor: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingPrimaryTextColor : item.presentationData.theme.theme.chat.bubble.outgoingPrimaryTextColor)
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
let maxTextWidth = max(1.0, constrainedSize.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var edited = false
var sentViaBot = false
var viewCount: Int?
for attribute in item.message.attributes {
if let _ = attribute as? EditedMessageAttribute {
edited = true
} else if let attribute = attribute as? ViewCountMessageAttribute {
viewCount = attribute.count
} else if let _ = attribute as? InlineBotMessageAttribute {
sentViaBot = true
}
}
if let author = item.message.author as? TelegramUser, author.botInfo != nil || author.flags.contains(.isSupport) {
sentViaBot = true
}
let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings)
let statusType: ChatMessageDateAndStatusType?
switch position {
case .linear(_, .None):
if item.message.effectivelyIncoming(item.context.account.peerId) {
statusType = .BubbleIncoming
} else {
if item.message.flags.contains(.Failed) {
statusType = .BubbleOutgoing(.Failed)
} else if item.message.flags.isSending && !item.message.isSentOrAcknowledged {
statusType = .BubbleOutgoing(.Sending)
} else {
statusType = .BubbleOutgoing(.Sent(read: item.read))
}
}
default:
statusType = nil
}
var statusSize = CGSize()
var statusApply: ((Bool) -> Void)?
if let statusType = statusType {
let (size, apply) = statusLayout(item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude))
statusSize = size
statusApply = apply
}
let buttonImage: UIImage
let buttonHighlightedImage: UIImage
let titleColor: UIColor
let titleHighlightedColor: UIColor
if item.message.effectivelyIncoming(item.context.account.peerId) {
buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(item.presentationData.theme.theme)!
buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(item.presentationData.theme.theme)!
titleColor = item.presentationData.theme.theme.chat.bubble.incomingAccentTextColor
let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: true, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
titleHighlightedColor = bubbleColors.fill
} else {
buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(item.presentationData.theme.theme)!
buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(item.presentationData.theme.theme)!
titleColor = item.presentationData.theme.theme.chat.bubble.outgoingAccentTextColor
let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: false, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
titleHighlightedColor = bubbleColors.fill
}
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, nil, nil, "SHARE MY PHONE NUMBER", titleColor, titleHighlightedColor)
var maxContentWidth: CGFloat = 0.0
maxContentWidth = max(maxContentWidth, statusSize.width)
maxContentWidth = max(maxContentWidth, textLayout.size.width)
maxContentWidth = max(maxContentWidth, buttonWidth)
let contentWidth = maxContentWidth + layoutConstants.text.bubbleInsets.right + 8.0
return (contentWidth, { boundingWidth in
let layoutSize: CGSize
let statusFrame: CGRect
let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0)
let buttonSpacing: CGFloat = 4.0
layoutSize = CGSize(width: contentWidth, height: layoutConstants.text.bubbleInsets.top + textLayout.size.height + 9.0 + statusSize.height + buttonSize.height + buttonSpacing)
statusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusSize.width - layoutConstants.text.bubbleInsets.right, y: layoutSize.height - statusSize.height - 9.0 - buttonSpacing - buttonSize.height), size: statusSize)
let buttonFrame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.right, y: layoutSize.height - 9.0 - buttonSize.height), size: buttonSize)
return (layoutSize, { [weak self] animation, _ in
if let strongSelf = self {
strongSelf.item = item
let _ = textApply()
let _ = buttonApply()
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.left, y: layoutConstants.text.bubbleInsets.top), size: textLayout.size)
strongSelf.buttonNode.frame = buttonFrame
if let statusApply = statusApply {
if strongSelf.dateAndStatusNode.supernode == nil {
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
}
var hasAnimation = true
if case .None = animation {
hasAnimation = false
}
statusApply(hasAnimation)
strongSelf.dateAndStatusNode.frame = statusFrame
} else if strongSelf.dateAndStatusNode.supernode != nil {
strongSelf.dateAndStatusNode.removeFromSupernode()
}
}
})
})
})
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
if self.buttonNode.frame.contains(point) {
//return .openMessage
}
return .none
}
@objc private func buttonPressed() {
if let item = self.item {
item.controllerInteraction.shareAccountContact()
}
}
}

View File

@ -104,7 +104,7 @@ public class ComposeController: ViewController {
} }
self.contactsNode.contactListNode.openPeer = { [weak self] peer in self.contactsNode.contactListNode.openPeer = { [weak self] peer in
if case let .peer(peer, _) = peer { if case let .peer(peer, _, _) = peer {
self?.openPeer(peerId: peer.id) self?.openPeer(peerId: peer.id)
} }
} }
@ -135,7 +135,7 @@ public class ComposeController: ViewController {
strongSelf.createActionDisposable.set((controller.result strongSelf.createActionDisposable.set((controller.result
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { [weak controller] peer in |> deliverOnMainQueue).start(next: { [weak controller] peer in
if let strongSelf = self, let contactPeer = peer, case let .peer(peer, _) = contactPeer { if let strongSelf = self, let contactPeer = peer, case let .peer(peer, _, _) = contactPeer {
controller?.dismissSearch() controller?.dismissSearch()
controller?.displayNavigationActivity = true controller?.displayNavigationActivity = true
strongSelf.createActionDisposable.set((createSecretChat(account: strongSelf.context.account, peerId: peer.id) |> deliverOnMainQueue).start(next: { peerId in strongSelf.createActionDisposable.set((createSecretChat(account: strongSelf.context.account, peerId: peer.id) |> deliverOnMainQueue).start(next: { peerId in

View File

@ -112,7 +112,7 @@ final class ComposeControllerNode: ASDisplayNode {
} }
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global], openPeer: { [weak self] peer in self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global], openPeer: { [weak self] peer in
if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch, case let .peer(peer, _) = peer { if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch, case let .peer(peer, _, _) = peer {
requestOpenPeerFromSearch(peer.id) requestOpenPeerFromSearch(peer.id)
} }
}), cancel: { [weak self] in }), cancel: { [weak self] in

View File

@ -121,12 +121,12 @@ enum ContactListPeerId: Hashable {
} }
enum ContactListPeer: Equatable { enum ContactListPeer: Equatable {
case peer(peer: Peer, isGlobal: Bool) case peer(peer: Peer, isGlobal: Bool, participantCount: Int32?)
case deviceContact(DeviceContactStableId, DeviceContactBasicData) case deviceContact(DeviceContactStableId, DeviceContactBasicData)
var id: ContactListPeerId { var id: ContactListPeerId {
switch self { switch self {
case let .peer(peer, _): case let .peer(peer, _, _):
return .peer(peer.id) return .peer(peer.id)
case let .deviceContact(id, _): case let .deviceContact(id, _):
return .deviceContact(id) return .deviceContact(id)
@ -135,7 +135,7 @@ enum ContactListPeer: Equatable {
var indexName: PeerIndexNameRepresentation { var indexName: PeerIndexNameRepresentation {
switch self { switch self {
case let .peer(peer, _): case let .peer(peer, _, _):
return peer.indexName return peer.indexName
case let .deviceContact(_, contact): case let .deviceContact(_, contact):
return .personName(first: contact.firstName, last: contact.lastName, addressName: "", phoneNumber: "") return .personName(first: contact.firstName, last: contact.lastName, addressName: "", phoneNumber: "")
@ -144,8 +144,8 @@ enum ContactListPeer: Equatable {
static func ==(lhs: ContactListPeer, rhs: ContactListPeer) -> Bool { static func ==(lhs: ContactListPeer, rhs: ContactListPeer) -> Bool {
switch lhs { switch lhs {
case let .peer(lhsPeer, lhsIsGlobal): case let .peer(lhsPeer, lhsIsGlobal, lhsParticipantCount):
if case let .peer(rhsPeer, rhsIsGlobal) = rhs, lhsPeer.isEqual(rhsPeer), lhsIsGlobal == rhsIsGlobal { if case let .peer(rhsPeer, rhsIsGlobal, rhsParticipantCount) = rhs, lhsPeer.isEqual(rhsPeer), lhsIsGlobal == rhsIsGlobal, lhsParticipantCount == rhsParticipantCount {
return true return true
} else { } else {
return false return false
@ -182,7 +182,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
return .option(index: index) return .option(index: index)
case let .peer(_, peer, _, _, _, _, _, _, _, _, _): case let .peer(_, peer, _, _, _, _, _, _, _, _, _):
switch peer { switch peer {
case let .peer(peer, _): case let .peer(peer, _, _):
return .peerId(peer.id.toInt64()) return .peerId(peer.id.toInt64())
case let .deviceContact(id, _): case let .deviceContact(id, _):
return .deviceContact(id) return .deviceContact(id)
@ -218,12 +218,24 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
let status: ContactsPeerItemStatus let status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer let itemPeer: ContactsPeerItemPeer
switch peer { switch peer {
case let .peer(peer, isGlobal): case let .peer(peer, isGlobal, participantCount):
if isGlobal, let _ = peer.addressName { if isGlobal, let _ = peer.addressName {
status = .addressName("") status = .addressName("")
} else { } else {
let presence = presence ?? TelegramUserPresence(status: .none, lastActivity: 0) if let _ = peer as? TelegramUser {
status = .presence(presence, dateTimeFormat) let presence = presence ?? TelegramUserPresence(status: .none, lastActivity: 0)
status = .presence(presence, dateTimeFormat)
} else if let group = peer as? TelegramGroup {
status = .custom(strings.Conversation_StatusMembers(Int32(group.participantCount)))
} else if let _ = peer as? TelegramChannel {
if let participantCount = participantCount {
status = .custom(strings.Conversation_StatusMembers(participantCount))
} else {
status = .custom(strings.Group_Status)
}
} else {
status = .none
}
} }
itemPeer = .peer(peer: peer, chatPeer: peer) itemPeer = .peer(peer: peer, chatPeer: peer)
case let .deviceContact(id, contact): case let .deviceContact(id, contact):
@ -465,7 +477,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
switch presentation { switch presentation {
case let .orderedByPresence(options): case let .orderedByPresence(options):
orderedPeers = peers.sorted(by: { lhs, rhs in orderedPeers = peers.sorted(by: { lhs, rhs in
if case let .peer(lhsPeer, _) = lhs, case let .peer(rhsPeer, _) = rhs { if case let .peer(lhsPeer, _, _) = lhs, case let .peer(rhsPeer, _, _) = rhs {
let lhsPresence = presences[lhsPeer.id] let lhsPresence = presences[lhsPeer.id]
let rhsPresence = presences[rhsPeer.id] let rhsPresence = presences[rhsPeer.id]
if let lhsPresence = lhsPresence as? TelegramUserPresence, let rhsPresence = rhsPresence as? TelegramUserPresence { if let lhsPresence = lhsPresence as? TelegramUserPresence, let rhsPresence = rhsPresence as? TelegramUserPresence {
@ -493,7 +505,7 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
let sortedPeers = peers.sorted(by: { lhs, rhs in let sortedPeers = peers.sorted(by: { lhs, rhs in
let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: sortOrder) let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: sortOrder)
if result == .orderedSame { if result == .orderedSame {
if case let .peer(lhsPeer, _) = lhs, case let .peer(rhsPeer, _) = rhs { if case let .peer(lhsPeer, _, _) = lhs, case let .peer(rhsPeer, _, _) = rhs {
return lhsPeer.id < rhsPeer.id return lhsPeer.id < rhsPeer.id
} else if case let .deviceContact(lhsId, _) = lhs, case let .deviceContact(rhsId, _) = rhs { } else if case let .deviceContact(lhsId, _) = lhs, case let .deviceContact(rhsId, _) = rhs {
return lhsId < rhsId return lhsId < rhsId
@ -596,12 +608,12 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
header = headers[orderedPeers[i].id] header = headers[orderedPeers[i].id]
} }
var presence: PeerPresence? var presence: PeerPresence?
if case let .peer(peer, _) = orderedPeers[i] { if case let .peer(peer, _, _) = orderedPeers[i] {
presence = presences[peer.id] presence = presences[peer.id]
} }
let enabled: Bool let enabled: Bool
switch orderedPeers[i] { switch orderedPeers[i] {
case let .peer(peer, _): case let .peer(peer, _, _):
enabled = !disabledPeerIds.contains(peer.id) enabled = !disabledPeerIds.contains(peer.id)
default: default:
enabled = true enabled = true
@ -687,7 +699,7 @@ public struct ContactListAdditionalOption: Equatable {
enum ContactListPresentation { enum ContactListPresentation {
case orderedByPresence(options: [ContactListAdditionalOption]) case orderedByPresence(options: [ContactListAdditionalOption])
case natural(options: [ContactListAdditionalOption]) case natural(options: [ContactListAdditionalOption])
case search(signal: Signal<String, NoError>, searchChatList: Bool, searchDeviceContacts: Bool) case search(signal: Signal<String, NoError>, searchChatList: Bool, searchDeviceContacts: Bool, searchGroups: Bool)
var sortOrder: ContactsSortOrder? { var sortOrder: ContactsSortOrder? {
switch self { switch self {
@ -929,35 +941,61 @@ final class ContactListNode: ASDisplayNode {
generateSections = true generateSections = true
} }
if case let .search(query, searchChatList, searchDeviceContacts) = presentation { if case let .search(query, searchChatList, searchDeviceContacts, searchGroups) = presentation {
return query return query
|> mapToSignal { query in |> mapToSignal { query in
let foundLocalContacts: Signal<([Peer], [PeerId : PeerPresence]), NoError> let foundLocalContacts: Signal<([FoundPeer], [PeerId: PeerPresence]), NoError>
if searchChatList { if searchChatList {
let foundChatListPeers = context.account.postbox.searchPeers(query: query.lowercased()) let foundChatListPeers = context.account.postbox.searchPeers(query: query.lowercased())
foundLocalContacts = foundChatListPeers foundLocalContacts = foundChatListPeers
|> mapToSignal { peers -> Signal<([Peer], [PeerId : PeerPresence]), NoError> in |> mapToSignal { peers -> Signal<([FoundPeer], [PeerId: PeerPresence]), NoError> in
var resultPeers: [Peer] = [] var resultPeers: [FoundPeer] = []
for peer in peers { for peer in peers {
if peer.peerId.namespace != Namespaces.Peer.CloudUser { if searchGroups {
continue let mainPeer = peer.chatMainPeer
} if let _ = mainPeer as? TelegramUser {
if let mainPeer = peer.chatMainPeer { } else if let _ = mainPeer as? TelegramGroup {
resultPeers.append(mainPeer) } else if let channel = mainPeer as? TelegramChannel {
} if case .broadcast = channel.info {
} continue
return context.account.postbox.transaction { transaction -> ([Peer], [PeerId : PeerPresence]) in }
var resultPresences: [PeerId: PeerPresence] = [:] } else {
for peer in resultPeers { continue
if let presence = transaction.getPeerPresence(peerId: peer.id) { }
resultPresences[peer.id] = presence } else {
if peer.peerId.namespace != Namespaces.Peer.CloudUser {
continue
} }
} }
return (resultPeers, resultPresences) if let mainPeer = peer.chatMainPeer {
resultPeers.append(FoundPeer(peer: mainPeer, subscribers: nil))
}
}
return context.account.postbox.transaction { transaction -> ([FoundPeer], [PeerId: PeerPresence]) in
var resultPresences: [PeerId: PeerPresence] = [:]
var mappedPeers: [FoundPeer] = []
for peer in resultPeers {
if let presence = transaction.getPeerPresence(peerId: peer.peer.id) {
resultPresences[peer.peer.id] = presence
}
if let _ = peer.peer as? TelegramChannel {
var subscribers: Int32?
if let cachedData = transaction.getPeerCachedData(peerId: peer.peer.id) as? CachedChannelData {
subscribers = cachedData.participantsSummary.memberCount
}
mappedPeers.append(FoundPeer(peer: peer.peer, subscribers: subscribers))
} else {
mappedPeers.append(peer)
}
}
return (mappedPeers, resultPresences)
} }
} }
} else { } else {
foundLocalContacts = context.account.postbox.searchContacts(query: query.lowercased()) foundLocalContacts = context.account.postbox.searchContacts(query: query.lowercased())
|> map { peers, presences -> ([FoundPeer], [PeerId: PeerPresence]) in
return (peers.map({ FoundPeer(peer: $0, subscribers: nil) }), presences)
}
} }
let foundRemoteContacts: Signal<([FoundPeer], [FoundPeer]), NoError> = .single(([], [])) let foundRemoteContacts: Signal<([FoundPeer], [FoundPeer]), NoError> = .single(([], []))
|> then( |> then(
@ -992,19 +1030,38 @@ final class ContactListNode: ASDisplayNode {
var peers: [ContactListPeer] = [] var peers: [ContactListPeer] = []
for peer in localPeersAndStatuses.0 { for peer in localPeersAndStatuses.0 {
if !existingPeerIds.contains(peer.id) { if !existingPeerIds.contains(peer.peer.id) {
existingPeerIds.insert(peer.id) existingPeerIds.insert(peer.peer.id)
peers.append(.peer(peer: peer, isGlobal: false)) peers.append(.peer(peer: peer.peer, isGlobal: false, participantCount: peer.subscribers))
if searchDeviceContacts, let user = peer as? TelegramUser, let phone = user.phone { if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
} }
} }
} }
for peer in remotePeers.0 { for peer in remotePeers.0 {
let matches: Bool
if peer.peer is TelegramUser { if peer.peer is TelegramUser {
matches = true
} else if searchGroups {
if peer.peer is TelegramGroup {
matches = true
} else if let channel = peer.peer as? TelegramChannel {
if case .group = channel.info {
matches = true
} else {
matches = false
}
} else {
matches = false
}
} else {
matches = false
}
if matches {
if !existingPeerIds.contains(peer.peer.id) { if !existingPeerIds.contains(peer.peer.id) {
existingPeerIds.insert(peer.peer.id) existingPeerIds.insert(peer.peer.id)
peers.append(.peer(peer: peer.peer, isGlobal: true)) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers))
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
} }
@ -1012,10 +1069,29 @@ final class ContactListNode: ASDisplayNode {
} }
} }
for peer in remotePeers.1 { for peer in remotePeers.1 {
let matches: Bool
if peer.peer is TelegramUser { if peer.peer is TelegramUser {
matches = true
} else if searchGroups {
if peer.peer is TelegramGroup {
matches = true
} else if let channel = peer.peer as? TelegramChannel {
if case .group = channel.info {
matches = true
} else {
matches = false
}
} else {
matches = false
}
} else {
matches = false
}
if matches {
if !existingPeerIds.contains(peer.peer.id) { if !existingPeerIds.contains(peer.peer.id) {
existingPeerIds.insert(peer.peer.id) existingPeerIds.insert(peer.peer.id)
peers.append(.peer(peer: peer.peer, isGlobal: true)) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers))
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
} }
@ -1049,7 +1125,7 @@ final class ContactListNode: ASDisplayNode {
return (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get(), contactsAuthorization.get(), contactsWarningSuppressed.get()) return (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get(), contactsAuthorization.get(), contactsWarningSuppressed.get())
|> mapToQueue { view, selectionState, themeAndStrings, authorizationStatus, warningSuppressed -> Signal<ContactsListNodeTransition, NoError> in |> mapToQueue { view, selectionState, themeAndStrings, authorizationStatus, warningSuppressed -> Signal<ContactsListNodeTransition, NoError> in
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) }) var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false, participantCount: nil) })
var existingPeerIds = Set<PeerId>() var existingPeerIds = Set<PeerId>()
var disabledPeerIds = Set<PeerId>() var disabledPeerIds = Set<PeerId>()
for filter in filters { for filter in filters {
@ -1065,7 +1141,7 @@ final class ContactListNode: ASDisplayNode {
peers = peers.filter { contact in peers = peers.filter { contact in
switch contact { switch contact {
case let .peer(peer, _): case let .peer(peer, _, _):
return !existingPeerIds.contains(peer.id) return !existingPeerIds.contains(peer.id)
default: default:
return true return true

View File

@ -7,7 +7,7 @@ import TelegramCore
enum ContactMultiselectionControllerMode { enum ContactMultiselectionControllerMode {
case groupCreation case groupCreation
case peerSelection(searchChatList: Bool) case peerSelection(searchChatList: Bool, searchGroups: Bool)
case channelCreation case channelCreation
} }
@ -162,7 +162,7 @@ class ContactMultiselectionController: ViewController {
} }
self.contactsNode.openPeer = { [weak self] peer in self.contactsNode.openPeer = { [weak self] peer in
if let strongSelf = self, case let .peer(peer, _) = peer { if let strongSelf = self, case let .peer(peer, _, _) = peer {
var updatedCount: Int? var updatedCount: Int?
var addedToken: EditableTokenListToken? var addedToken: EditableTokenListToken?
var removedTokenId: AnyHashable? var removedTokenId: AnyHashable?

View File

@ -50,8 +50,12 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
let placeholder: String let placeholder: String
switch mode { switch mode {
case .peerSelection: case let .peerSelection(_, searchGroups):
placeholder = self.presentationData.strings.Contacts_SearchLabel if searchGroups {
placeholder = self.presentationData.strings.Contacts_SearchUsersAndGroupsLabel
} else {
placeholder = self.presentationData.strings.Contacts_SearchLabel
}
default: default:
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
} }
@ -96,10 +100,12 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
return state return state
} }
var searchChatList = false var searchChatList = false
if case let .peerSelection(value) = mode { var searchGroups = false
searchChatList = value if case let .peerSelection(peerSelection) = mode {
searchChatList = peerSelection.searchChatList
searchGroups = peerSelection.searchGroups
} }
let searchResultsNode = ContactListNode(context: context, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false)), filters: filters, selectionState: selectionState) let searchResultsNode = ContactListNode(context: context, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false, searchGroups: searchGroups)), filters: filters, selectionState: selectionState)
searchResultsNode.openPeer = { peer in searchResultsNode.openPeer = { peer in
self?.tokenListNode.setText("") self?.tokenListNode.setText("")
self?.openPeer?(peer) self?.openPeer?(peer)

View File

@ -193,7 +193,7 @@ public class ContactsController: ViewController {
if let strongSelf = self { if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
switch peer { switch peer {
case let .peer(peer, _): case let .peer(peer, _, _):
if let navigationController = strongSelf.navigationController as? NavigationController { if let navigationController = strongSelf.navigationController as? NavigationController {
navigateToChatController(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in navigateToChatController(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in
if fromSearch { if fromSearch {

View File

@ -70,7 +70,7 @@ private struct ContactListSearchEntry: Identifiable, Comparable {
} }
case .global: case .global:
header = ChatListSearchItemHeader(type: .globalPeers, theme: self.theme, strings: self.strings, actionTitle: nil, action: nil) header = ChatListSearchItemHeader(type: .globalPeers, theme: self.theme, strings: self.strings, actionTitle: nil, action: nil)
if case let .peer(peer, _) = self.peer, let _ = peer.addressName { if case let .peer(peer, _, _) = self.peer, let _ = peer.addressName {
status = .addressName("") status = .addressName("")
} else { } else {
status = .none status = .none
@ -82,7 +82,7 @@ private struct ContactListSearchEntry: Identifiable, Comparable {
let peer = self.peer let peer = self.peer
let peerItem: ContactsPeerItemPeer let peerItem: ContactsPeerItemPeer
switch peer { switch peer {
case let .peer(peer, _): case let .peer(peer, _, _):
peerItem = .peer(peer: peer, chatPeer: peer) peerItem = .peer(peer: peer, chatPeer: peer)
case let .deviceContact(stableId, contact): case let .deviceContact(stableId, contact):
peerItem = .deviceContact(stableId: stableId, contact: contact) peerItem = .deviceContact(stableId: stableId, contact: contact)
@ -220,7 +220,7 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
if onlyWriteable { if onlyWriteable {
enabled = canSendMessagesToPeer(peer) enabled = canSendMessagesToPeer(peer)
} }
entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer, isGlobal: false), presence: localPeersAndPresences.1[peer.id], group: .contacts, enabled: enabled)) entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer, isGlobal: false, participantCount: nil), presence: localPeersAndPresences.1[peer.id], group: .contacts, enabled: enabled))
if searchDeviceContacts, let user = peer as? TelegramUser, let phone = user.phone { if searchDeviceContacts, let user = peer as? TelegramUser, let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
} }
@ -239,7 +239,7 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
enabled = canSendMessagesToPeer(peer.peer) enabled = canSendMessagesToPeer(peer.peer)
} }
entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true), presence: nil, group: .global, enabled: enabled)) entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled))
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
} }
@ -258,7 +258,7 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
enabled = canSendMessagesToPeer(peer.peer) enabled = canSendMessagesToPeer(peer.peer)
} }
entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true), presence: nil, group: .global, enabled: enabled)) entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled))
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
} }

View File

@ -1139,7 +1139,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie
if let peer = peer { if let peer = peer {
let dataSignal: Signal<(Peer?, DeviceContactStableId?), NoError> let dataSignal: Signal<(Peer?, DeviceContactStableId?), NoError>
switch peer { switch peer {
case let .peer(contact, _): case let .peer(contact, _, _):
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
return return
} }

View File

@ -1492,14 +1492,14 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
let contactsController: ViewController let contactsController: ViewController
if peerView.peerId.namespace == Namespaces.Peer.CloudGroup { if peerView.peerId.namespace == Namespaces.Peer.CloudGroup {
contactsController = ContactSelectionController(context: context, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in contactsController = ContactSelectionController(context: context, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in
if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer { if let confirmationImpl = confirmationImpl, case let .peer(peer, _, _) = peer {
return confirmationImpl(peer.id) return confirmationImpl(peer.id)
} else { } else {
return .single(false) return .single(false)
} }
}) })
} else { } else {
contactsController = ContactMultiselectionController(context: context, mode: .peerSelection(searchChatList: false), options: options, filters: [.excludeSelf, .disable(recentIds)]) contactsController = ContactMultiselectionController(context: context, mode: .peerSelection(searchChatList: false, searchGroups: false), options: options, filters: [.excludeSelf, .disable(recentIds)])
} }
confirmationImpl = { [weak contactsController] peerId in confirmationImpl = { [weak contactsController] peerId in
@ -1525,7 +1525,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
} }
let addMember: (ContactListPeer) -> Signal<Void, NoError> = { memberPeer -> Signal<Void, NoError> in let addMember: (ContactListPeer) -> Signal<Void, NoError> = { memberPeer -> Signal<Void, NoError> in
if case let .peer(selectedPeer, _) = memberPeer { if case let .peer(selectedPeer, _, _) = memberPeer {
let memberId = selectedPeer.id let memberId = selectedPeer.id
if peerView.peerId.namespace == Namespaces.Peer.CloudChannel { if peerView.peerId.namespace == Namespaces.Peer.CloudChannel {
return context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: peerView.peerId, memberId: memberId) return context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: peerView.peerId, memberId: memberId)

View File

@ -246,7 +246,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: true, categories: [.cloudContacts, .global], openPeer: { [weak self] peer in self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: true, categories: [.cloudContacts, .global], openPeer: { [weak self] peer in
if let strongSelf = self { if let strongSelf = self {
switch peer { switch peer {
case let .peer(peer, _): case let .peer(peer, _, _):
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Peer? in let _ = (strongSelf.context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peer.id) return transaction.getPeer(peer.id)
} |> deliverOnMainQueue).start(next: { peer in } |> deliverOnMainQueue).start(next: { peer in
@ -333,7 +333,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self?.requestActivateSearch?() self?.requestActivateSearch?()
} }
contactListNode.openPeer = { [weak self] peer in contactListNode.openPeer = { [weak self] peer in
if case let .peer(peer, _) = peer { if case let .peer(peer, _, _) = peer {
self?.requestOpenPeer?(peer.id) self?.requestOpenPeer?(peer.id)
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -12,13 +12,14 @@ private final class PrivacyAndSecurityControllerArguments {
let openVoiceCallPrivacy: () -> Void let openVoiceCallPrivacy: () -> Void
let openProfilePhotoPrivacy: () -> Void let openProfilePhotoPrivacy: () -> Void
let openForwardPrivacy: () -> Void let openForwardPrivacy: () -> Void
let openPhoneNumberPrivacy: () -> Void
let openPasscode: () -> Void let openPasscode: () -> Void
let openTwoStepVerification: () -> Void let openTwoStepVerification: () -> Void
let openActiveSessions: () -> Void let openActiveSessions: () -> Void
let setupAccountAutoremove: () -> Void let setupAccountAutoremove: () -> Void
let openDataSettings: () -> Void let openDataSettings: () -> Void
init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping () -> Void, openActiveSessions: @escaping () -> Void, setupAccountAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void) { init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping () -> Void, openActiveSessions: @escaping () -> Void, setupAccountAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void) {
self.account = account self.account = account
self.openBlockedUsers = openBlockedUsers self.openBlockedUsers = openBlockedUsers
self.openLastSeenPrivacy = openLastSeenPrivacy self.openLastSeenPrivacy = openLastSeenPrivacy
@ -26,6 +27,7 @@ private final class PrivacyAndSecurityControllerArguments {
self.openVoiceCallPrivacy = openVoiceCallPrivacy self.openVoiceCallPrivacy = openVoiceCallPrivacy
self.openProfilePhotoPrivacy = openProfilePhotoPrivacy self.openProfilePhotoPrivacy = openProfilePhotoPrivacy
self.openForwardPrivacy = openForwardPrivacy self.openForwardPrivacy = openForwardPrivacy
self.openPhoneNumberPrivacy = openPhoneNumberPrivacy
self.openPasscode = openPasscode self.openPasscode = openPasscode
self.openTwoStepVerification = openTwoStepVerification self.openTwoStepVerification = openTwoStepVerification
self.openActiveSessions = openActiveSessions self.openActiveSessions = openActiveSessions
@ -35,8 +37,8 @@ private final class PrivacyAndSecurityControllerArguments {
} }
private enum PrivacyAndSecuritySection: Int32 { private enum PrivacyAndSecuritySection: Int32 {
case general
case privacy case privacy
case security
case account case account
case dataSettings case dataSettings
} }
@ -56,14 +58,14 @@ public enum PrivacyAndSecurityEntryTag: ItemListItemTag {
private enum PrivacyAndSecurityEntry: ItemListNodeEntry { private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
case privacyHeader(PresentationTheme, String) case privacyHeader(PresentationTheme, String)
case blockedPeers(PresentationTheme, String) case blockedPeers(PresentationTheme, String)
case phoneNumberPrivacy(PresentationTheme, String, String)
case lastSeenPrivacy(PresentationTheme, String, String) case lastSeenPrivacy(PresentationTheme, String, String)
case profilePhotoPrivacy(PresentationTheme, String, String) case profilePhotoPrivacy(PresentationTheme, String, String)
case voiceCallPrivacy(PresentationTheme, String, String) case voiceCallPrivacy(PresentationTheme, String, String)
case forwardPrivacy(PresentationTheme, String, String) case forwardPrivacy(PresentationTheme, String, String)
case groupPrivacy(PresentationTheme, String, String) case groupPrivacy(PresentationTheme, String, String)
case selectivePrivacyInfo(PresentationTheme, String) case selectivePrivacyInfo(PresentationTheme, String)
case securityHeader(PresentationTheme, String) case passcode(PresentationTheme, String, Bool)
case passcode(PresentationTheme, String)
case twoStepVerification(PresentationTheme, String) case twoStepVerification(PresentationTheme, String)
case activeSessions(PresentationTheme, String) case activeSessions(PresentationTheme, String)
case accountHeader(PresentationTheme, String) case accountHeader(PresentationTheme, String)
@ -74,10 +76,10 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .privacyHeader, .blockedPeers, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .selectivePrivacyInfo, .voiceCallPrivacy: case .blockedPeers, .activeSessions, .passcode, .twoStepVerification:
return PrivacyAndSecuritySection.general.rawValue
case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .selectivePrivacyInfo, .voiceCallPrivacy:
return PrivacyAndSecuritySection.privacy.rawValue return PrivacyAndSecuritySection.privacy.rawValue
case .securityHeader, .passcode, .twoStepVerification, .activeSessions:
return PrivacyAndSecuritySection.security.rawValue
case .accountHeader, .accountTimeout, .accountInfo: case .accountHeader, .accountTimeout, .accountInfo:
return PrivacyAndSecuritySection.account.rawValue return PrivacyAndSecuritySection.account.rawValue
case .dataSettings, .dataSettingsInfo: case .dataSettings, .dataSettingsInfo:
@ -87,40 +89,40 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
var stableId: Int32 { var stableId: Int32 {
switch self { switch self {
case .privacyHeader:
return 0
case .blockedPeers: case .blockedPeers:
return 1 return 1
case .lastSeenPrivacy:
return 2
case .profilePhotoPrivacy:
return 3
case .voiceCallPrivacy:
return 4
case .forwardPrivacy:
return 5
case .groupPrivacy:
return 6
case .selectivePrivacyInfo:
return 7
case .securityHeader:
return 8
case .passcode:
return 9
case .twoStepVerification:
return 10
case .activeSessions: case .activeSessions:
return 2
case .passcode:
return 3
case .twoStepVerification:
return 4
case .privacyHeader:
return 5
case .phoneNumberPrivacy:
return 6
case .lastSeenPrivacy:
return 7
case .profilePhotoPrivacy:
return 8
case .voiceCallPrivacy:
return 9
case .forwardPrivacy:
return 10
case .groupPrivacy:
return 11 return 11
case .accountHeader: case .selectivePrivacyInfo:
return 12 return 12
case .accountTimeout: case .accountHeader:
return 13 return 13
case .accountInfo: case .accountTimeout:
return 14 return 14
case .dataSettings: case .accountInfo:
return 15 return 15
case .dataSettingsInfo: case .dataSettings:
return 16 return 16
case .dataSettingsInfo:
return 17
} }
} }
@ -138,6 +140,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .phoneNumberPrivacy(lhsTheme, lhsText, lhsValue):
if case let .phoneNumberPrivacy(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .lastSeenPrivacy(lhsTheme, lhsText, lhsValue): case let .lastSeenPrivacy(lhsTheme, lhsText, lhsValue):
if case let .lastSeenPrivacy(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .lastSeenPrivacy(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true return true
@ -174,14 +182,8 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .securityHeader(lhsTheme, lhsText): case let .passcode(lhsTheme, lhsText, lhsHasFaceId):
if case let .securityHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .passcode(rhsTheme, rhsText, rhsHasFaceId) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsHasFaceId == rhsHasFaceId {
return true
} else {
return false
}
case let .passcode(lhsTheme, lhsText):
if case let .passcode(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
} else { } else {
return false return false
@ -240,9 +242,13 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
case let .privacyHeader(theme, text): case let .privacyHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .blockedPeers(theme, text): case let .blockedPeers(theme, text):
return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, icon: UIImage(bundleImageName: "Settings/MenuIcons/Blocked")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openBlockedUsers() arguments.openBlockedUsers()
}) })
case let .phoneNumberPrivacy(theme, text, value):
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openPhoneNumberPrivacy()
})
case let .lastSeenPrivacy(theme, text, value): case let .lastSeenPrivacy(theme, text, value):
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openLastSeenPrivacy() arguments.openLastSeenPrivacy()
@ -265,18 +271,16 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openVoiceCallPrivacy() arguments.openVoiceCallPrivacy()
}) })
case let .securityHeader(theme, text): case let .passcode(theme, text, hasFaceId):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListDisclosureItem(theme: theme, icon: UIImage(bundleImageName: hasFaceId ? "Settings/MenuIcons/FaceId" : "Settings/MenuIcons/TouchId")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
case let .passcode(theme, text):
return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openPasscode() arguments.openPasscode()
}) })
case let .twoStepVerification(theme, text): case let .twoStepVerification(theme, text):
return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, icon: UIImage(bundleImageName: "Settings/MenuIcons/TwoStepAuth")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openTwoStepVerification() arguments.openTwoStepVerification()
}) })
case let .activeSessions(theme, text): case let .activeSessions(theme, text):
return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, icon: UIImage(bundleImageName: "Settings/MenuIcons/Sessions")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openActiveSessions() arguments.openActiveSessions()
}) })
case let .accountHeader(theme, text): case let .accountHeader(theme, text):
@ -301,27 +305,35 @@ private struct PrivacyAndSecurityControllerState: Equatable {
var updatingAccountTimeoutValue: Int32? = nil var updatingAccountTimeoutValue: Int32? = nil
} }
private func countForSelectivePeers(_ peers: [PeerId: SelectivePrivacyPeer]) -> Int {
var result = 0
for (_, peer) in peers {
result += peer.userCount
}
return result
}
private func stringForSelectiveSettings(strings: PresentationStrings, settings: SelectivePrivacySettings) -> String { private func stringForSelectiveSettings(strings: PresentationStrings, settings: SelectivePrivacySettings) -> String {
switch settings { switch settings {
case let .disableEveryone(enableFor): case let .disableEveryone(enableFor):
if enableFor.isEmpty { if enableFor.isEmpty {
return strings.PrivacySettings_LastSeenNobody return strings.PrivacySettings_LastSeenNobody
} else { } else {
return strings.PrivacySettings_LastSeenNobodyPlus("\(enableFor.count)").0 return strings.PrivacySettings_LastSeenNobodyPlus("\(countForSelectivePeers(enableFor))").0
} }
case let .enableEveryone(disableFor): case let .enableEveryone(disableFor):
if disableFor.isEmpty { if disableFor.isEmpty {
return strings.PrivacySettings_LastSeenEverybody return strings.PrivacySettings_LastSeenEverybody
} else { } else {
return strings.PrivacySettings_LastSeenEverybodyMinus("\(disableFor.count)").0 return strings.PrivacySettings_LastSeenEverybodyMinus("\(countForSelectivePeers(disableFor))").0
} }
case let .enableContacts(enableFor, disableFor): case let .enableContacts(enableFor, disableFor):
if !enableFor.isEmpty && !disableFor.isEmpty { if !enableFor.isEmpty && !disableFor.isEmpty {
return strings.PrivacySettings_LastSeenContactsMinusPlus("\(enableFor.count)", "\(disableFor.count)").0 return strings.PrivacySettings_LastSeenContactsMinusPlus("\(countForSelectivePeers(enableFor))", "\(countForSelectivePeers(disableFor))").0
} else if !enableFor.isEmpty { } else if !enableFor.isEmpty {
return strings.PrivacySettings_LastSeenContactsPlus("\(enableFor.count)").0 return strings.PrivacySettings_LastSeenContactsPlus("\(countForSelectivePeers(enableFor))").0
} else if !disableFor.isEmpty { } else if !disableFor.isEmpty {
return strings.PrivacySettings_LastSeenContactsMinus("\(disableFor.count)").0 return strings.PrivacySettings_LastSeenContactsMinus("\(countForSelectivePeers(disableFor))").0
} else { } else {
return strings.PrivacySettings_LastSeenContacts return strings.PrivacySettings_LastSeenContacts
} }
@ -331,9 +343,23 @@ private func stringForSelectiveSettings(strings: PresentationStrings, settings:
private func privacyAndSecurityControllerEntries(presentationData: PresentationData, state: PrivacyAndSecurityControllerState, privacySettings: AccountPrivacySettings?) -> [PrivacyAndSecurityEntry] { private func privacyAndSecurityControllerEntries(presentationData: PresentationData, state: PrivacyAndSecurityControllerState, privacySettings: AccountPrivacySettings?) -> [PrivacyAndSecurityEntry] {
var entries: [PrivacyAndSecurityEntry] = [] var entries: [PrivacyAndSecurityEntry] = []
entries.append(.privacyHeader(presentationData.theme, presentationData.strings.PrivacySettings_PrivacyTitle))
entries.append(.blockedPeers(presentationData.theme, presentationData.strings.Settings_BlockedUsers)) entries.append(.blockedPeers(presentationData.theme, presentationData.strings.Settings_BlockedUsers))
entries.append(.activeSessions(presentationData.theme, presentationData.strings.PrivacySettings_AuthSessions))
if let biometricAuthentication = LocalAuth.biometricAuthentication {
switch biometricAuthentication {
case .touchId:
entries.append(.passcode(presentationData.theme, presentationData.strings.PrivacySettings_PasscodeAndTouchId, true))
case .faceId:
entries.append(.passcode(presentationData.theme, presentationData.strings.PrivacySettings_PasscodeAndFaceId, false))
}
} else {
entries.append(.passcode(presentationData.theme, presentationData.strings.PrivacySettings_Passcode, false))
}
entries.append(.twoStepVerification(presentationData.theme, presentationData.strings.PrivacySettings_TwoStepAuth))
entries.append(.privacyHeader(presentationData.theme, presentationData.strings.PrivacySettings_PrivacyTitle))
if let privacySettings = privacySettings { if let privacySettings = privacySettings {
entries.append(.phoneNumberPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_PhoneNumber, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.phoneNumber)))
entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.presence))) entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.presence)))
entries.append(.profilePhotoPrivacy(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.profilePhoto))) entries.append(.profilePhotoPrivacy(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.profilePhoto)))
entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceCalls))) entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceCalls)))
@ -350,19 +376,6 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp)) entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))
} }
entries.append(.securityHeader(presentationData.theme, presentationData.strings.PrivacySettings_SecurityTitle))
if let biometricAuthentication = LocalAuth.biometricAuthentication {
switch biometricAuthentication {
case .touchId:
entries.append(.passcode(presentationData.theme, presentationData.strings.PrivacySettings_PasscodeAndTouchId))
case .faceId:
entries.append(.passcode(presentationData.theme, presentationData.strings.PrivacySettings_PasscodeAndFaceId))
}
} else {
entries.append(.passcode(presentationData.theme, presentationData.strings.PrivacySettings_Passcode))
}
entries.append(.twoStepVerification(presentationData.theme, presentationData.strings.PrivacySettings_TwoStepAuth))
entries.append(.activeSessions(presentationData.theme, presentationData.strings.PrivacySettings_AuthSessions))
entries.append(.accountHeader(presentationData.theme, presentationData.strings.PrivacySettings_DeleteAccountTitle.uppercased())) entries.append(.accountHeader(presentationData.theme, presentationData.strings.PrivacySettings_DeleteAccountTitle.uppercased()))
if let privacySettings = privacySettings { if let privacySettings = privacySettings {
let value: Int32 let value: Int32
@ -422,7 +435,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, accountRemovalTimeout: value.accountRemovalTimeout)))
} }
return .complete() return .complete()
} }
@ -445,7 +458,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, accountRemovalTimeout: value.accountRemovalTimeout)))
} }
return .complete() return .complete()
} }
@ -482,7 +495,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, voiceCallsP2P: updatedCallsPrivacy, profilePhoto: value.profilePhoto, forwards: value.forwards, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, voiceCallsP2P: updatedCallsPrivacy, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, accountRemovalTimeout: value.accountRemovalTimeout)))
} }
return .complete() return .complete()
} }
@ -505,7 +518,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: updated, forwards: value.forwards, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: updated, forwards: value.forwards, phoneNumber: value.phoneNumber, accountRemovalTimeout: value.accountRemovalTimeout)))
} }
return .complete() return .complete()
} }
@ -528,7 +541,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: updated, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: updated, phoneNumber: value.phoneNumber, accountRemovalTimeout: value.accountRemovalTimeout)))
} }
return .complete() return .complete()
} }
@ -537,6 +550,29 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
})) }))
} }
})) }))
}, openPhoneNumberPrivacy: {
let signal = privacySettingsPromise.get()
|> take(1)
|> deliverOnMainQueue
currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in
if let info = info {
pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .phoneNumber, current: info.phoneNumber, updated: { updated, _ in
if let currentInfoDisposable = currentInfoDisposable {
let applySetting: Signal<Void, NoError> = privacySettingsPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in
if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: updated, accountRemovalTimeout: value.accountRemovalTimeout)))
}
return .complete()
}
currentInfoDisposable.set(applySetting.start())
}
}))
}
}))
}, openPasscode: { }, openPasscode: {
let _ = passcodeOptionsAccessController(context: context, pushController: { controller in let _ = passcodeOptionsAccessController(context: context, pushController: { controller in
replaceTopControllerImpl?(controller) replaceTopControllerImpl?(controller)
@ -570,24 +606,24 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
return state return state
} }
let applyTimeout: Signal<Void, NoError> = privacySettingsPromise.get() let applyTimeout: Signal<Void, NoError> = privacySettingsPromise.get()
|> filter { $0 != nil } |> filter { $0 != nil }
|> take(1) |> take(1)
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, accountRemovalTimeout: timeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, accountRemovalTimeout: timeout)))
}
return .complete()
} }
updateAccountTimeoutDisposable.set((updateAccountRemovalTimeout(account: context.account, timeout: timeout) return .complete()
|> then(applyTimeout) }
|> deliverOnMainQueue).start(completed: { updateAccountTimeoutDisposable.set((updateAccountRemovalTimeout(account: context.account, timeout: timeout)
updateState { state in |> then(applyTimeout)
var state = state |> deliverOnMainQueue).start(completed: {
state.updatingAccountTimeoutValue = nil updateState { state in
return state var state = state
} state.updatingAccountTimeoutValue = nil
})) return state
}
}))
} }
} }
let timeoutValues: [Int32] = [ let timeoutValues: [Int32] = [

View File

@ -10,6 +10,7 @@ enum SelectivePrivacySettingsKind {
case voiceCalls case voiceCalls
case profilePhoto case profilePhoto
case forwards case forwards
case phoneNumber
} }
private enum SelectivePrivacySettingType { private enum SelectivePrivacySettingType {
@ -64,11 +65,15 @@ private enum SelectivePrivacySettingsSection: Int32 {
case callsIntegrationEnabled case callsIntegrationEnabled
} }
private func stringForUserCount(_ count: Int, strings: PresentationStrings) -> String { private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings: PresentationStrings) -> String {
if count == 0 { if peers.isEmpty {
return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder
} else { } else {
return strings.UserCount(Int32(count)) var result = 0
for (_, peer) in peers {
result += peer.userCount
}
return strings.UserCount(Int32(result))
} }
} }
@ -80,6 +85,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
case contacts(PresentationTheme, String, Bool) case contacts(PresentationTheme, String, Bool)
case nobody(PresentationTheme, String, Bool) case nobody(PresentationTheme, String, Bool)
case settingInfo(PresentationTheme, String) case settingInfo(PresentationTheme, String)
case exceptionsHeader(PresentationTheme, String)
case disableFor(PresentationTheme, String, String) case disableFor(PresentationTheme, String, String)
case enableFor(PresentationTheme, String, String) case enableFor(PresentationTheme, String, String)
case peersInfo(PresentationTheme, String) case peersInfo(PresentationTheme, String)
@ -100,7 +106,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return SelectivePrivacySettingsSection.forwards.rawValue return SelectivePrivacySettingsSection.forwards.rawValue
case .settingHeader, .everybody, .contacts, .nobody, .settingInfo: case .settingHeader, .everybody, .contacts, .nobody, .settingInfo:
return SelectivePrivacySettingsSection.setting.rawValue return SelectivePrivacySettingsSection.setting.rawValue
case .disableFor, .enableFor, .peersInfo: case .exceptionsHeader, .disableFor, .enableFor, .peersInfo:
return SelectivePrivacySettingsSection.peers.rawValue return SelectivePrivacySettingsSection.peers.rawValue
case .callsP2PHeader, .callsP2PAlways, .callsP2PContacts, .callsP2PNever, .callsP2PInfo: case .callsP2PHeader, .callsP2PAlways, .callsP2PContacts, .callsP2PNever, .callsP2PInfo:
return SelectivePrivacySettingsSection.callsP2P.rawValue return SelectivePrivacySettingsSection.callsP2P.rawValue
@ -127,32 +133,34 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return 5 return 5
case .settingInfo: case .settingInfo:
return 6 return 6
case .disableFor: case .exceptionsHeader:
return 7 return 7
case .enableFor: case .disableFor:
return 8 return 8
case .peersInfo: case .enableFor:
return 9 return 9
case .callsP2PHeader: case .peersInfo:
return 10 return 10
case .callsP2PAlways: case .callsP2PHeader:
return 11 return 11
case .callsP2PContacts: case .callsP2PAlways:
return 12 return 12
case .callsP2PNever: case .callsP2PContacts:
return 13 return 13
case .callsP2PInfo: case .callsP2PNever:
return 14 return 14
case .callsP2PDisableFor: case .callsP2PInfo:
return 15 return 15
case .callsP2PEnableFor: case .callsP2PDisableFor:
return 16 return 16
case .callsP2PPeersInfo: case .callsP2PEnableFor:
return 17 return 17
case .callsIntegrationEnabled: case .callsP2PPeersInfo:
return 18 return 18
case .callsIntegrationInfo: case .callsIntegrationEnabled:
return 19 return 19
case .callsIntegrationInfo:
return 20
} }
} }
@ -194,6 +202,12 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .exceptionsHeader(lhsTheme, lhsText):
if case let .exceptionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .settingInfo(lhsTheme, lhsText): case let .settingInfo(lhsTheme, lhsText):
if case let .settingInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .settingInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
@ -307,6 +321,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
}) })
case let .settingInfo(theme, text): case let .settingInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .exceptionsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .disableFor(theme, title, value): case let .disableFor(theme, title, value):
return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openDisableFor(.main) arguments.openDisableFor(.main)
@ -355,19 +371,19 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
private struct SelectivePrivacySettingsControllerState: Equatable { private struct SelectivePrivacySettingsControllerState: Equatable {
let setting: SelectivePrivacySettingType let setting: SelectivePrivacySettingType
let enableFor: Set<PeerId> let enableFor: [PeerId: SelectivePrivacyPeer]
let disableFor: Set<PeerId> let disableFor: [PeerId: SelectivePrivacyPeer]
let saving: Bool let saving: Bool
let callDataSaving: VoiceCallDataSaving? let callDataSaving: VoiceCallDataSaving?
let callP2PMode: SelectivePrivacySettingType? let callP2PMode: SelectivePrivacySettingType?
let callP2PEnableFor: Set<PeerId>? let callP2PEnableFor: [PeerId: SelectivePrivacyPeer]?
let callP2PDisableFor: Set<PeerId>? let callP2PDisableFor: [PeerId: SelectivePrivacyPeer]?
let callIntegrationAvailable: Bool? let callIntegrationAvailable: Bool?
let callIntegrationEnabled: Bool? let callIntegrationEnabled: Bool?
init(setting: SelectivePrivacySettingType, enableFor: Set<PeerId>, disableFor: Set<PeerId>, saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: Set<PeerId>?, callP2PDisableFor: Set<PeerId>?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?) { init(setting: SelectivePrivacySettingType, enableFor: [PeerId: SelectivePrivacyPeer], disableFor: [PeerId: SelectivePrivacyPeer], saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: [PeerId: SelectivePrivacyPeer]?, callP2PDisableFor: [PeerId: SelectivePrivacyPeer]?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?) {
self.setting = setting self.setting = setting
self.enableFor = enableFor self.enableFor = enableFor
self.disableFor = disableFor self.disableFor = disableFor
@ -419,11 +435,11 @@ private struct SelectivePrivacySettingsControllerState: Equatable {
return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
func withUpdatedEnableFor(_ enableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState { func withUpdatedEnableFor(_ enableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
func withUpdatedDisableFor(_ disableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState { func withUpdatedDisableFor(_ disableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
@ -435,11 +451,11 @@ private struct SelectivePrivacySettingsControllerState: Equatable {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: mode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: mode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
func withUpdatedCallP2PEnableFor(_ enableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState { func withUpdatedCallP2PEnableFor(_ enableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: enableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: enableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
func withUpdatedCallP2PDisableFor(_ disableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState { func withUpdatedCallP2PDisableFor(_ disableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: disableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: disableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
@ -481,6 +497,11 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
settingInfoText = presentationData.strings.Privacy_Forwards_CustomHelp settingInfoText = presentationData.strings.Privacy_Forwards_CustomHelp
disableForText = presentationData.strings.Privacy_GroupsAndChannels_NeverAllow disableForText = presentationData.strings.Privacy_GroupsAndChannels_NeverAllow
enableForText = presentationData.strings.Privacy_GroupsAndChannels_AlwaysAllow enableForText = presentationData.strings.Privacy_GroupsAndChannels_AlwaysAllow
case .phoneNumber:
settingTitle = presentationData.strings.PrivacyPhoneNumberSettings_WhoCanSeeMyPhoneNumber
settingInfoText = presentationData.strings.PrivacyLastSeenSettings_CustomHelp
disableForText = presentationData.strings.PrivacyLastSeenSettings_NeverShareWith
enableForText = presentationData.strings.PrivacyLastSeenSettings_AlwaysShareWith
} }
if case .forwards = kind { if case .forwards = kind {
@ -506,21 +527,23 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody)) entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody))
entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts)) entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts))
switch kind { switch kind {
case .presence, .voiceCalls, .forwards: case .presence, .voiceCalls, .forwards, .phoneNumber:
entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody)) entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody))
case .groupInvitations, .profilePhoto: case .groupInvitations, .profilePhoto:
break break
} }
entries.append(.settingInfo(presentationData.theme, settingInfoText)) entries.append(.settingInfo(presentationData.theme, settingInfoText))
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions))
switch state.setting { switch state.setting {
case .everybody: case .everybody:
entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor.count, strings: presentationData.strings))) entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, strings: presentationData.strings)))
case .contacts: case .contacts:
entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor.count, strings: presentationData.strings))) entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, strings: presentationData.strings)))
entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor.count, strings: presentationData.strings))) entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings)))
case .nobody: case .nobody:
entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor.count, strings: presentationData.strings))) entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings)))
} }
entries.append(.peersInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp)) entries.append(.peersInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp))
@ -535,12 +558,12 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
if let callP2PMode = state.callP2PMode, let disableFor = state.callP2PDisableFor, let enableFor = state.callP2PEnableFor { if let callP2PMode = state.callP2PMode, let disableFor = state.callP2PDisableFor, let enableFor = state.callP2PEnableFor {
switch callP2PMode { switch callP2PMode {
case .everybody: case .everybody:
entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor.count, strings: presentationData.strings))) entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor, strings: presentationData.strings)))
case .contacts: case .contacts:
entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor.count, strings: presentationData.strings))) entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor, strings: presentationData.strings)))
entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor.count, strings: presentationData.strings))) entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor, strings: presentationData.strings)))
case .nobody: case .nobody:
entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor.count, strings: presentationData.strings))) entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor, strings: presentationData.strings)))
} }
} }
entries.append(.callsP2PPeersInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp)) entries.append(.callsP2PPeersInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp))
@ -557,8 +580,8 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
func selectivePrivacySettingsController(context: AccountContext, kind: SelectivePrivacySettingsKind, current: SelectivePrivacySettings, callSettings: (SelectivePrivacySettings, VoiceCallSettings)? = nil, voipConfiguration: VoipConfiguration? = nil, callIntegrationAvailable: Bool? = nil, updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?) -> Void) -> ViewController { func selectivePrivacySettingsController(context: AccountContext, kind: SelectivePrivacySettingsKind, current: SelectivePrivacySettings, callSettings: (SelectivePrivacySettings, VoiceCallSettings)? = nil, voipConfiguration: VoipConfiguration? = nil, callIntegrationAvailable: Bool? = nil, updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?) -> Void) -> ViewController {
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
var initialEnableFor = Set<PeerId>() var initialEnableFor: [PeerId: SelectivePrivacyPeer] = [:]
var initialDisableFor = Set<PeerId>() var initialDisableFor: [PeerId: SelectivePrivacyPeer] = [:]
switch current { switch current {
case let .disableEveryone(enableFor): case let .disableEveryone(enableFor):
initialEnableFor = enableFor initialEnableFor = enableFor
@ -568,18 +591,18 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
case let .enableEveryone(disableFor): case let .enableEveryone(disableFor):
initialDisableFor = disableFor initialDisableFor = disableFor
} }
var initialCallP2PEnableFor: Set<PeerId>? var initialCallP2PEnableFor: [PeerId: SelectivePrivacyPeer]?
var initialCallP2PDisableFor: Set<PeerId>? var initialCallP2PDisableFor: [PeerId: SelectivePrivacyPeer]?
if let callCurrent = callSettings?.0 { if let callCurrent = callSettings?.0 {
switch callCurrent { switch callCurrent {
case let .disableEveryone(enableFor): case let .disableEveryone(enableFor):
initialCallP2PEnableFor = enableFor initialCallP2PEnableFor = enableFor
initialCallP2PDisableFor = Set<PeerId>() initialCallP2PDisableFor = [:]
case let .enableContacts(enableFor, disableFor): case let .enableContacts(enableFor, disableFor):
initialCallP2PEnableFor = enableFor initialCallP2PEnableFor = enableFor
initialCallP2PDisableFor = disableFor initialCallP2PDisableFor = disableFor
case let .enableEveryone(disableFor): case let .enableEveryone(disableFor):
initialCallP2PEnableFor = Set<PeerId>() initialCallP2PEnableFor = [:]
initialCallP2PDisableFor = disableFor initialCallP2PDisableFor = disableFor
} }
} }
@ -616,27 +639,37 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
title = strings.Privacy_ProfilePhoto_AlwaysShareWith_Title title = strings.Privacy_ProfilePhoto_AlwaysShareWith_Title
case .forwards: case .forwards:
title = strings.Privacy_Forwards_AlwaysAllow_Title title = strings.Privacy_Forwards_AlwaysAllow_Title
case .phoneNumber:
title = strings.PrivacyLastSeenSettings_AlwaysShareWith_Title
} }
var peerIds = Set<PeerId>() var peerIds: [PeerId: SelectivePrivacyPeer] = [:]
updateState { state in updateState { state in
switch target { switch target {
case .main: case .main:
peerIds = state.enableFor peerIds = state.enableFor
case .callP2P: case .callP2P:
if let callP2PEnableFor = state.callP2PEnableFor { if let callP2PEnableFor = state.callP2PEnableFor {
peerIds = callP2PEnableFor peerIds = callP2PEnableFor
} }
} }
return state return state
} }
pushControllerImpl?(selectivePrivacyPeersController(context: context, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds in pushControllerImpl?(selectivePrivacyPeersController(context: context, title: title, initialPeers: peerIds, updated: { updatedPeerIds in
updateState { state in updateState { state in
switch target { switch target {
case .main: case .main:
return state.withUpdatedEnableFor(Set(updatedPeerIds)).withUpdatedDisableFor(state.disableFor.subtracting(Set(updatedPeerIds))) var disableFor = state.disableFor
for (key, _) in updatedPeerIds {
disableFor.removeValue(forKey: key)
}
return state.withUpdatedEnableFor(updatedPeerIds).withUpdatedDisableFor(disableFor)
case .callP2P: case .callP2P:
let callP2PDisableFor = state.callP2PDisableFor ?? Set() var callP2PDisableFor = state.callP2PDisableFor ?? [:]
return state.withUpdatedCallP2PEnableFor(Set(updatedPeerIds)).withUpdatedCallP2PDisableFor(callP2PDisableFor.subtracting(Set(updatedPeerIds))) var disableFor = state.disableFor
for (key, _) in updatedPeerIds {
callP2PDisableFor.removeValue(forKey: key)
}
return state.withUpdatedCallP2PEnableFor(updatedPeerIds).withUpdatedCallP2PDisableFor(callP2PDisableFor)
} }
} }
})) }))
@ -653,8 +686,10 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
title = strings.Privacy_ProfilePhoto_NeverShareWith_Title title = strings.Privacy_ProfilePhoto_NeverShareWith_Title
case .forwards: case .forwards:
title = strings.Privacy_Forwards_NeverAllow_Title title = strings.Privacy_Forwards_NeverAllow_Title
case .phoneNumber:
title = strings.PrivacyLastSeenSettings_NeverShareWith_Title
} }
var peerIds = Set<PeerId>() var peerIds: [PeerId: SelectivePrivacyPeer] = [:]
updateState { state in updateState { state in
switch target { switch target {
case .main: case .main:
@ -666,14 +701,21 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
} }
return state return state
} }
pushControllerImpl?(selectivePrivacyPeersController(context: context, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds in pushControllerImpl?(selectivePrivacyPeersController(context: context, title: title, initialPeers: peerIds, updated: { updatedPeerIds in
updateState { state in updateState { state in
switch target { switch target {
case .main: case .main:
return state.withUpdatedDisableFor(Set(updatedPeerIds)).withUpdatedEnableFor(state.enableFor.subtracting(Set(updatedPeerIds))) var enableFor = state.enableFor
for (key, _) in updatedPeerIds {
enableFor.removeValue(forKey: key)
}
return state.withUpdatedDisableFor(updatedPeerIds).withUpdatedEnableFor(enableFor)
case .callP2P: case .callP2P:
let callP2PEnableFor = state.callP2PEnableFor ?? Set() var callP2PEnableFor = state.callP2PEnableFor ?? [:]
return state.withUpdatedCallP2PDisableFor(Set(updatedPeerIds)).withUpdatedCallP2PEnableFor(callP2PEnableFor.subtracting(Set(updatedPeerIds))) for (key, _) in updatedPeerIds {
callP2PEnableFor.removeValue(forKey: key)
}
return state.withUpdatedCallP2PDisableFor(updatedPeerIds).withUpdatedCallP2PEnableFor(callP2PEnableFor)
} }
} }
})) }))
@ -711,6 +753,8 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
title = presentationData.strings.Privacy_ProfilePhoto title = presentationData.strings.Privacy_ProfilePhoto
case .forwards: case .forwards:
title = presentationData.strings.Privacy_Forwards title = presentationData.strings.Privacy_Forwards
case .phoneNumber:
title = presentationData.strings.Privacy_PhoneNumber
} }
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(entries: selectivePrivacySettingsControllerEntries(presentationData: presentationData, kind: kind, state: state, peerName: peerName), style: .blocks, animateChanges: false) let listState = ItemListNodeState(entries: selectivePrivacySettingsControllerEntries(presentationData: presentationData, kind: kind, state: state, peerName: peerName), style: .blocks, animateChanges: false)
@ -767,6 +811,8 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
type = .profilePhoto type = .profilePhoto
case .forwards: case .forwards:
type = .forwards type = .forwards
case .phoneNumber:
type = .phoneNumber
} }
let updateSettingsSignal = updateSelectiveAccountPrivacySettings(account: context.account, type: type, settings: settings) let updateSettingsSignal = updateSelectiveAccountPrivacySettings(account: context.account, type: type, settings: settings)

View File

@ -56,7 +56,7 @@ private enum SelectivePrivacyPeersEntryStableId: Hashable {
} }
private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { private enum SelectivePrivacyPeersEntry: ItemListNodeEntry {
case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, SelectivePrivacyPeer, ItemListPeerItemEditing, Bool)
case addItem(PresentationTheme, String, Bool) case addItem(PresentationTheme, String, Bool)
var section: ItemListSectionId { var section: ItemListSectionId {
@ -71,7 +71,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry {
var stableId: SelectivePrivacyPeersEntryStableId { var stableId: SelectivePrivacyPeersEntryStableId {
switch self { switch self {
case let .peerItem(_, _, _, _, _, peer, _, _): case let .peerItem(_, _, _, _, _, peer, _, _):
return .peer(peer.id) return .peer(peer.peer.id)
case .addItem: case .addItem:
return .add return .add
} }
@ -84,7 +84,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry {
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
if !lhsPeer.isEqual(rhsPeer) { if lhsPeer != rhsPeer {
return false return false
} }
if lhsTheme !== rhsTheme { if lhsTheme !== rhsTheme {
@ -135,13 +135,28 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry {
func item(_ arguments: SelectivePrivacyPeersControllerArguments) -> ListViewItem { func item(_ arguments: SelectivePrivacyPeersControllerArguments) -> ListViewItem {
switch self { switch self {
case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in var text: ItemListPeerItemText = .none
if let group = peer.peer as? TelegramGroup {
text = .text(strings.Conversation_StatusMembers(Int32(group.participantCount)))
} else if let channel = peer.peer as? TelegramChannel {
if let participantCount = peer.participantCount {
text = .text(strings.Conversation_StatusMembers(Int32(participantCount)))
} else {
switch channel.info {
case .group:
text = .text(strings.Group_Status)
case .broadcast:
text = .text(strings.Channel_Status)
}
}
}
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, account: arguments.account, peer: peer.peer, presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in
arguments.setPeerIdWithRevealedOptions(previousId, id) arguments.setPeerIdWithRevealedOptions(previousId, id)
}, removePeer: { peerId in }, removePeer: { peerId in
arguments.removePeer(peerId) arguments.removePeer(peerId)
}) })
case let .addItem(theme, text, editing): case let .addItem(theme, text, editing):
return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: editing, action: { return ItemListPeerActionItem(theme: theme, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: editing, action: {
arguments.addPeer() arguments.addPeer()
}) })
} }
@ -181,21 +196,21 @@ private struct SelectivePrivacyPeersControllerState: Equatable {
} }
} }
private func selectivePrivacyPeersControllerEntries(presentationData: PresentationData, state: SelectivePrivacyPeersControllerState, peers: [Peer]) -> [SelectivePrivacyPeersEntry] { private func selectivePrivacyPeersControllerEntries(presentationData: PresentationData, state: SelectivePrivacyPeersControllerState, peers: [SelectivePrivacyPeer]) -> [SelectivePrivacyPeersEntry] {
var entries: [SelectivePrivacyPeersEntry] = [] var entries: [SelectivePrivacyPeersEntry] = []
var index: Int32 = 0 var index: Int32 = 0
for peer in peers { for peer in peers {
entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.id == state.peerIdWithRevealedOptions), true)) entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.peer.id == state.peerIdWithRevealedOptions), true))
index += 1 index += 1
} }
entries.append(.addItem(presentationData.theme, presentationData.strings.BlockedUsers_AddNew, state.editing)) entries.append(.addItem(presentationData.theme, presentationData.strings.Privacy_AddNewPeer, state.editing))
return entries return entries
} }
public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeerIds: [PeerId], updated: @escaping ([PeerId]) -> Void) -> ViewController { public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeers: [PeerId: SelectivePrivacyPeer], updated: @escaping ([PeerId: SelectivePrivacyPeer]) -> Void) -> ViewController {
let statePromise = ValuePromise(SelectivePrivacyPeersControllerState(), ignoreRepeated: true) let statePromise = ValuePromise(SelectivePrivacyPeersControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: SelectivePrivacyPeersControllerState()) let stateValue = Atomic(value: SelectivePrivacyPeersControllerState())
let updateState: ((SelectivePrivacyPeersControllerState) -> SelectivePrivacyPeersControllerState) -> Void = { f in let updateState: ((SelectivePrivacyPeersControllerState) -> SelectivePrivacyPeersControllerState) -> Void = { f in
@ -212,15 +227,9 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
let removePeerDisposable = MetaDisposable() let removePeerDisposable = MetaDisposable()
actionsDisposable.add(removePeerDisposable) actionsDisposable.add(removePeerDisposable)
let peersPromise = Promise<[Peer]>() let peersPromise = Promise<[SelectivePrivacyPeer]>()
peersPromise.set(context.account.postbox.transaction { transaction -> [Peer] in peersPromise.set(context.account.postbox.transaction { transaction -> [SelectivePrivacyPeer] in
var result: [Peer] = [] return Array(initialPeers.values)
for peerId in initialPeerIds {
if let peer = transaction.getPeer(peerId) {
result.append(peer)
}
}
return result
}) })
let arguments = SelectivePrivacyPeersControllerArguments(account: context.account, setPeerIdWithRevealedOptions: { peerId, fromPeerId in let arguments = SelectivePrivacyPeersControllerArguments(account: context.account, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
@ -233,39 +242,53 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
} }
}, removePeer: { memberId in }, removePeer: { memberId in
let applyPeers: Signal<Void, NoError> = peersPromise.get() let applyPeers: Signal<Void, NoError> = peersPromise.get()
|> take(1) |> take(1)
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { peers -> Signal<Void, NoError> in |> mapToSignal { peers -> Signal<Void, NoError> in
var updatedPeers = peers var updatedPeers = peers
for i in 0 ..< updatedPeers.count { for i in 0 ..< updatedPeers.count {
if updatedPeers[i].id == memberId { if updatedPeers[i].peer.id == memberId {
updatedPeers.remove(at: i) updatedPeers.remove(at: i)
break break
}
} }
peersPromise.set(.single(updatedPeers)) }
updated(updatedPeers.map { $0.id }) peersPromise.set(.single(updatedPeers))
return .complete() var updatedPeerDict: [PeerId: SelectivePrivacyPeer] = [:]
for peer in updatedPeers {
updatedPeerDict[peer.peer.id] = peer
}
updated(updatedPeerDict)
return .complete()
} }
removePeerDisposable.set(applyPeers.start()) removePeerDisposable.set(applyPeers.start())
}, addPeer: { }, addPeer: {
let controller = ContactMultiselectionController(context: context, mode: .peerSelection(searchChatList: true), options: []) let controller = ContactMultiselectionController(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true), options: [])
addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] peerIds in addPeerDisposable.set((controller.result
|> take(1)
|> deliverOnMainQueue).start(next: { [weak controller] peerIds in
let applyPeers: Signal<Void, NoError> = peersPromise.get() let applyPeers: Signal<Void, NoError> = peersPromise.get()
|> take(1) |> take(1)
|> mapToSignal { peers -> Signal<[Peer], NoError> in |> mapToSignal { peers -> Signal<[SelectivePrivacyPeer], NoError> in
return context.account.postbox.transaction { transaction -> [Peer] in return context.account.postbox.transaction { transaction -> [SelectivePrivacyPeer] in
var updatedPeers = peers var updatedPeers = peers
var existingIds = Set(updatedPeers.map { $0.id }) var existingIds = Set(updatedPeers.map { $0.peer.id })
for peerId in peerIds { for peerId in peerIds {
guard case let .peer(peerId) = peerId else { guard case let .peer(peerId) = peerId else {
continue continue
} }
if let peer = transaction.getPeer(peerId), !existingIds.contains(peerId) { if let peer = transaction.getPeer(peerId), !existingIds.contains(peerId) {
existingIds.insert(peerId) existingIds.insert(peerId)
updatedPeers.append(peer) var participantCount: Int32?
if let channel = peer as? TelegramChannel, case .group = channel.info {
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData {
participantCount = cachedData.participantsSummary.memberCount
}
}
updatedPeers.append(SelectivePrivacyPeer(peer: peer, participantCount: participantCount))
} }
} }
return updatedPeers return updatedPeers
@ -274,7 +297,13 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { updatedPeers -> Signal<Void, NoError> in |> mapToSignal { updatedPeers -> Signal<Void, NoError> in
peersPromise.set(.single(updatedPeers)) peersPromise.set(.single(updatedPeers))
updated(updatedPeers.map { $0.id })
var updatedPeerDict: [PeerId: SelectivePrivacyPeer] = [:]
for peer in updatedPeers {
updatedPeerDict[peer.peer.id] = peer
}
updated(updatedPeerDict)
return .complete() return .complete()
} }
@ -284,37 +313,38 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}) })
var previousPeers: [Peer]? var previousPeers: [SelectivePrivacyPeer]?
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peersPromise.get()) let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peersPromise.get())
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, peers -> (ItemListControllerState, (ItemListNodeState<SelectivePrivacyPeersEntry>, SelectivePrivacyPeersEntry.ItemGenerationArguments)) in |> map { presentationData, state, peers -> (ItemListControllerState, (ItemListNodeState<SelectivePrivacyPeersEntry>, SelectivePrivacyPeersEntry.ItemGenerationArguments)) in
var rightNavigationButton: ItemListNavigationButton? var rightNavigationButton: ItemListNavigationButton?
if !peers.isEmpty { if !peers.isEmpty {
if state.editing { if state.editing {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
updateState { state in updateState { state in
return state.withUpdatedEditing(false) return state.withUpdatedEditing(false)
} }
}) })
} else { } else {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
updateState { state in updateState { state in
return state.withUpdatedEditing(true) return state.withUpdatedEditing(true)
} }
}) })
}
} }
}
let previous = previousPeers
previousPeers = peers let previous = previousPeers
previousPeers = peers
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: selectivePrivacyPeersControllerEntries(presentationData: presentationData, state: state, peers: peers), style: .blocks, emptyStateItem: nil, animateChanges: previous != nil && previous!.count >= peers.count) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(entries: selectivePrivacyPeersControllerEntries(presentationData: presentationData, state: state, peers: peers), style: .blocks, emptyStateItem: nil, animateChanges: previous != nil && previous!.count >= peers.count)
return (controllerState, (listState, arguments))
} |> afterDisposed { return (controllerState, (listState, arguments))
actionsDisposable.dispose() }
|> afterDisposed {
actionsDisposable.dispose()
} }
let controller = ItemListController(context: context, state: signal) let controller = ItemListController(context: context, state: signal)

View File

@ -451,6 +451,8 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac
current = info.profilePhoto current = info.profilePhoto
case .forwards: case .forwards:
current = info.forwards current = info.forwards
case .phoneNumber:
current = info.phoneNumber
} }
present(.push, selectivePrivacySettingsController(context: context, kind: kind, current: current, callSettings: callSettings != nil ? (info.voiceCallsP2P, callSettings!.0) : nil, voipConfiguration: callSettings?.1, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings in present(.push, selectivePrivacySettingsController(context: context, kind: kind, current: current, callSettings: callSettings != nil ? (info.voiceCallsP2P, callSettings!.0) : nil, voipConfiguration: callSettings?.1, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings in

View File

@ -24,6 +24,7 @@ private final class UserInfoControllerArguments {
let displayCopyContextMenu: (UserInfoEntryTag, String) -> Void let displayCopyContextMenu: (UserInfoEntryTag, String) -> Void
let call: () -> Void let call: () -> Void
let openCallMenu: (String) -> Void let openCallMenu: (String) -> Void
let requestPhoneNumber: () -> Void
let aboutLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void let aboutLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
let displayAboutContextMenu: (String) -> Void let displayAboutContextMenu: (String) -> Void
let openEncryptionKey: (SecretChatKeyFingerprint) -> Void let openEncryptionKey: (SecretChatKeyFingerprint) -> Void
@ -34,7 +35,7 @@ private final class UserInfoControllerArguments {
let botPrivacy: () -> Void let botPrivacy: () -> Void
let report: () -> Void let report: () -> Void
init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, tapAvatarAction: @escaping () -> Void, openChat: @escaping () -> Void, addContact: @escaping () -> Void, shareContact: @escaping () -> Void, shareMyContact: @escaping () -> Void, startSecretChat: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openGroupsInCommon: @escaping () -> Void, updatePeerBlocked: @escaping (Bool) -> Void, deleteContact: @escaping () -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayCopyContextMenu: @escaping (UserInfoEntryTag, String) -> Void, call: @escaping () -> Void, openCallMenu: @escaping (String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, displayAboutContextMenu: @escaping (String) -> Void, openEncryptionKey: @escaping (SecretChatKeyFingerprint) -> Void, addBotToGroup: @escaping () -> Void, shareBot: @escaping () -> Void, botSettings: @escaping () -> Void, botHelp: @escaping () -> Void, botPrivacy: @escaping () -> Void, report: @escaping () -> Void) { init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, tapAvatarAction: @escaping () -> Void, openChat: @escaping () -> Void, addContact: @escaping () -> Void, shareContact: @escaping () -> Void, shareMyContact: @escaping () -> Void, startSecretChat: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openGroupsInCommon: @escaping () -> Void, updatePeerBlocked: @escaping (Bool) -> Void, deleteContact: @escaping () -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayCopyContextMenu: @escaping (UserInfoEntryTag, String) -> Void, call: @escaping () -> Void, openCallMenu: @escaping (String) -> Void, requestPhoneNumber: @escaping () -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, displayAboutContextMenu: @escaping (String) -> Void, openEncryptionKey: @escaping (SecretChatKeyFingerprint) -> Void, addBotToGroup: @escaping () -> Void, shareBot: @escaping () -> Void, botSettings: @escaping () -> Void, botHelp: @escaping () -> Void, botPrivacy: @escaping () -> Void, report: @escaping () -> Void) {
self.account = account self.account = account
self.avatarAndNameInfoContext = avatarAndNameInfoContext self.avatarAndNameInfoContext = avatarAndNameInfoContext
self.updateEditingName = updateEditingName self.updateEditingName = updateEditingName
@ -54,6 +55,7 @@ private final class UserInfoControllerArguments {
self.displayCopyContextMenu = displayCopyContextMenu self.displayCopyContextMenu = displayCopyContextMenu
self.call = call self.call = call
self.openCallMenu = openCallMenu self.openCallMenu = openCallMenu
self.requestPhoneNumber = requestPhoneNumber
self.aboutLinkAction = aboutLinkAction self.aboutLinkAction = aboutLinkAction
self.displayAboutContextMenu = displayAboutContextMenu self.displayAboutContextMenu = displayAboutContextMenu
self.openEncryptionKey = openEncryptionKey self.openEncryptionKey = openEncryptionKey
@ -95,6 +97,7 @@ private enum UserInfoEntry: ItemListNodeEntry {
case calls(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, messages: [Message]) case calls(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, messages: [Message])
case about(PresentationTheme, Peer, String, String) case about(PresentationTheme, Peer, String, String)
case phoneNumber(PresentationTheme, Int, String, String, Bool) case phoneNumber(PresentationTheme, Int, String, String, Bool)
case requestPhoneNumber(PresentationTheme, String, String)
case userName(PresentationTheme, String, String) case userName(PresentationTheme, String, String)
case sendMessage(PresentationTheme, String) case sendMessage(PresentationTheme, String)
case addContact(PresentationTheme, String) case addContact(PresentationTheme, String)
@ -115,7 +118,7 @@ private enum UserInfoEntry: ItemListNodeEntry {
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .info, .calls, .about, .phoneNumber, .userName: case .info, .calls, .about, .phoneNumber, .requestPhoneNumber, .userName:
return UserInfoSection.info.rawValue return UserInfoSection.info.rawValue
case .sendMessage, .addContact, .shareContact, .shareMyContact, .startSecretChat, .botAddToGroup, .botShare: case .sendMessage, .addContact, .shareContact, .shareMyContact, .startSecretChat, .botAddToGroup, .botShare:
return UserInfoSection.actions.rawValue return UserInfoSection.actions.rawValue
@ -203,6 +206,12 @@ private enum UserInfoEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .requestPhoneNumber(lhsTheme, lhsLabel, lhsValue):
if case let .requestPhoneNumber(rhsTheme, rhsLabel, rhsValue) = rhs, lhsTheme === rhsTheme, lhsLabel == rhsLabel, lhsValue == rhsValue {
return true
} else {
return false
}
case let .userName(lhsTheme, lhsText, lhsValue): case let .userName(lhsTheme, lhsText, lhsValue):
if case let .userName(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .userName(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true return true
@ -316,6 +325,8 @@ private enum UserInfoEntry: ItemListNodeEntry {
return 1 return 1
case let .phoneNumber(_, index, _, _, _): case let .phoneNumber(_, index, _, _, _):
return 2 + index return 2 + index
case .requestPhoneNumber:
return 998
case .about: case .about:
return 999 return 999
case .userName: case .userName:
@ -387,6 +398,10 @@ private enum UserInfoEntry: ItemListNodeEntry {
}, longTapAction: { }, longTapAction: {
arguments.displayCopyContextMenu(.phoneNumber, value) arguments.displayCopyContextMenu(.phoneNumber, value)
}, tag: UserInfoEntryTag.phoneNumber) }, tag: UserInfoEntryTag.phoneNumber)
case let .requestPhoneNumber(theme, label, value):
return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: {
arguments.requestPhoneNumber()
})
case let .userName(theme, text, value): case let .userName(theme, text, value):
return ItemListTextWithLabelItem(theme: theme, label: text, text: "@\(value)", textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: { return ItemListTextWithLabelItem(theme: theme, label: text, text: "@\(value)", textColor: .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: {
arguments.displayUsernameContextMenu("@\(value)") arguments.displayUsernameContextMenu("@\(value)")
@ -602,6 +617,8 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat
index += 1 index += 1
} }
} }
} else {
entries.append(UserInfoEntry.requestPhoneNumber(presentationData.theme, "phone", "Request Number"))
} }
let aboutTitle: String let aboutTitle: String
@ -989,6 +1006,11 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us
context.sharedContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(number).replacingOccurrences(of: " ", with: ""))") context.sharedContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(number).replacingOccurrences(of: " ", with: ""))")
} }
}) })
}, requestPhoneNumber: {
let _ = (requestPhoneNumber(account: context.account, peerId: peerId)
|> deliverOnMainQueue).start(completed: {
})
}, aboutLinkAction: { action, itemLink in }, aboutLinkAction: { action, itemLink in
aboutLinkActionImpl?(action, itemLink) aboutLinkActionImpl?(action, itemLink)
}, displayAboutContextMenu: { text in }, displayAboutContextMenu: { text in