diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0423fbe632..9f4554c312 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8089,10 +8089,26 @@ Sorry for the inconvenience."; "Group.Setup.LinkActive" = "active"; "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.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.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.LinksOrder" = "USERNAMES ORDER"; "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.ActivateAlertShow" = "Show"; -"Username.DeactivateAlertTitle" = "Deativate Username"; +"Username.DeactivateAlertTitle" = "Deactivate Username"; "Username.DeactivateAlertText" = "Do you want to hide this link from your info page?"; "Username.DeactivateAlertHide" = "Hide"; diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 925a64c66e..75e0de63d9 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2677,10 +2677,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.animateRevealOptionsFill { self.revealOptionsInteractivelyClosed() } - case RevealOptionKey.open.rawValue: - break - case RevealOptionKey.close.rawValue: - break default: break } @@ -2688,6 +2684,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { switch option.key { case RevealOptionKey.delete.rawValue: item.interaction.deletePeerThread(peerId, threadId) + case RevealOptionKey.open.rawValue: + break + case RevealOptionKey.close.rawValue: + break default: break } diff --git a/submodules/Display/Source/LinkHighlightingNode.swift b/submodules/Display/Source/LinkHighlightingNode.swift index ac51ca4afe..48f4a4d762 100644 --- a/submodules/Display/Source/LinkHighlightingNode.swift +++ b/submodules/Display/Source/LinkHighlightingNode.swift @@ -156,7 +156,7 @@ private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, } public final class LinkHighlightingNode: ASDisplayNode { - private var rects: [CGRect] = [] + public private(set) var rects: [CGRect] = [] public let imageNode: ASImageNode public var innerRadius: CGFloat = 4.0 diff --git a/submodules/SettingsUI/Sources/AdditionalLinkItem.swift b/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift similarity index 97% rename from submodules/SettingsUI/Sources/AdditionalLinkItem.swift rename to submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift index 87701e98a8..696f854345 100644 --- a/submodules/SettingsUI/Sources/AdditionalLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift @@ -44,7 +44,7 @@ public class AdditionalLinkItem: ListViewItem, ItemListItem { 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) 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) { Queue.mainQueue().async { - if let nodeValue = node() as? ItemListInviteLinkItemNode { + if let nodeValue = node() as? AdditionalLinkItemNode { let makeLayout = nodeValue.asyncLayout() 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 topStripeNode: 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)) - if strongSelf.reorderControlNode == nil { + if strongSelf.reorderControlNode == nil && item.username?.flags.contains(.isActive) == true { let reorderControlNode = reorderControlSizeAndApply.1(layout.contentSize.height, false, .immediate) strongSelf.reorderControlNode = reorderControlNode strongSelf.addSubnode(reorderControlNode) reorderControlNode.alpha = 0.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)) strongSelf.reorderControlNode?.frame = reorderControlFrame diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index c7cb1f99d1..ac0f168c09 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -39,8 +39,10 @@ private final class ChannelVisibilityControllerArguments { let toggleForwarding: (Bool) -> Void let updateJoinToSend: (CurrentChannelJoinToSend) -> 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.updateCurrentType = updateCurrentType self.updatePublicLinkText = updatePublicLinkText @@ -55,6 +57,8 @@ private final class ChannelVisibilityControllerArguments { self.toggleForwarding = toggleForwarding self.updateJoinToSend = updateJoinToSend self.toggleApproveMembers = toggleApproveMembers + self.activateLink = activateLink + self.deactivateLink = deactivateLink } } @@ -63,6 +67,7 @@ private enum ChannelVisibilitySection: Int32 { case limitInfo case link case linkActions + case additional case joinToSend case approveMembers case forwarding @@ -81,6 +86,11 @@ private enum ChannelVisibilityEntryTag: ItemListItemTag { } } +private enum ChannelVisibilityEntryId: Hashable { + case index(Int32) + case username(String) +} + private enum ChannelVisibilityEntry: ItemListNodeEntry { case typeHeader(PresentationTheme, String) case typePublic(PresentationTheme, String, Bool) @@ -103,6 +113,10 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case existingLinksInfo(PresentationTheme, String) 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 joinToSendEveryone(PresentationTheme, String, Bool) case joinToSendMembers(PresentationTheme, String, Bool) @@ -122,6 +136,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { return ChannelVisibilitySection.limitInfo.rawValue case .publicLinkHeader, .publicLinkAvailability, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus: return ChannelVisibilitySection.link.rawValue + case .additionalLinkHeader, .additionalLink, .additionalLinkInfo: + return ChannelVisibilitySection.additional.rawValue case .privateLinkManage, .privateLinkManageInfo: return ChannelVisibilitySection.linkActions.rawValue case .existingLinksInfo, .existingLinkPeerItem: @@ -135,58 +151,64 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { } } - var stableId: Int32 { + var stableId: ChannelVisibilityEntryId { switch self { case .typeHeader: - return 0 + return .index(0) case .typePublic: - return 1 + return .index(1) case .typePrivate: - return 2 + return .index(2) case .typeInfo: - return 3 + return .index(3) case .publicLinkHeader: - return 4 + return .index(4) case .publicLinkAvailability: - return 5 + return .index(5) case .linksLimitInfo: - return 6 + return .index(6) case .privateLinkHeader: - return 7 + return .index(7) case .privateLink: - return 8 + return .index(8) case .editablePublicLink: - return 9 + return .index(9) case .privateLinkInfo: - return 10 + return .index(10) case .publicLinkStatus: - return 11 + return .index(11) case .publicLinkInfo: - return 12 + return .index(12) case .existingLinksInfo: - return 13 + return .index(13) 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: - return 1000 + return .index(2001) case .privateLinkManageInfo: - return 1001 + return .index(2002) case .joinToSendHeader: - return 1002 + return .index(2003) case .joinToSendEveryone: - return 1003 + return .index(2004) case .joinToSendMembers: - return 1004 + return .index(2005) case .approveMembers: - return 1005 + return .index(2006) case .approveMembersInfo: - return 1006 + return .index(2007) case .forwardingHeader: - return 1007 + return .index(2008) case .forwardingDisabled: - return 1008 + return .index(2009) case .forwardingInfo: - return 1009 + return .index(2010) } } @@ -234,6 +256,24 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { } else { 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): if case let .privateLinkHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle { return true @@ -368,9 +408,208 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { } } } - + 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 { @@ -465,6 +704,20 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { }, removePeer: { peerId in 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): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) 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] = [] 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 { let count = Int32(publicChannelsToRevoke?.count ?? 0) @@ -821,6 +1076,33 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa } else { 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 { case .initialSetup, .revokeNames: break @@ -1110,6 +1392,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta })) var dismissImpl: (() -> Void)? + var dismissInputImpl: (() -> Void)? var nextImpl: (() -> Void)? var scrollToPublicLinkTextImpl: (() -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? @@ -1142,6 +1425,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta let toggleRequestToJoinDisposable = MetaDisposable() actionsDisposable.add(toggleRequestToJoinDisposable) + let temporaryOrder = Promise<[String]?>(nil) + let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in if type == .publicChannel { let _ = combineLatest( @@ -1363,6 +1648,63 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta updateState { state in 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) @@ -1370,6 +1712,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta let previousHadNamesToRevoke = Atomic(value: nil) let previousInvitation = Atomic(value: nil) + let previousUsernames = Atomic<[String]?>(value: nil) let mainLink = context.engine.data.subscribe( 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: true), TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) - ) + ), + temporaryOrder.get() ) |> 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 (limits, premiumLimits, accountPeer) = data @@ -1653,7 +1997,11 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta let hasNamesToRevoke = publicChannelsToRevoke != nil && !publicChannelsToRevoke!.isEmpty let hadNamesToRevoke = previousHadNamesToRevoke.swap(hasNamesToRevoke) + if let peer = view.peers[view.peerId] as? TelegramChannel { + let currentUsernames = peer.usernames.map { $0.username } + let previousUsernames = previousUsernames.swap(currentUsernames) + let selectedType: CurrentChannelType if case .privateLink = mode { selectedType = .privateChannel @@ -1678,6 +2026,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta if let hadNamesToRevoke = hadNamesToRevoke { animateChanges = hadNamesToRevoke != hasNamesToRevoke } + + if temporaryOrder != nil || previousUsernames != currentUsernames { + animateChanges = true + } } let title: String @@ -1694,7 +2046,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta 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? 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 dismissTooltipsImpl?() } + controller.setReorderEntry({ (fromIndex: Int, toIndex: Int, entries: [ChannelVisibilityEntry]) -> Signal 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 guard let controller = controller else { return @@ -1730,6 +2201,9 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta controller.dismiss() } } + dismissInputImpl = { [weak controller] in + controller?.view.endEditing(true) + } nextImpl = { [weak controller] in if let controller = controller { if case .initialSetup = mode { diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index d20e49df53..1af5c2a653 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -10,6 +10,7 @@ import PresentationDataUtils import AccountContext import ShareController import UndoUI +import InviteLinksUI private final class UsernameSetupControllerArguments { 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 { case publicLinkHeader(PresentationTheme, String) @@ -66,22 +71,22 @@ private enum UsernameSetupEntry: ItemListNodeEntry { } } - var stableId: Int32 { + var stableId: UsernameSetupEntryId { switch self { case .publicLinkHeader: - return 0 + return .index(0) case .editablePublicLink: - return 1 + return .index(1) case .publicLinkStatus: - return 2 + return .index(2) case .publicLinkInfo: - return 3 + return .index(3) case .additionalLinkHeader: - return 4 - case let .additionalLink(_, _, index): - return 5 + index + return .index(4) + case let .additionalLink(_, username, _): + return .username(username.username) case .additionalLinkInfo: - return 1000 + return .index(5) } } @@ -133,7 +138,57 @@ private enum UsernameSetupEntry: ItemListNodeEntry { } 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 { @@ -482,10 +537,32 @@ public func usernameSetupController(context: AccountContext) -> ViewController { 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, _): - referenceId = toUsername.username + if toUsername.flags.contains(.isActive) { + referenceId = toUsername.username + } else { + afterAll = true + } default: if entries[toIndex] < fromEntry { beforeAll = true @@ -497,16 +574,6 @@ public func usernameSetupController(context: AccountContext) -> ViewController { afterAll = true } - var currentUsernames: [String] = [] - for entry in entries { - switch entry { - case let .additionalLink(_, link, _): - currentUsernames.append(link.username) - default: - break - } - } - var previousIndex: Int? for i in 0 ..< currentUsernames.count { if currentUsernames[i] == fromUsername.username { @@ -517,7 +584,6 @@ public func usernameSetupController(context: AccountContext) -> ViewController { } var didReorder = false - if let referenceId = referenceId { var inserted = false for i in 0 ..< currentUsernames.count { @@ -535,18 +601,26 @@ public func usernameSetupController(context: AccountContext) -> ViewController { } if !inserted { didReorder = previousIndex != currentUsernames.count - currentUsernames.append(fromUsername.username) + 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 - currentUsernames.append(fromUsername.username) + 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?() diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift index 5ac2645ec5..edb124ef14 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift @@ -190,14 +190,40 @@ func _internal_toggleAddressNameActive(account: Account, domain: AddressNameDoma var updatedNames = peer.usernames if let index = updatedNames.firstIndex(where: { $0.username == name }) { var updatedFlags = updatedNames[index].flags + var updateOrder = true + var updatedIndex = index if active { + if updatedFlags.contains(.isActive) { + updateOrder = false + } updatedFlags.insert(.isActive) } else { + if !updatedFlags.contains(.isActive) { + updateOrder = false + } updatedFlags.remove(.isActive) } let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name) 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) updatePeers(transaction: transaction, peers: [updatedUser], update: { _, updated in @@ -218,14 +244,40 @@ func _internal_toggleAddressNameActive(account: Account, domain: AddressNameDoma var updatedNames = peer.usernames if let index = updatedNames.firstIndex(where: { $0.username == name }) { var updatedFlags = updatedNames[index].flags + var updateOrder = true + var updatedIndex = index if active { + if updatedFlags.contains(.isActive) { + updateOrder = false + } updatedFlags.insert(.isActive) } else { + if !updatedFlags.contains(.isActive) { + updateOrder = false + } updatedFlags.remove(.isActive) } let updatedName = TelegramPeerUsername(flags: updatedFlags, username: name) 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) 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 in switch domain { 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 return .generic } @@ -267,7 +319,7 @@ func _internal_reorderAddressNames(account: Account, domain: AddressNameDomain, } case let .peer(peerId): 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 return .generic } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index f12933e6d2..951535423a 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -300,6 +300,7 @@ swift_library( "//submodules/TelegramUI/Components/ForumTopicListScreen:ForumTopicListScreen", "//submodules/TelegramUI/Components/ForumCreateTopicScreen:ForumCreateTopicScreen", "//submodules/TelegramUI/Components/ChatTitleView", + "//submodules/InviteLinksUI:InviteLinksUI", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 9e9f524fb8..ef358ac316 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -3906,7 +3906,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode var animateReplyNodeIn = false if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) { 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)) self.swipeToReplyNode = swipeToReplyNode diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index d5f8797394..e0e22d2e4b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -998,7 +998,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD var animateReplyNodeIn = false if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) { 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)) self.swipeToReplyNode = swipeToReplyNode diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 6c3afc923d..eb095d63b6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -1250,7 +1250,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { var animateReplyNodeIn = false if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) { 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)) self.swipeToReplyNode = swipeToReplyNode diff --git a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift index afebcc3bab..a57b097912 100644 --- a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift @@ -578,7 +578,7 @@ final class ChatQrCodeScreen: ViewController { } else { result = "t_me-\(Int32.random(in: 0 ..< Int32.max))" } - if let threadId = threadId { + if let threadId = threadId, threadId != 0 { result.append("-\(threadId)") } return result @@ -1519,7 +1519,7 @@ private class QrContentNode: ASDisplayNode, ContentNode { } else { codeText = peer.debugDisplayTitle.uppercased() } - if let threadId = self.threadId { + if let threadId = self.threadId, threadId != 0 { codeText += "/\(threadId)" } diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift index 9dfb49fd67..9e21e6e6b4 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -32,7 +32,7 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { let icon: PeerInfoScreenLabeledValueIcon? let action: ((ASDisplayNode) -> Void)? let longTapAction: ((ASDisplayNode) -> Void)? - let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? + let linkItemAction: ((TextLinkItemActionType, TextLinkItem, ASDisplayNode, CGRect?) -> Void)? let iconAction: (() -> Void)? let contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? let requestLayout: () -> Void @@ -47,7 +47,7 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { icon: PeerInfoScreenLabeledValueIcon? = nil, action: ((ASDisplayNode) -> Void)?, longTapAction: ((ASDisplayNode) -> Void)? = nil, - linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, + linkItemAction: ((TextLinkItemActionType, TextLinkItem, ASDisplayNode, CGRect?) -> Void)? = nil, iconAction: (() -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, requestLayout: @escaping () -> Void @@ -313,7 +313,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { case .tap, .longTap: if let item = self.item { 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 { item.longTapAction?(self) } else if case .tap = gesture { @@ -601,7 +601,8 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { } if textNode == nil { 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] = [ TelegramTextAttributes.URL, TelegramTextAttributes.PeerMention, diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index f7185661c3..ba9fe57488 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -488,7 +488,7 @@ private final class PeerInfoInteraction { let editingOpenSetupLocation: () -> Void let openPeerInfo: (Peer, Bool) -> Void let performMemberAction: (PeerInfoMember, PeerInfoMemberAction) -> Void - let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode) -> Void + let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void let performBioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void let requestLayout: (Bool) -> Void let openEncryptionKey: () -> Void @@ -533,7 +533,7 @@ private final class PeerInfoInteraction { editingOpenSetupLocation: @escaping () -> Void, openPeerInfo: @escaping (Peer, Bool) -> Void, performMemberAction: @escaping (PeerInfoMember, PeerInfoMemberAction) -> Void, - openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode) -> Void, + openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void, performBioLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, requestLayout: @escaping (Bool) -> Void, openEncryptionKey: @escaping () -> Void, @@ -934,9 +934,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese } 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) } @@ -974,8 +974,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese action: { _ in interaction.openUsername(username) }, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode) - }, linkItemAction: { type, item in + interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode, nil) + }, linkItemAction: { type, item, _, _ in if case .tap = type { if case let .mention(username) = item { interaction.openUsername(String(username[username.index(username.startIndex, offsetBy: 1)...])) @@ -1103,8 +1103,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese action: { _ in interaction.openUsername(linkText) }, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.link(customLink: linkText), sourceNode) - }, linkItemAction: { type, item in + interaction.openPeerInfoContextMenu(.link(customLink: linkText), sourceNode, nil) + }, linkItemAction: { type, item, _, _ in if case .tap = type { if case let .mention(username) = item { 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 interaction.openUsername(username) }, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode) - }, linkItemAction: { type, item in + interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode, nil) + }, linkItemAction: { type, item, sourceNode, sourceRect in if case .tap = type { if case let .mention(username) = item { 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: { interaction.openQrCode() @@ -1987,8 +1991,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate performMemberAction: { [weak self] member, action in self?.performMemberAction(member: member, action: action) }, - openPeerInfoContextMenu: { [weak self] subject, sourceNode in - self?.openPeerInfoContextMenu(subject: subject, sourceNode: sourceNode) + openPeerInfoContextMenu: { [weak self] subject, sourceNode, sourceRect in + self?.openPeerInfoContextMenu(subject: subject, sourceNode: sourceNode, sourceRect: sourceRect) }, performBioLinkAction: { [weak self] action, item in 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 { return } @@ -6144,7 +6148,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let contextMenuController = ContextMenuController(actions: actions) controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in 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 { 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 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 { 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 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 { return nil }