diff --git a/Swiftgram/SGStrings/Strings/en.lproj/SGLocalizable.strings b/Swiftgram/SGStrings/Strings/en.lproj/SGLocalizable.strings index f37c56ea3f..99e9528f87 100644 --- a/Swiftgram/SGStrings/Strings/en.lproj/SGLocalizable.strings +++ b/Swiftgram/SGStrings/Strings/en.lproj/SGLocalizable.strings @@ -121,13 +121,21 @@ "Settings.DefaultEmojisFirst" = "Standard emojis first"; "Settings.DefaultEmojisFirst.Notice" = "Show standard emojis before premium in emoji keyboard"; -/* Date when chat was created. "created: 24 May 2016" */ -"Chat.Created" = "created: %@"; - +"Chat.Created" = "Created"; +"Chat.Created.Copy" = "Copy Creation Date"; +"Chat.Created.Copied" = "Creation Date copied to clipboard."; /* Date when user joined the chat. "Joined Swiftgram Chat" */ "Chat.JoinedDateTitle" = "Joined %@"; +"Chat.JoinedDate.Copy" = "Copy Joined Date"; +"Chat.JoinedDate.Copied" = "Joined Date copied to clipboard."; /* Date when user registered in Telegram. Will be shown like "Registered\n24 May 2016" */ "Chat.RegDate" = "Registered"; +"Chat.RegDate.Copy" = "Copy Registration Date"; +"Chat.RegDate.Copied" = "Registration Date copied to clipboard."; + +"Chat.ID" = "id"; +"Chat.ID.Copy" = "Copy ID"; +"Chat.ID.Copied" = "ID copied to clipboard."; "Settings.messageDoubleTapActionOutgoingEdit" = "Double-tap to edit message"; diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 7c88e04bdb..4ed9501b34 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -498,7 +498,6 @@ private enum PeerInfoMemberAction { private enum PeerInfoContextSubject { case copy(String) - case aboutDC case bio case phone(String) case link(customLink: String?) @@ -556,6 +555,11 @@ private enum TopicsLimitedReason { case discussion } +private enum SGContextMenuAction { + case copy(text: String, copyKey: String, copiedKey: String) + case openURL(url: String) +} + private final class PeerInfoInteraction { let notifyTextCopied: () -> Void let openChat: (EnginePeer.Id?) -> Void @@ -624,6 +628,7 @@ private final class PeerInfoInteraction { let openWorkingHoursContextMenu: (ASDisplayNode, ContextGesture?) -> Void let openBusinessLocationContextMenu: (ASDisplayNode, ContextGesture?) -> Void let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void + let openSgContextMenu: (ASDisplayNode, ContextGesture?, SGContextMenuAction) -> Void let editingOpenAffiliateProgram: () -> Void let editingOpenVerifyAccounts: () -> Void let editingToggleAutoTranslate: (Bool) -> Void @@ -698,6 +703,7 @@ private final class PeerInfoInteraction { openWorkingHoursContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, openBusinessLocationContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void, + openSgContextMenu: @escaping (ASDisplayNode, ContextGesture?, SGContextMenuAction) -> Void, editingOpenAffiliateProgram: @escaping () -> Void, editingOpenVerifyAccounts: @escaping () -> Void, editingToggleAutoTranslate: @escaping (Bool) -> Void, @@ -771,6 +777,7 @@ private final class PeerInfoInteraction { self.openWorkingHoursContextMenu = openWorkingHoursContextMenu self.openBusinessLocationContextMenu = openBusinessLocationContextMenu self.openBirthdayContextMenu = openBirthdayContextMenu + self.openSgContextMenu = openSgContextMenu self.editingOpenAffiliateProgram = editingOpenAffiliateProgram self.editingOpenVerifyAccounts = editingOpenVerifyAccounts self.editingToggleAutoTranslate = editingToggleAutoTranslate @@ -1357,6 +1364,9 @@ private func infoItems(nearestChatParticipant: (String?, Int32?), showProfileId: let birthdayContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in interaction.openBirthdayContextMenu(node, gesture) } + let openSgContextMenu: (ASDisplayNode, ContextGesture?, SGContextMenuAction) -> Void = { node, gesture, action in + interaction.openSgContextMenu(node, gesture, action) + } if let user = data.peer as? TelegramUser { // MARK: Swiftgram @@ -2105,11 +2115,11 @@ private func infoItems(nearestChatParticipant: (String?, Int32?), showProfileId: // MARK: Swiftgram if showProfileId { - items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, label: "id: \(idText)", text: "", textColor: .primary, action: nil, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.copy(idText), sourceNode, nil) - }, requestLayout: { _ in - interaction.requestLayout(false) - })) + items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, context: context, label: i18n("Chat.ID", presentationData.strings.baseLanguageCode), text: idText, textColor: .primary, leftIcon: nil, icon: nil, action: { node, _ in + openSgContextMenu(node, nil, .copy(text: idText, copyKey: "Chat.ID.Copy", copiedKey: "Chat.ID.Copied")) + }, longTapAction: nil, iconAction: nil, contextAction: { node, gesture, _ in + openSgContextMenu(node, gesture, .copy(text: idText, copyKey: "Chat.ID.Copy", copiedKey: "Chat.ID.Copied")) + }, requestLayout: { _ in })) sgItemId += 1 } @@ -2166,12 +2176,12 @@ private func infoItems(nearestChatParticipant: (String?, Int32?), showProfileId: dcText = phoneCountryText } - if !dcText.isEmpty || !dcLabel.isEmpty { - items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, label: dcLabel, text: dcText, textColor: .primary, action: nil, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.aboutDC, sourceNode, nil) - }, requestLayout: { _ in - interaction.requestLayout(false) - })) + if !dcLabel.isEmpty { + items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, context: context, label: dcLabel, text: dcText, textColor: .primary, leftIcon: nil, icon: nil, action: { node, _ in + openSgContextMenu(node, nil, .openURL(url: "https://core.telegram.org/api/datacenter")) + }, longTapAction: nil, iconAction: nil, contextAction: { node, gesture, _ in + openSgContextMenu(node, gesture, .openURL(url: "https://core.telegram.org/api/datacenter")) + }, requestLayout: { _ in })) sgItemId += 1 } } @@ -2179,22 +2189,22 @@ private func infoItems(nearestChatParticipant: (String?, Int32?), showProfileId: if SGSimpleSettings.shared.showCreationDate { if let channelCreationTimestamp = data.channelCreationTimestamp { let creationDateString = stringForDate(timestamp: channelCreationTimestamp, strings: presentationData.strings) - items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, label: i18n("Chat.Created", presentationData.strings.baseLanguageCode, creationDateString), text: "", action: nil, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.copy(creationDateString), sourceNode, nil) - }, requestLayout: { _ in - interaction.requestLayout(false) - })) + items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, context: context, label: i18n("Chat.Created", presentationData.strings.baseLanguageCode), text: creationDateString, textColor: .primary, leftIcon: nil, icon: nil, action: { node, _ in + openSgContextMenu(node, nil, .copy(text: creationDateString, copyKey: "Chat.Created.Copy", copiedKey: "Chat.Created.Copied")) + }, longTapAction: nil, iconAction: nil, contextAction: { node, gesture, _ in + openSgContextMenu(node, nil, .copy(text: creationDateString, copyKey: "Chat.Created.Copy", copiedKey: "Chat.Created.Copied")) + }, requestLayout: { _ in })) sgItemId += 1 } } if let invitedAt = nearestChatParticipant.1 { let joinedDateString = stringForDate(timestamp: invitedAt, strings: presentationData.strings) - items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, label: i18n("Chat.JoinedDateTitle", presentationData.strings.baseLanguageCode, nearestChatParticipant.0 ?? "chat") , text: joinedDateString, action: nil, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.copy(joinedDateString), sourceNode, nil) - }, requestLayout: { _ in - interaction.requestLayout(false) - })) + items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, context: context, label: i18n("Chat.JoinedDateTitle", presentationData.strings.baseLanguageCode, nearestChatParticipant.0 ?? "chat"), text: joinedDateString, textColor: .primary, leftIcon: nil, icon: nil, action: { node, _ in + openSgContextMenu(node, nil, .copy(text: joinedDateString, copyKey: "Chat.JoinedDate.Copy", copiedKey: "Chat.JoinedDate.Copied")) + }, longTapAction: nil, iconAction: nil, contextAction: { node, gesture, _ in + openSgContextMenu(node, nil, .copy(text: joinedDateString, copyKey: "Chat.JoinedDate.Copy", copiedKey: "Chat.JoinedDate.Copied")) + }, requestLayout: { _ in })) sgItemId += 1 } @@ -2220,11 +2230,11 @@ private func infoItems(nearestChatParticipant: (String?, Int32?), showProfileId: } } if !regDateString.isEmpty { - items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, label: i18n("Chat.RegDate", presentationData.strings.baseLanguageCode), text: regDateString, action: nil, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.copy(regDateString), sourceNode, nil) - }, requestLayout: { _ in - interaction.requestLayout(false) - })) + items[.swiftgram]!.append(PeerInfoScreenLabeledValueItem(id: sgItemId, context: context, label: i18n("Chat.RegDate", presentationData.strings.baseLanguageCode), text: regDateString, textColor: .primary, leftIcon: nil, icon: nil, action: { node, _ in + openSgContextMenu(node, nil, .copy(text: regDateString, copyKey: "Chat.RegDate.Copy", copiedKey: "Chat.RegDate.Copied")) + }, longTapAction: nil, iconAction: nil, contextAction: { node, gesture, _ in + openSgContextMenu(node, nil, .copy(text: regDateString, copyKey: "Chat.RegDate.Copy", copiedKey: "Chat.RegDate.Copied")) + }, requestLayout: { _ in })) sgItemId += 1 } } @@ -3457,7 +3467,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } self.openBirthdayContextMenu(node: node, gesture: gesture) - }, editingOpenAffiliateProgram: { [weak self] in + }, openSgContextMenu: { [weak self] node, gesture, action in + guard let self else { + return + } + self.openSgContextMenu(node: node, gesture: gesture, action: action) + }, editingOpenAffiliateProgram: { [weak self] in guard let self else { return } @@ -8232,6 +8247,44 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.present(contextController, in: .window(.root)) } + private func openSgContextMenu(node: ASDisplayNode, gesture: ContextGesture?, action: SGContextMenuAction) { + guard let sourceNode = node as? ContextExtractedContentContainingNode else { return } + var items: [ContextMenuItem] = [] + switch action { + case let .copy(text, copyKey, copiedKey): + items.append(.action(ContextMenuActionItem(text: i18n(copyKey, self.presentationData.strings.baseLanguageCode), icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { [weak self] in + guard let self else { return } + UIPasteboard.general.string = text + self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: i18n(copiedKey, self.presentationData.strings.baseLanguageCode)), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + }))) + case let .openURL(url): + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Passport_InfoLearnMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c?.dismiss { [weak self] in + self?.openUrl(url: url, concealed: false, external: false) + } + }))) + } + let actions = ContextController.Items(content: .list(items)) + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + self.controller?.present(contextController, in: .window(.root)) + } + + private func openDcContextMenu(node: ASDisplayNode, gesture: ContextGesture?) { + guard let sourceNode = node as? ContextExtractedContentContainingNode else { return } + let learnMoreAction = { [weak self] in + self?.openUrl(url: "https://core.telegram.org/api/datacenter", concealed: false, external: false) + } + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Passport_InfoLearnMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { c, _ in + c?.dismiss { learnMoreAction() } + }))) + let actions = ContextController.Items(content: .list(items)) + let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture) + self.controller?.present(contextController, in: .window(.root)) + } + private func openPhone(value: String, node: ASDisplayNode, gesture: ContextGesture?, progress: Promise?) { guard let sourceNode = node as? ContextExtractedContentContainingNode else { return @@ -9835,22 +9888,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return nil } })) - case .aboutDC: - let contextMenuController = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Passport_InfoLearnMore, accessibilityLabel: self.presentationData.strings.Passport_InfoLearnMore), action: { [weak self] in - self?.openUrl(url: "https://core.telegram.org/api/datacenter", concealed: false, external: false) - - })]) - controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in - if let controller = self?.controller, let sourceNode = sourceNode { - 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 - } - })) case .bio: var text: String?