Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mike Renoir 2022-10-11 11:25:12 +04:00
commit 6c73f79acf
14 changed files with 737 additions and 97 deletions

View File

@ -8089,10 +8089,26 @@ Sorry for the inconvenience.";
"Group.Setup.LinkActive" = "active"; "Group.Setup.LinkActive" = "active";
"Group.Setup.LinkInactive" = "inactive"; "Group.Setup.LinkInactive" = "inactive";
"Group.Setup.ActivateAlertTitle" = "Activate Link";
"Group.Setup.ActivateAlertText" = "Do you want to show this link on the group info page?";
"Group.Setup.ActivateAlertShow" = "Show";
"Group.Setup.DeactivateAlertTitle" = "Deactivate Link";
"Group.Setup.DeactivateAlertText" = "Do you want to hide this link from the group info page?";
"Group.Setup.DeactivateAlertHide" = "Hide";
"Channel.Setup.PublicLink" = "PUBLIC LINK"; "Channel.Setup.PublicLink" = "PUBLIC LINK";
"Channel.Setup.LinksOrder" = "LINKS ORDER"; "Channel.Setup.LinksOrder" = "LINKS ORDER";
"Channel.Setup.LinksOrderInfo" = "Drag and drop links to change the order in which they will be displayed on the channel info page."; "Channel.Setup.LinksOrderInfo" = "Drag and drop links to change the order in which they will be displayed on the channel info page.";
"Channel.Setup.ActivateAlertTitle" = "Activate Link";
"Channel.Setup.ActivateAlertText" = "Do you want to show this link on the channel info page?";
"Channel.Setup.ActivateAlertShow" = "Show";
"Channel.Setup.DeactivateAlertTitle" = "Deactivate Link";
"Channel.Setup.DeactivateAlertText" = "Do you want to hide this link from the channel info page?";
"Channel.Setup.DeactivateAlertHide" = "Hide";
"Username.Username" = "USERNAME"; "Username.Username" = "USERNAME";
"Username.LinksOrder" = "USERNAMES ORDER"; "Username.LinksOrder" = "USERNAMES ORDER";
"Username.LinksOrderInfo" = "Drag and drop links to change the order in which they will be displayed on your info page."; "Username.LinksOrderInfo" = "Drag and drop links to change the order in which they will be displayed on your info page.";
@ -8101,7 +8117,7 @@ Sorry for the inconvenience.";
"Username.ActivateAlertText" = "Do you want to show this link on your info page?"; "Username.ActivateAlertText" = "Do you want to show this link on your info page?";
"Username.ActivateAlertShow" = "Show"; "Username.ActivateAlertShow" = "Show";
"Username.DeactivateAlertTitle" = "Deativate Username"; "Username.DeactivateAlertTitle" = "Deactivate Username";
"Username.DeactivateAlertText" = "Do you want to hide this link from your info page?"; "Username.DeactivateAlertText" = "Do you want to hide this link from your info page?";
"Username.DeactivateAlertHide" = "Hide"; "Username.DeactivateAlertHide" = "Hide";

View File

@ -2677,10 +2677,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.animateRevealOptionsFill { self.animateRevealOptionsFill {
self.revealOptionsInteractivelyClosed() self.revealOptionsInteractivelyClosed()
} }
case RevealOptionKey.open.rawValue:
break
case RevealOptionKey.close.rawValue:
break
default: default:
break break
} }
@ -2688,6 +2684,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
switch option.key { switch option.key {
case RevealOptionKey.delete.rawValue: case RevealOptionKey.delete.rawValue:
item.interaction.deletePeerThread(peerId, threadId) item.interaction.deletePeerThread(peerId, threadId)
case RevealOptionKey.open.rawValue:
break
case RevealOptionKey.close.rawValue:
break
default: default:
break break
} }

View File

@ -156,7 +156,7 @@ private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat,
} }
public final class LinkHighlightingNode: ASDisplayNode { public final class LinkHighlightingNode: ASDisplayNode {
private var rects: [CGRect] = [] public private(set) var rects: [CGRect] = []
public let imageNode: ASImageNode public let imageNode: ASImageNode
public var innerRadius: CGFloat = 4.0 public var innerRadius: CGFloat = 4.0

View File

@ -44,7 +44,7 @@ public class AdditionalLinkItem: ListViewItem, ItemListItem {
last = true last = true
} }
} }
let node = ItemListInviteLinkItemNode() let node = AdditionalLinkItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), firstWithHeader, last) let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem), firstWithHeader, last)
node.contentSize = layout.contentSize node.contentSize = layout.contentSize
@ -60,7 +60,7 @@ public class AdditionalLinkItem: ListViewItem, ItemListItem {
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async { Queue.mainQueue().async {
if let nodeValue = node() as? ItemListInviteLinkItemNode { if let nodeValue = node() as? AdditionalLinkItemNode {
let makeLayout = nodeValue.asyncLayout() let makeLayout = nodeValue.asyncLayout()
async { async {
@ -94,7 +94,7 @@ public class AdditionalLinkItem: ListViewItem, ItemListItem {
} }
} }
public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
@ -384,13 +384,19 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel))
if strongSelf.reorderControlNode == nil { if strongSelf.reorderControlNode == nil && item.username?.flags.contains(.isActive) == true {
let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate) let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate)
strongSelf.reorderControlNode = reorderControlNode strongSelf.reorderControlNode = reorderControlNode
strongSelf.addSubnode(reorderControlNode) strongSelf.addSubnode(reorderControlNode)
reorderControlNode.alpha = 0.0 reorderControlNode.alpha = 0.0
transition.updateAlpha(node: reorderControlNode, alpha: 1.0) transition.updateAlpha(node: reorderControlNode, alpha: 1.0)
} else if let reorderControlNode = strongSelf.reorderControlNode, item.username?.flags.contains(.isActive) == false {
strongSelf.reorderControlNode = nil
reorderControlNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reorderControlNode] _ in
reorderControlNode?.removeFromSupernode()
})
} }
let reorderControlFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - reorderControlSizeAndApply.0, y: 0.0), size: CGSize(width: reorderControlSizeAndApply.0, height: layout.contentSize.height)) let reorderControlFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - reorderControlSizeAndApply.0, y: 0.0), size: CGSize(width: reorderControlSizeAndApply.0, height: layout.contentSize.height))
strongSelf.reorderControlNode?.frame = reorderControlFrame strongSelf.reorderControlNode?.frame = reorderControlFrame

View File

@ -39,8 +39,10 @@ private final class ChannelVisibilityControllerArguments {
let toggleForwarding: (Bool) -> Void let toggleForwarding: (Bool) -> Void
let updateJoinToSend: (CurrentChannelJoinToSend) -> Void let updateJoinToSend: (CurrentChannelJoinToSend) -> Void
let toggleApproveMembers: (Bool) -> Void let toggleApproveMembers: (Bool) -> Void
let activateLink: (String) -> Void
let deactivateLink: (String) -> Void
init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void) { init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void) {
self.context = context self.context = context
self.updateCurrentType = updateCurrentType self.updateCurrentType = updateCurrentType
self.updatePublicLinkText = updatePublicLinkText self.updatePublicLinkText = updatePublicLinkText
@ -55,6 +57,8 @@ private final class ChannelVisibilityControllerArguments {
self.toggleForwarding = toggleForwarding self.toggleForwarding = toggleForwarding
self.updateJoinToSend = updateJoinToSend self.updateJoinToSend = updateJoinToSend
self.toggleApproveMembers = toggleApproveMembers self.toggleApproveMembers = toggleApproveMembers
self.activateLink = activateLink
self.deactivateLink = deactivateLink
} }
} }
@ -63,6 +67,7 @@ private enum ChannelVisibilitySection: Int32 {
case limitInfo case limitInfo
case link case link
case linkActions case linkActions
case additional
case joinToSend case joinToSend
case approveMembers case approveMembers
case forwarding case forwarding
@ -81,6 +86,11 @@ private enum ChannelVisibilityEntryTag: ItemListItemTag {
} }
} }
private enum ChannelVisibilityEntryId: Hashable {
case index(Int32)
case username(String)
}
private enum ChannelVisibilityEntry: ItemListNodeEntry { private enum ChannelVisibilityEntry: ItemListNodeEntry {
case typeHeader(PresentationTheme, String) case typeHeader(PresentationTheme, String)
case typePublic(PresentationTheme, String, Bool) case typePublic(PresentationTheme, String, Bool)
@ -103,6 +113,10 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case existingLinksInfo(PresentationTheme, String) case existingLinksInfo(PresentationTheme, String)
case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool)
case additionalLinkHeader(PresentationTheme, String)
case additionalLink(PresentationTheme, TelegramPeerUsername, Int32)
case additionalLinkInfo(PresentationTheme, String)
case joinToSendHeader(PresentationTheme, String) case joinToSendHeader(PresentationTheme, String)
case joinToSendEveryone(PresentationTheme, String, Bool) case joinToSendEveryone(PresentationTheme, String, Bool)
case joinToSendMembers(PresentationTheme, String, Bool) case joinToSendMembers(PresentationTheme, String, Bool)
@ -122,6 +136,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return ChannelVisibilitySection.limitInfo.rawValue return ChannelVisibilitySection.limitInfo.rawValue
case .publicLinkHeader, .publicLinkAvailability, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus: case .publicLinkHeader, .publicLinkAvailability, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus:
return ChannelVisibilitySection.link.rawValue return ChannelVisibilitySection.link.rawValue
case .additionalLinkHeader, .additionalLink, .additionalLinkInfo:
return ChannelVisibilitySection.additional.rawValue
case .privateLinkManage, .privateLinkManageInfo: case .privateLinkManage, .privateLinkManageInfo:
return ChannelVisibilitySection.linkActions.rawValue return ChannelVisibilitySection.linkActions.rawValue
case .existingLinksInfo, .existingLinkPeerItem: case .existingLinksInfo, .existingLinkPeerItem:
@ -135,58 +151,64 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} }
} }
var stableId: Int32 { var stableId: ChannelVisibilityEntryId {
switch self { switch self {
case .typeHeader: case .typeHeader:
return 0 return .index(0)
case .typePublic: case .typePublic:
return 1 return .index(1)
case .typePrivate: case .typePrivate:
return 2 return .index(2)
case .typeInfo: case .typeInfo:
return 3 return .index(3)
case .publicLinkHeader: case .publicLinkHeader:
return 4 return .index(4)
case .publicLinkAvailability: case .publicLinkAvailability:
return 5 return .index(5)
case .linksLimitInfo: case .linksLimitInfo:
return 6 return .index(6)
case .privateLinkHeader: case .privateLinkHeader:
return 7 return .index(7)
case .privateLink: case .privateLink:
return 8 return .index(8)
case .editablePublicLink: case .editablePublicLink:
return 9 return .index(9)
case .privateLinkInfo: case .privateLinkInfo:
return 10 return .index(10)
case .publicLinkStatus: case .publicLinkStatus:
return 11 return .index(11)
case .publicLinkInfo: case .publicLinkInfo:
return 12 return .index(12)
case .existingLinksInfo: case .existingLinksInfo:
return 13 return .index(13)
case let .existingLinkPeerItem(index, _, _, _, _, _, _, _): case let .existingLinkPeerItem(index, _, _, _, _, _, _, _):
return 14 + index return .index(14 + index)
case .additionalLinkHeader:
return .index(1000)
case let .additionalLink(_, username, _):
return .username(username.username)
case .additionalLinkInfo:
return .index(2000)
case .privateLinkManage: case .privateLinkManage:
return 1000 return .index(2001)
case .privateLinkManageInfo: case .privateLinkManageInfo:
return 1001 return .index(2002)
case .joinToSendHeader: case .joinToSendHeader:
return 1002 return .index(2003)
case .joinToSendEveryone: case .joinToSendEveryone:
return 1003 return .index(2004)
case .joinToSendMembers: case .joinToSendMembers:
return 1004 return .index(2005)
case .approveMembers: case .approveMembers:
return 1005 return .index(2006)
case .approveMembersInfo: case .approveMembersInfo:
return 1006 return .index(2007)
case .forwardingHeader: case .forwardingHeader:
return 1007 return .index(2008)
case .forwardingDisabled: case .forwardingDisabled:
return 1008 return .index(2009)
case .forwardingInfo: case .forwardingInfo:
return 1009 return .index(2010)
} }
} }
@ -234,6 +256,24 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .additionalLinkHeader(lhsTheme, lhsText):
if case let .additionalLinkHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .additionalLink(lhsTheme, lhsAddressName, lhsIndex):
if case let .additionalLink(rhsTheme, rhsAddressName, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsIndex == rhsIndex {
return true
} else {
return false
}
case let .additionalLinkInfo(lhsTheme, lhsText):
if case let .additionalLinkInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .privateLinkHeader(lhsTheme, lhsTitle): case let .privateLinkHeader(lhsTheme, lhsTitle):
if case let .privateLinkHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle { if case let .privateLinkHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle {
return true return true
@ -370,7 +410,206 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} }
static func <(lhs: ChannelVisibilityEntry, rhs: ChannelVisibilityEntry) -> Bool { static func <(lhs: ChannelVisibilityEntry, rhs: ChannelVisibilityEntry) -> Bool {
return lhs.stableId < rhs.stableId switch lhs {
case .typeHeader:
switch rhs {
case .typeHeader:
return false
default:
return true
}
case .typePublic:
switch rhs {
case .typeHeader, .typePublic:
return false
default:
return true
}
case .typePrivate:
switch rhs {
case .typeHeader, .typePublic, .typePrivate:
return false
default:
return true
}
case .typeInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
return false
default:
return true
}
case .publicLinkHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader:
return false
default:
return true
}
case .publicLinkAvailability:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability:
return false
default:
return true
}
case .linksLimitInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo:
return false
default:
return true
}
case .privateLinkHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader:
return false
default:
return true
}
case .privateLink:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink:
return false
default:
return true
}
case .editablePublicLink:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink:
return false
default:
return true
}
case .privateLinkInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo:
return false
default:
return true
}
case .publicLinkStatus:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus:
return false
default:
return true
}
case .publicLinkInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo:
return false
default:
return true
}
case .existingLinksInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo:
return false
default:
return true
}
case let .existingLinkPeerItem(lhsIndex, _, _, _, _, _, _, _):
switch rhs {
case let .existingLinkPeerItem(rhsIndex, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo:
return false
default:
return true
}
case .additionalLinkHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader:
return false
default:
return true
}
case let .additionalLink(_, _, lhsIndex):
switch rhs {
case let .additionalLink(_, _, rhsIndex):
return lhsIndex < rhsIndex
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader:
return false
default:
return true
}
case .additionalLinkInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo:
return false
default:
return true
}
case .privateLinkManage:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage:
return false
default:
return true
}
case .privateLinkManageInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo:
return false
default:
return true
}
case .joinToSendHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader:
return false
default:
return true
}
case .joinToSendEveryone:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone:
return false
default:
return true
}
case .joinToSendMembers:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers:
return false
default:
return true
}
case .approveMembers:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers:
return false
default:
return true
}
case .approveMembersInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers, .approveMembersInfo:
return false
default:
return true
}
case .forwardingHeader:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers, .approveMembersInfo, .forwardingHeader:
return false
default:
return true
}
case .forwardingDisabled:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers, .approveMembersInfo, .forwardingHeader, .forwardingDisabled:
return false
default:
return true
}
case .forwardingInfo:
switch rhs {
case .typeHeader, .typePublic, .typePrivate, .typeInfo, .publicLinkHeader, .publicLinkAvailability, .linksLimitInfo, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkStatus, .publicLinkInfo, .existingLinksInfo, .existingLinkPeerItem, .additionalLinkHeader, .additionalLink, .additionalLinkInfo, .privateLinkManage, .privateLinkManageInfo, .joinToSendHeader, .joinToSendEveryone, .joinToSendMembers, .approveMembers, .approveMembersInfo, .forwardingHeader, .forwardingDisabled, .forwardingInfo:
return false
}
}
} }
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
@ -465,6 +704,20 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
}, removePeer: { peerId in }, removePeer: { peerId in
arguments.revokePeerId(peerId) arguments.revokePeerId(peerId)
}) })
case let .additionalLinkHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .additionalLink(_, link, _):
return AdditionalLinkItem(presentationData: presentationData, username: link, sectionId: self.section, style: .blocks, tapAction: {
if !link.flags.contains(.isEditable) {
if link.flags.contains(.isActive) {
arguments.deactivateLink(link.username)
} else {
arguments.activateLink(link.username)
}
}
})
case let .additionalLinkInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .joinToSendHeader(_, title): case let .joinToSendHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .joinToSendEveryone(_, text, selected): case let .joinToSendEveryone(_, text, selected):
@ -622,7 +875,7 @@ private struct ChannelVisibilityControllerState: Equatable {
} }
} }
private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [Peer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool, isPremiumDisabled: Bool) -> [ChannelVisibilityEntry] { private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [Peer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool, isPremiumDisabled: Bool, temporaryOrder: [String]?) -> [ChannelVisibilityEntry] {
var entries: [ChannelVisibilityEntry] = [] var entries: [ChannelVisibilityEntry] = []
let isInitialSetup: Bool let isInitialSetup: Bool
@ -728,6 +981,8 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
} }
} }
let otherUsernames = peer.usernames.filter { !$0.flags.contains(.isEditable) }
if case .revokeNames = mode { if case .revokeNames = mode {
let count = Int32(publicChannelsToRevoke?.count ?? 0) let count = Int32(publicChannelsToRevoke?.count ?? 0)
@ -821,6 +1076,33 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
} else { } else {
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp)) entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Channel_Username_CreatePublicLinkHelp))
} }
if !otherUsernames.isEmpty {
entries.append(.additionalLinkHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_LinksOrder : presentationData.strings.Channel_Setup_LinksOrder))
var usernames = peer.usernames
if let temporaryOrder = temporaryOrder {
var usernamesMap: [String: TelegramPeerUsername] = [:]
for username in usernames {
usernamesMap[username.username] = username
}
var sortedUsernames: [TelegramPeerUsername] = []
for username in temporaryOrder {
if let username = usernamesMap[username] {
sortedUsernames.append(username)
}
}
usernames = sortedUsernames
}
var i: Int32 = 0
for username in usernames {
entries.append(.additionalLink(presentationData.theme, username, i))
i += 1
}
entries.append(.additionalLinkInfo(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_LinksOrderInfo : presentationData.strings.Channel_Setup_LinksOrderInfo))
}
switch mode { switch mode {
case .initialSetup, .revokeNames: case .initialSetup, .revokeNames:
break break
@ -1110,6 +1392,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
})) }))
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
var dismissInputImpl: (() -> Void)?
var nextImpl: (() -> Void)? var nextImpl: (() -> Void)?
var scrollToPublicLinkTextImpl: (() -> Void)? var scrollToPublicLinkTextImpl: (() -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)?
@ -1142,6 +1425,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
let toggleRequestToJoinDisposable = MetaDisposable() let toggleRequestToJoinDisposable = MetaDisposable()
actionsDisposable.add(toggleRequestToJoinDisposable) actionsDisposable.add(toggleRequestToJoinDisposable)
let temporaryOrder = Promise<[String]?>(nil)
let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in
if type == .publicChannel { if type == .publicChannel {
let _ = combineLatest( let _ = combineLatest(
@ -1363,6 +1648,63 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
updateState { state in updateState { state in
return state.withUpdatedApproveMembers(value) return state.withUpdatedApproveMembers(value)
} }
}, activateLink: { name in
dismissInputImpl?()
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if case let .channel(channel) = peer, case .broadcast = channel.info {
isGroup = false
} else {
isGroup = true
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let title: String
let text: String
let action: String
if isGroup {
title = presentationData.strings.Group_Setup_ActivateAlertTitle
text = presentationData.strings.Group_Setup_ActivateAlertText
action = presentationData.strings.Group_Setup_ActivateAlertShow
} else {
title = presentationData.strings.Channel_Setup_ActivateAlertTitle
text = presentationData.strings.Channel_Setup_ActivateAlertText
action = presentationData.strings.Channel_Setup_ActivateAlertShow
}
presentControllerImpl?(textAlertController(context: context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: action, action: {
let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: true).start()
})]), nil)
})
}, deactivateLink: { name in
dismissInputImpl?()
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
let isGroup: Bool
if case let .channel(channel) = peer, case .broadcast = channel.info {
isGroup = false
} else {
isGroup = true
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let title: String
let text: String
let action: String
if isGroup {
title = presentationData.strings.Group_Setup_DeactivateAlertTitle
text = presentationData.strings.Group_Setup_DeactivateAlertText
action = presentationData.strings.Group_Setup_DeactivateAlertHide
} else {
title = presentationData.strings.Channel_Setup_DeactivateAlertTitle
text = presentationData.strings.Channel_Setup_DeactivateAlertText
action = presentationData.strings.Channel_Setup_DeactivateAlertHide
}
presentControllerImpl?(textAlertController(context: context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: action, action: {
let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: false).start()
})]), nil)
})
}) })
let peerView = context.account.viewTracker.peerView(peerId) let peerView = context.account.viewTracker.peerView(peerId)
@ -1370,6 +1712,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
let previousHadNamesToRevoke = Atomic<Bool?>(value: nil) let previousHadNamesToRevoke = Atomic<Bool?>(value: nil)
let previousInvitation = Atomic<ExportedInvitation?>(value: nil) let previousInvitation = Atomic<ExportedInvitation?>(value: nil)
let previousUsernames = Atomic<[String]?>(value: nil)
let mainLink = context.engine.data.subscribe( let mainLink = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId) TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId)
@ -1403,10 +1746,11 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true), TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true),
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)
) ),
temporaryOrder.get()
) )
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, publicChannelsToRevoke, importersContext, importers, data -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, view, publicChannelsToRevoke, importersContext, importers, data, temporaryOrder -> (ItemListControllerState, (ItemListNodeState, Any)) in
let peer = peerViewMainPeer(view) let peer = peerViewMainPeer(view)
let (limits, premiumLimits, accountPeer) = data let (limits, premiumLimits, accountPeer) = data
@ -1653,7 +1997,11 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
let hasNamesToRevoke = publicChannelsToRevoke != nil && !publicChannelsToRevoke!.isEmpty let hasNamesToRevoke = publicChannelsToRevoke != nil && !publicChannelsToRevoke!.isEmpty
let hadNamesToRevoke = previousHadNamesToRevoke.swap(hasNamesToRevoke) let hadNamesToRevoke = previousHadNamesToRevoke.swap(hasNamesToRevoke)
if let peer = view.peers[view.peerId] as? TelegramChannel { if let peer = view.peers[view.peerId] as? TelegramChannel {
let currentUsernames = peer.usernames.map { $0.username }
let previousUsernames = previousUsernames.swap(currentUsernames)
let selectedType: CurrentChannelType let selectedType: CurrentChannelType
if case .privateLink = mode { if case .privateLink = mode {
selectedType = .privateChannel selectedType = .privateChannel
@ -1678,6 +2026,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
if let hadNamesToRevoke = hadNamesToRevoke { if let hadNamesToRevoke = hadNamesToRevoke {
animateChanges = hadNamesToRevoke != hasNamesToRevoke animateChanges = hadNamesToRevoke != hasNamesToRevoke
} }
if temporaryOrder != nil || previousUsernames != currentUsernames {
animateChanges = true
}
} }
let title: String let title: String
@ -1694,7 +2046,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
title = presentationData.strings.Premium_LimitReached title = presentationData.strings.Premium_LimitReached
} }
let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, importers: importers, state: state, limits: limits, premiumLimits: premiumLimits, isPremium: isPremium, isPremiumDisabled: premiumConfiguration.isPremiumDisabled) let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, importers: importers, state: state, limits: limits, premiumLimits: premiumLimits, isPremium: isPremium, isPremiumDisabled: premiumConfiguration.isPremiumDisabled, temporaryOrder: temporaryOrder)
var focusItemTag: ItemListItemTag? var focusItemTag: ItemListItemTag?
if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil { if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
@ -1713,6 +2065,125 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
controller.willDisappear = { _ in controller.willDisappear = { _ in
dismissTooltipsImpl?() dismissTooltipsImpl?()
} }
controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ChannelVisibilityEntry]) -> Signal<Bool, NoError> in
let fromEntry = entries[fromIndex]
guard case let .additionalLink(_, fromUsername, _) = fromEntry else {
return .single(false)
}
var referenceId: String?
var beforeAll = false
var afterAll = false
var maxIndex: Int?
var currentUsernames: [String] = []
var i = 0
for entry in entries {
switch entry {
case let .additionalLink(_, link, _):
currentUsernames.append(link.username)
if !link.flags.contains(.isActive) && maxIndex == nil {
maxIndex = max(0, i - 1)
}
i += 1
default:
break
}
}
if toIndex < entries.count {
switch entries[toIndex] {
case let .additionalLink(_, toUsername, _):
if toUsername.flags.contains(.isActive) {
referenceId = toUsername.username
} else {
afterAll = true
}
default:
if entries[toIndex] < fromEntry {
beforeAll = true
} else {
afterAll = true
}
}
} else {
afterAll = true
}
var previousIndex: Int?
for i in 0 ..< currentUsernames.count {
if currentUsernames[i] == fromUsername.username {
previousIndex = i
currentUsernames.remove(at: i)
break
}
}
var didReorder = false
if let referenceId = referenceId {
var inserted = false
for i in 0 ..< currentUsernames.count {
if currentUsernames[i] == referenceId {
if fromIndex < toIndex {
didReorder = previousIndex != i + 1
currentUsernames.insert(fromUsername.username, at: i + 1)
} else {
didReorder = previousIndex != i
currentUsernames.insert(fromUsername.username, at: i)
}
inserted = true
break
}
}
if !inserted {
didReorder = previousIndex != currentUsernames.count
if let maxIndex = maxIndex {
currentUsernames.insert(fromUsername.username, at: maxIndex)
} else {
currentUsernames.append(fromUsername.username)
}
}
} else if beforeAll {
didReorder = previousIndex != 0
currentUsernames.insert(fromUsername.username, at: 0)
} else if afterAll {
didReorder = previousIndex != currentUsernames.count
if let maxIndex = maxIndex {
currentUsernames.insert(fromUsername.username, at: maxIndex)
} else {
currentUsernames.append(fromUsername.username)
}
}
temporaryOrder.set(.single(currentUsernames))
if didReorder {
DispatchQueue.main.async {
dismissInputImpl?()
}
}
return .single(didReorder)
})
controller.setReorderCompleted({ (entries: [ChannelVisibilityEntry]) -> Void in
var currentUsernames: [TelegramPeerUsername] = []
for entry in entries {
switch entry {
case let .additionalLink(_, username, _):
currentUsernames.append(username)
default:
break
}
}
let _ = (context.engine.peers.reorderAddressNames(domain: .peer(peerId), names: currentUsernames)
|> deliverOnMainQueue).start(completed: {
temporaryOrder.set(.single(nil))
})
})
controller.beganInteractiveDragging = {
dismissInputImpl?()
}
dismissImpl = { [weak controller, weak onDismissRemoveController] in dismissImpl = { [weak controller, weak onDismissRemoveController] in
guard let controller = controller else { guard let controller = controller else {
return return
@ -1730,6 +2201,9 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
controller.dismiss() controller.dismiss()
} }
} }
dismissInputImpl = { [weak controller] in
controller?.view.endEditing(true)
}
nextImpl = { [weak controller] in nextImpl = { [weak controller] in
if let controller = controller { if let controller = controller {
if case .initialSetup = mode { if case .initialSetup = mode {

View File

@ -10,6 +10,7 @@ import PresentationDataUtils
import AccountContext import AccountContext
import ShareController import ShareController
import UndoUI import UndoUI
import InviteLinksUI
private final class UsernameSetupControllerArguments { private final class UsernameSetupControllerArguments {
let account: Account let account: Account
@ -46,6 +47,10 @@ public enum UsernameEntryTag: ItemListItemTag {
} }
} }
private enum UsernameSetupEntryId: Hashable {
case index(Int32)
case username(String)
}
private enum UsernameSetupEntry: ItemListNodeEntry { private enum UsernameSetupEntry: ItemListNodeEntry {
case publicLinkHeader(PresentationTheme, String) case publicLinkHeader(PresentationTheme, String)
@ -66,22 +71,22 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
} }
} }
var stableId: Int32 { var stableId: UsernameSetupEntryId {
switch self { switch self {
case .publicLinkHeader: case .publicLinkHeader:
return 0 return .index(0)
case .editablePublicLink: case .editablePublicLink:
return 1 return .index(1)
case .publicLinkStatus: case .publicLinkStatus:
return 2 return .index(2)
case .publicLinkInfo: case .publicLinkInfo:
return 3 return .index(3)
case .additionalLinkHeader: case .additionalLinkHeader:
return 4 return .index(4)
case let .additionalLink(_, _, index): case let .additionalLink(_, username, _):
return 5 + index return .username(username.username)
case .additionalLinkInfo: case .additionalLinkInfo:
return 1000 return .index(5)
} }
} }
@ -133,7 +138,57 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
} }
static func <(lhs: UsernameSetupEntry, rhs: UsernameSetupEntry) -> Bool { static func <(lhs: UsernameSetupEntry, rhs: UsernameSetupEntry) -> Bool {
return lhs.stableId < rhs.stableId switch lhs {
case .publicLinkHeader:
switch rhs {
case .publicLinkHeader:
return false
default:
return true
}
case .editablePublicLink:
switch rhs {
case .publicLinkHeader, .editablePublicLink:
return false
default:
return true
}
case .publicLinkStatus:
switch rhs {
case .publicLinkHeader, .editablePublicLink, .publicLinkStatus:
return false
default:
return true
}
case .publicLinkInfo:
switch rhs {
case .publicLinkHeader, .editablePublicLink, .publicLinkStatus, .publicLinkInfo:
return false
default:
return true
}
case .additionalLinkHeader:
switch rhs {
case .publicLinkHeader, .editablePublicLink, .publicLinkStatus, .publicLinkInfo, .additionalLinkHeader:
return false
default:
return true
}
case let .additionalLink(_, _, lhsIndex):
switch rhs {
case let .additionalLink(_, _, rhsIndex):
return lhsIndex < rhsIndex
case .publicLinkHeader, .editablePublicLink, .publicLinkStatus, .publicLinkInfo, .additionalLinkHeader:
return false
default:
return true
}
case .additionalLinkInfo:
switch rhs {
case .publicLinkHeader, .editablePublicLink, .publicLinkStatus, .publicLinkInfo, .additionalLinkHeader, .additionalLink, .additionalLinkInfo:
return false
}
}
} }
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
@ -482,10 +537,32 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
var referenceId: String? var referenceId: String?
var beforeAll = false var beforeAll = false
var afterAll = false var afterAll = false
var maxIndex: Int?
var currentUsernames: [String] = []
var i = 0
for entry in entries {
switch entry {
case let .additionalLink(_, link, _):
currentUsernames.append(link.username)
if !link.flags.contains(.isActive) && maxIndex == nil {
maxIndex = max(0, i - 1)
}
i += 1
default:
break
}
}
if toIndex < entries.count { if toIndex < entries.count {
switch entries[toIndex] { switch entries[toIndex] {
case let .additionalLink(_, toUsername, _): case let .additionalLink(_, toUsername, _):
if toUsername.flags.contains(.isActive) {
referenceId = toUsername.username referenceId = toUsername.username
} else {
afterAll = true
}
default: default:
if entries[toIndex] < fromEntry { if entries[toIndex] < fromEntry {
beforeAll = true beforeAll = true
@ -497,16 +574,6 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
afterAll = true afterAll = true
} }
var currentUsernames: [String] = []
for entry in entries {
switch entry {
case let .additionalLink(_, link, _):
currentUsernames.append(link.username)
default:
break
}
}
var previousIndex: Int? var previousIndex: Int?
for i in 0 ..< currentUsernames.count { for i in 0 ..< currentUsernames.count {
if currentUsernames[i] == fromUsername.username { if currentUsernames[i] == fromUsername.username {
@ -517,7 +584,6 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
} }
var didReorder = false var didReorder = false
if let referenceId = referenceId { if let referenceId = referenceId {
var inserted = false var inserted = false
for i in 0 ..< currentUsernames.count { for i in 0 ..< currentUsernames.count {
@ -535,15 +601,23 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
} }
if !inserted { if !inserted {
didReorder = previousIndex != currentUsernames.count didReorder = previousIndex != currentUsernames.count
if let maxIndex = maxIndex {
currentUsernames.insert(fromUsername.username, at: maxIndex)
} else {
currentUsernames.append(fromUsername.username) currentUsernames.append(fromUsername.username)
} }
}
} else if beforeAll { } else if beforeAll {
didReorder = previousIndex != 0 didReorder = previousIndex != 0
currentUsernames.insert(fromUsername.username, at: 0) currentUsernames.insert(fromUsername.username, at: 0)
} else if afterAll { } else if afterAll {
didReorder = previousIndex != currentUsernames.count didReorder = previousIndex != currentUsernames.count
if let maxIndex = maxIndex {
currentUsernames.insert(fromUsername.username, at: maxIndex)
} else {
currentUsernames.append(fromUsername.username) currentUsernames.append(fromUsername.username)
} }
}
temporaryOrder.set(.single(currentUsernames)) temporaryOrder.set(.single(currentUsernames))

View File

@ -190,14 +190,40 @@ func _internal_toggleAddressNameActive(account: Account, domain: AddressNameDoma
var updatedNames = peer.usernames var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) { if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active { if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive) updatedFlags.insert(.isActive)
} else { } else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive) updatedFlags.remove(.isActive)
} }
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name) let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index) updatedNames.remove(at: index)
updatedNames.insert(updatedName, at: index) if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
} }
let updatedUser = peer.withUpdatedUsernames(updatedNames) let updatedUser = peer.withUpdatedUsernames(updatedNames)
updatePeers(transaction: transaction, peers: [updatedUser], update: { _, updated in updatePeers(transaction: transaction, peers: [updatedUser], update: { _, updated in
@ -218,14 +244,40 @@ func _internal_toggleAddressNameActive(account: Account, domain: AddressNameDoma
var updatedNames = peer.usernames var updatedNames = peer.usernames
if let index = updatedNames.firstIndex(where: { $0.username == name }) { if let index = updatedNames.firstIndex(where: { $0.username == name }) {
var updatedFlags = updatedNames[index].flags var updatedFlags = updatedNames[index].flags
var updateOrder = true
var updatedIndex = index
if active { if active {
if updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.insert(.isActive) updatedFlags.insert(.isActive)
} else { } else {
if !updatedFlags.contains(.isActive) {
updateOrder = false
}
updatedFlags.remove(.isActive) updatedFlags.remove(.isActive)
} }
let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name) let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name)
updatedNames.remove(at: index) updatedNames.remove(at: index)
updatedNames.insert(updatedName, at: index) if updateOrder {
if active {
updatedIndex = 0
} else {
updatedIndex = updatedNames.count
}
var i = 0
for name in updatedNames {
if active && !name.flags.contains(.isActive) {
updatedIndex = i
break
} else if !active && !name.flags.contains(.isActive) {
updatedIndex = i
break
}
i += 1
}
}
updatedNames.insert(updatedName, at: updatedIndex)
} }
let updatedPeer = peer.withUpdatedAddressNames(updatedNames) let updatedPeer = peer.withUpdatedAddressNames(updatedNames)
updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in
@ -251,7 +303,7 @@ func _internal_reorderAddressNames(account: Account, domain: AddressNameDomain,
return account.postbox.transaction { transaction -> Signal<Void, ReorderAddressNamesError> in return account.postbox.transaction { transaction -> Signal<Void, ReorderAddressNamesError> in
switch domain { switch domain {
case .account: case .account:
return account.network.request(Api.functions.account.reorderUsernames(order: names.map { $0.username }), automaticFloodWait: false) return account.network.request(Api.functions.account.reorderUsernames(order: names.filter { $0.flags.contains(.isActive) }.map { $0.username }), automaticFloodWait: false)
|> mapError { _ -> ReorderAddressNamesError in |> mapError { _ -> ReorderAddressNamesError in
return .generic return .generic
} }
@ -267,7 +319,7 @@ func _internal_reorderAddressNames(account: Account, domain: AddressNameDomain,
} }
case let .peer(peerId): case let .peer(peerId):
if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) { if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.reorderUsernames(channel: inputChannel, order: names.map { $0.username }), automaticFloodWait: false) return account.network.request(Api.functions.channels.reorderUsernames(channel: inputChannel, order: names.filter { $0.flags.contains(.isActive) }.map { $0.username }), automaticFloodWait: false)
|> mapError { _ -> ReorderAddressNamesError in |> mapError { _ -> ReorderAddressNamesError in
return .generic return .generic
} }

View File

@ -300,6 +300,7 @@ swift_library(
"//submodules/TelegramUI/Components/ForumTopicListScreen:ForumTopicListScreen", "//submodules/TelegramUI/Components/ForumTopicListScreen:ForumTopicListScreen",
"//submodules/TelegramUI/Components/ForumCreateTopicScreen:ForumCreateTopicScreen", "//submodules/TelegramUI/Components/ForumCreateTopicScreen:ForumCreateTopicScreen",
"//submodules/TelegramUI/Components/ChatTitleView", "//submodules/TelegramUI/Components/ChatTitleView",
"//submodules/InviteLinksUI:InviteLinksUI",
] + select({ ] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -3906,7 +3906,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var animateReplyNodeIn = false var animateReplyNodeIn = false
if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) { if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) {
if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item { if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item {
self.swipeToReplyFeedback?.impact() self.swipeToReplyFeedback?.impact(.heavy)
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction)) let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction))
self.swipeToReplyNode = swipeToReplyNode self.swipeToReplyNode = swipeToReplyNode

View File

@ -998,7 +998,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
var animateReplyNodeIn = false var animateReplyNodeIn = false
if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) { if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) {
if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item { if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item {
self.swipeToReplyFeedback?.impact() self.swipeToReplyFeedback?.impact(.heavy)
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction)) let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction))
self.swipeToReplyNode = swipeToReplyNode self.swipeToReplyNode = swipeToReplyNode

View File

@ -1250,7 +1250,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
var animateReplyNodeIn = false var animateReplyNodeIn = false
if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) { if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) {
if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item { if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item {
self.swipeToReplyFeedback?.impact() self.swipeToReplyFeedback?.impact(.heavy)
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction)) let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction))
self.swipeToReplyNode = swipeToReplyNode self.swipeToReplyNode = swipeToReplyNode

View File

@ -578,7 +578,7 @@ final class ChatQrCodeScreen: ViewController {
} else { } else {
result = "t_me-\(Int32.random(in: 0 ..< Int32.max))" result = "t_me-\(Int32.random(in: 0 ..< Int32.max))"
} }
if let threadId = threadId { if let threadId = threadId, threadId != 0 {
result.append("-\(threadId)") result.append("-\(threadId)")
} }
return result return result
@ -1519,7 +1519,7 @@ private class QrContentNode: ASDisplayNode, ContentNode {
} else { } else {
codeText = peer.debugDisplayTitle.uppercased() codeText = peer.debugDisplayTitle.uppercased()
} }
if let threadId = self.threadId { if let threadId = self.threadId, threadId != 0 {
codeText += "/\(threadId)" codeText += "/\(threadId)"
} }

View File

@ -32,7 +32,7 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
let icon: PeerInfoScreenLabeledValueIcon? let icon: PeerInfoScreenLabeledValueIcon?
let action: ((ASDisplayNode) -> Void)? let action: ((ASDisplayNode) -> Void)?
let longTapAction: ((ASDisplayNode) -> Void)? let longTapAction: ((ASDisplayNode) -> Void)?
let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? let linkItemAction: ((TextLinkItemActionType, TextLinkItem, ASDisplayNode, CGRect?) -> Void)?
let iconAction: (() -> Void)? let iconAction: (() -> Void)?
let contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? let contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
let requestLayout: () -> Void let requestLayout: () -> Void
@ -47,7 +47,7 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
icon: PeerInfoScreenLabeledValueIcon? = nil, icon: PeerInfoScreenLabeledValueIcon? = nil,
action: ((ASDisplayNode) -> Void)?, action: ((ASDisplayNode) -> Void)?,
longTapAction: ((ASDisplayNode) -> Void)? = nil, longTapAction: ((ASDisplayNode) -> Void)? = nil,
linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem, ASDisplayNode, CGRect?) -> Void)? = nil,
iconAction: (() -> Void)? = nil, iconAction: (() -> Void)? = nil,
contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil,
requestLayout: @escaping () -> Void requestLayout: @escaping () -> Void
@ -313,7 +313,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
case .tap, .longTap: case .tap, .longTap:
if let item = self.item { if let item = self.item {
if let linkItem = self.linkItemAtPoint(location) { if let linkItem = self.linkItemAtPoint(location) {
item.linkItemAction?(gesture == .tap ? .tap : .longTap, linkItem) item.linkItemAction?(gesture == .tap ? .tap : .longTap, linkItem, self.linkHighlightingNode ?? self, self.linkHighlightingNode?.rects.first)
} else if case .longTap = gesture { } else if case .longTap = gesture {
item.longTapAction?(self) item.longTapAction?(self)
} else if case .tap = gesture { } else if case .tap = gesture {
@ -601,7 +601,8 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
} }
if textNode == nil { if textNode == nil {
let additionalTextNodeFrame = self.additionalTextNode.frame let additionalTextNodeFrame = self.additionalTextNode.frame
if let (index, attributes) = self.additionalTextNode.attributesAtPoint(CGPoint(x: point.x - additionalTextNodeFrame.minX, y: point.y - additionalTextNodeFrame.minY)) { let mappedPoint = CGPoint(x: point.x - additionalTextNodeFrame.minX, y: point.y - additionalTextNodeFrame.minY)
if mappedPoint.y > 0.0, let (index, attributes) = self.additionalTextNode.attributesAtPoint(mappedPoint) {
let possibleNames: [String] = [ let possibleNames: [String] = [
TelegramTextAttributes.URL, TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention, TelegramTextAttributes.PeerMention,

View File

@ -488,7 +488,7 @@ private final class PeerInfoInteraction {
let editingOpenSetupLocation: () -> Void let editingOpenSetupLocation: () -> Void
let openPeerInfo: (Peer, Bool) -> Void let openPeerInfo: (Peer, Bool) -> Void
let performMemberAction: (PeerInfoMember, PeerInfoMemberAction) -> Void let performMemberAction: (PeerInfoMember, PeerInfoMemberAction) -> Void
let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode) -> Void let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void
let performBioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void let performBioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
let requestLayout: (Bool) -> Void let requestLayout: (Bool) -> Void
let openEncryptionKey: () -> Void let openEncryptionKey: () -> Void
@ -533,7 +533,7 @@ private final class PeerInfoInteraction {
editingOpenSetupLocation: @escaping () -> Void, editingOpenSetupLocation: @escaping () -> Void,
openPeerInfo: @escaping (Peer, Bool) -> Void, openPeerInfo: @escaping (Peer, Bool) -> Void,
performMemberAction: @escaping (PeerInfoMember, PeerInfoMemberAction) -> Void, performMemberAction: @escaping (PeerInfoMember, PeerInfoMemberAction) -> Void,
openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode) -> Void, openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void,
performBioLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, performBioLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void,
requestLayout: @escaping (Bool) -> Void, requestLayout: @escaping (Bool) -> Void,
openEncryptionKey: @escaping () -> Void, openEncryptionKey: @escaping () -> Void,
@ -934,9 +934,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
} }
let bioContextAction: (ASDisplayNode) -> Void = { sourceNode in let bioContextAction: (ASDisplayNode) -> Void = { sourceNode in
interaction.openPeerInfoContextMenu(.bio, sourceNode) interaction.openPeerInfoContextMenu(.bio, sourceNode, nil)
} }
let bioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void = { action, item in let bioLinkAction: (TextLinkItemActionType, TextLinkItem, ASDisplayNode, CGRect?) -> Void = { action, item, _, _ in
interaction.performBioLinkAction(action, item) interaction.performBioLinkAction(action, item)
} }
@ -974,8 +974,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
action: { _ in action: { _ in
interaction.openUsername(username) interaction.openUsername(username)
}, longTapAction: { sourceNode in }, longTapAction: { sourceNode in
interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode) interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode, nil)
}, linkItemAction: { type, item in }, linkItemAction: { type, item, _, _ in
if case .tap = type { if case .tap = type {
if case let .mention(username) = item { if case let .mention(username) = item {
interaction.openUsername(String(username[username.index(username.startIndex, offsetBy: 1)...])) interaction.openUsername(String(username[username.index(username.startIndex, offsetBy: 1)...]))
@ -1103,8 +1103,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
action: { _ in action: { _ in
interaction.openUsername(linkText) interaction.openUsername(linkText)
}, longTapAction: { sourceNode in }, longTapAction: { sourceNode in
interaction.openPeerInfoContextMenu(.link(customLink: linkText), sourceNode) interaction.openPeerInfoContextMenu(.link(customLink: linkText), sourceNode, nil)
}, linkItemAction: { type, item in }, linkItemAction: { type, item, _, _ in
if case .tap = type { if case .tap = type {
if case let .mention(username) = item { if case let .mention(username) = item {
interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1)))) interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1))))
@ -1153,12 +1153,16 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
action: { _ in action: { _ in
interaction.openUsername(username) interaction.openUsername(username)
}, longTapAction: { sourceNode in }, longTapAction: { sourceNode in
interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode) interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode, nil)
}, linkItemAction: { type, item in }, linkItemAction: { type, item, sourceNode, sourceRect in
if case .tap = type { if case .tap = type {
if case let .mention(username) = item { if case let .mention(username) = item {
interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1)))) interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1))))
} }
} else if case .longTap = type {
if case let .mention(username) = item {
interaction.openPeerInfoContextMenu(.link(customLink: username), sourceNode, sourceRect)
}
} }
}, iconAction: { }, iconAction: {
interaction.openQrCode() interaction.openQrCode()
@ -1987,8 +1991,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
performMemberAction: { [weak self] member, action in performMemberAction: { [weak self] member, action in
self?.performMemberAction(member: member, action: action) self?.performMemberAction(member: member, action: action)
}, },
openPeerInfoContextMenu: { [weak self] subject, sourceNode in openPeerInfoContextMenu: { [weak self] subject, sourceNode, sourceRect in
self?.openPeerInfoContextMenu(subject: subject, sourceNode: sourceNode) self?.openPeerInfoContextMenu(subject: subject, sourceNode: sourceNode, sourceRect: sourceRect)
}, },
performBioLinkAction: { [weak self] action, item in performBioLinkAction: { [weak self] action, item in
self?.performBioLinkAction(action: action, item: item) self?.performBioLinkAction(action: action, item: item)
@ -6092,7 +6096,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} }
} }
private func openPeerInfoContextMenu(subject: PeerInfoContextSubject, sourceNode: ASDisplayNode) { private func openPeerInfoContextMenu(subject: PeerInfoContextSubject, sourceNode: ASDisplayNode, sourceRect: CGRect?) {
guard let data = self.data, let peer = data.peer, let controller = self.controller else { guard let data = self.data, let peer = data.peer, let controller = self.controller else {
return return
} }
@ -6144,7 +6148,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let contextMenuController = ContextMenuController(actions: actions) let contextMenuController = ContextMenuController(actions: actions)
controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in
if let controller = self?.controller, let sourceNode = sourceNode { if let controller = self?.controller, let sourceNode = sourceNode {
return (sourceNode, sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0), controller.displayNode, controller.view.bounds) var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0)
if let sourceRect = sourceRect {
rect = sourceRect.insetBy(dx: 0.0, dy: 2.0)
}
return (sourceNode, rect, controller.displayNode, controller.view.bounds)
} else { } else {
return nil return nil
} }
@ -6160,7 +6168,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})]) })])
controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in
if let controller = self?.controller, let sourceNode = sourceNode { if let controller = self?.controller, let sourceNode = sourceNode {
return (sourceNode, sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0), controller.displayNode, controller.view.bounds) var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0)
if let sourceRect = sourceRect {
rect = sourceRect.insetBy(dx: 0.0, dy: 2.0)
}
return (sourceNode, rect, controller.displayNode, controller.view.bounds)
} else { } else {
return nil return nil
} }
@ -6192,7 +6204,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})]) })])
controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in
if let controller = self?.controller, let sourceNode = sourceNode { if let controller = self?.controller, let sourceNode = sourceNode {
return (sourceNode, sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0), controller.displayNode, controller.view.bounds) var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0)
if let sourceRect = sourceRect {
rect = sourceRect.insetBy(dx: 0.0, dy: 2.0)
}
return (sourceNode, rect, controller.displayNode, controller.view.bounds)
} else { } else {
return nil return nil
} }