diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index fd056f9e9a..eb673c232a 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12190,3 +12190,19 @@ Sorry for the inconvenience."; "EmojiPacks.UnarchiveEmojiPacksConfirmation_1" = "Unarchive %@ Pack"; "EmojiPacks.UnarchiveEmojiPacksConfirmation_any" = "Unarchive %@ Packs"; + +"HashtagSearch.ThisChat" = "This Chat"; +"HashtagSearch.MyMessages" = "My Messages"; +"HashtagSearch.PublicPosts" = "Public Posts"; + +"Chat.Context.Phone.AddToContacts" = "Add to Contacts"; +"Chat.Context.Phone.CreateNewContact" = "Create New Contact"; +"Chat.Context.Phone.AddToExistingContact" = "Add to Existing Contact"; +"Chat.Context.Phone.SendMessage" = "Send Message"; +"Chat.Context.Phone.TelegramVoiceCall" = "Telegram Voice Call"; +"Chat.Context.Phone.TelegramVideoCall" = "Telegram Video Call"; +"Chat.Context.Phone.InviteToTelegram" = "Invite to Telegram"; +"Chat.Context.Phone.CallViaCarrier" = "Call via Carrier"; +"Chat.Context.Phone.CopyNumber" = "Copy Number"; +"Chat.Context.Phone.NotOnTelegram" = "This number is not on Telegram."; +"Chat.Context.Phone.ViewProfile" = "View Profile"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 20e4a3a547..fa00b5b6ae 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -932,7 +932,7 @@ public protocol SharedAccountContext: AnyObject { func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController? func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController? - func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController + func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController func makePeersNearbyController(context: AccountContext) -> ViewController func makeComposeController(context: AccountContext) -> ViewController func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController @@ -1224,3 +1224,28 @@ public struct StickersSearchConfiguration { } } } + +public protocol ShareControllerAccountContext: AnyObject { + var accountId: AccountRecordId { get } + var accountPeerId: EnginePeer.Id { get } + var stateManager: AccountStateManager { get } + var engineData: TelegramEngine.EngineData { get } + var animationCache: AnimationCache { get } + var animationRenderer: MultiAnimationRenderer { get } + var contentSettings: ContentSettings { get } + var appConfiguration: AppConfiguration { get } + + func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> +} + +public protocol ShareControllerEnvironment: AnyObject { + var presentationData: PresentationData { get } + var updatedPresentationData: Signal { get } + var isMainApp: Bool { get } + var energyUsageSettings: EnergyUsageSettings { get } + + var mediaManager: MediaManager? { get } + + func setAccountUserInterfaceInUse(id: AccountRecordId) -> Disposable + func donateSendMessageIntent(account: ShareControllerAccountContext, peerIds: [EnginePeer.Id]) +} diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 780cd01564..ebbf1c9d50 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -971,6 +971,7 @@ public protocol ChatController: ViewController { var chatLocation: ChatLocation { get } var canReadHistory: ValuePromise { get } var parentController: ViewController? { get set } + var customNavigationController: NavigationController? { get set } var purposefulAction: (() -> Void)? { get set } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index 990bda668d..3e0c29edef 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift @@ -28,13 +28,22 @@ public final class BotCheckoutController: ViewController { } public static func fetch(context: AccountContext, source: BotPaymentInvoiceSource) -> Signal { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let theme = context.sharedContext.currentPresentationData.with { $0 }.theme let themeParams: [String: Any] = [ - "bg_color": Int32(bitPattern: presentationData.theme.list.plainBackgroundColor.argb), - "text_color": Int32(bitPattern: presentationData.theme.list.itemPrimaryTextColor.argb), - "link_color": Int32(bitPattern: presentationData.theme.list.itemAccentColor.argb), - "button_color": Int32(bitPattern: presentationData.theme.list.itemCheckColors.fillColor.argb), - "button_text_color": Int32(bitPattern: presentationData.theme.list.itemCheckColors.foregroundColor.argb) + "bg_color": Int32(bitPattern: theme.list.plainBackgroundColor.rgb), + "secondary_bg_color": Int32(bitPattern: theme.list.blocksBackgroundColor.rgb), + "text_color": Int32(bitPattern: theme.list.itemPrimaryTextColor.rgb), + "hint_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "link_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "button_color": Int32(bitPattern: theme.list.itemCheckColors.fillColor.rgb), + "button_text_color": Int32(bitPattern: theme.list.itemCheckColors.foregroundColor.rgb), + "header_bg_color": Int32(bitPattern: theme.rootController.navigationBar.opaqueBackgroundColor.rgb), + "accent_text_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "section_bg_color": Int32(bitPattern: theme.list.itemBlocksBackgroundColor.rgb), + "section_header_text_color": Int32(bitPattern: theme.list.freeTextColor.rgb), + "subtitle_text_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "destructive_text_color": Int32(bitPattern: theme.list.itemDestructiveColor.rgb), + "section_separator_color": Int32(bitPattern: theme.list.itemBlocksSeparatorColor.rgb) ] return context.engine.payments.fetchBotPaymentForm(source: source, themeParams: themeParams) diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index f8dc911f7b..1204cd5b39 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -23,6 +23,7 @@ import StoryContainerScreen import ChatListHeaderComponent import TelegramIntents import UndoUI +import ShareController private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController @@ -309,7 +310,7 @@ public class ContactsController: ViewController { guard let strongSelf = self, let value = value else { return } - (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .vcard(nil, id, value), completed: nil, cancelled: nil), completion: { [weak self] in + (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, id, value), completed: nil, cancelled: nil), completion: { [weak self] in if let strongSelf = self { strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) } @@ -741,7 +742,7 @@ public class ContactsController: ViewController { case .allowed: let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in guard let strongSelf = self else { return } @@ -755,7 +756,7 @@ public class ContactsController: ViewController { } } else { if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) + navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) } } }), completed: nil, cancelled: nil)) diff --git a/submodules/ContextUI/Sources/ContextActionNode.swift b/submodules/ContextUI/Sources/ContextActionNode.swift index 4aa8dd42fe..e0ab204190 100644 --- a/submodules/ContextUI/Sources/ContextActionNode.swift +++ b/submodules/ContextUI/Sources/ContextActionNode.swift @@ -121,6 +121,19 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol { statusNode.attributedText = NSAttributedString(string: value, font: subtitleFont, textColor: presentationData.theme.contextMenu.secondaryColor) statusNode.maximumNumberOfLines = 1 self.statusNode = statusNode + case let .secondLineWithAttributedValue(value): + self.textNode.maximumNumberOfLines = 1 + let statusNode = ImmediateTextNode() + statusNode.isAccessibilityElement = false + statusNode.isUserInteractionEnabled = false + statusNode.displaysAsynchronously = false + + let mutableString = value.mutableCopy() as! NSMutableAttributedString + mutableString.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.secondaryColor, range: NSRange(location: 0, length: mutableString.length)) + mutableString.addAttribute(.font, value: subtitleFont, range: NSRange(location: 0, length: mutableString.length)) + statusNode.attributedText = mutableString + statusNode.maximumNumberOfLines = 1 + self.statusNode = statusNode case .multiline: self.textNode.maximumNumberOfLines = 0 self.statusNode = nil @@ -350,10 +363,15 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol { self.textNode.attributedText = NSAttributedString(string: self.action.text, font: titleFont, textColor: textColor) + let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0) switch self.action.textLayout { case let .secondLineWithValue(value): - let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0) self.statusNode?.attributedText = NSAttributedString(string: value, font: subtitleFont, textColor: presentationData.theme.contextMenu.secondaryColor) + case let .secondLineWithAttributedValue(value): + let mutableString = value.mutableCopy() as! NSMutableAttributedString + mutableString.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.secondaryColor, range: NSRange(location: 0, length: mutableString.length)) + mutableString.addAttribute(.font, value: subtitleFont, range: NSRange(location: 0, length: mutableString.length)) + self.statusNode?.attributedText = mutableString default: break } diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index ea6c74a2fc..acf2dcbb5f 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -36,6 +36,7 @@ public enum ContextMenuActionItemTextLayout { case singleLine case twoLinesMax case secondLineWithValue(String) + case secondLineWithAttributedValue(NSAttributedString) case multiline } @@ -2150,6 +2151,7 @@ public protocol ContextExtractedContentSource: AnyObject { var initialAppearanceOffset: CGPoint { get } var centerVertically: Bool { get } var keepInPlace: Bool { get } + var adjustContentHorizontally: Bool { get } var adjustContentForSideInset: Bool { get } var ignoreContentTouches: Bool { get } var blurBackground: Bool { get } @@ -2170,6 +2172,10 @@ public extension ContextExtractedContentSource { return false } + var adjustContentHorizontally: Bool { + return false + } + var adjustContentForSideInset: Bool { return false } diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 7042758bc9..449b9e1f6e 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -228,21 +228,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking let iconSpacing: CGFloat = 8.0 self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor - - var subtitle: String? - switch self.item.textLayout { - case .singleLine: - self.titleLabelNode.maximumNumberOfLines = 1 - case .twoLinesMax: - self.titleLabelNode.maximumNumberOfLines = 2 - case let .secondLineWithValue(subtitleValue): - self.titleLabelNode.maximumNumberOfLines = 1 - subtitle = subtitleValue - case .multiline: - self.titleLabelNode.maximumNumberOfLines = 0 - self.titleLabelNode.lineSpacing = 0.1 - } - + var forcedHeight: CGFloat? var titleVerticalOffset: CGFloat? let titleFont: UIFont @@ -265,6 +251,30 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) let subtitleColor = presentationData.theme.contextMenu.secondaryColor + var subtitle: NSAttributedString? + switch self.item.textLayout { + case .singleLine: + self.titleLabelNode.maximumNumberOfLines = 1 + case .twoLinesMax: + self.titleLabelNode.maximumNumberOfLines = 2 + case let .secondLineWithValue(subtitleValue): + self.titleLabelNode.maximumNumberOfLines = 1 + subtitle = NSAttributedString( + string: subtitleValue, + font: subtitleFont, + textColor: subtitleColor + ) + case let .secondLineWithAttributedValue(subtitleValue): + self.titleLabelNode.maximumNumberOfLines = 1 + let mutableString = subtitleValue.mutableCopy() as! NSMutableAttributedString + mutableString.addAttribute(.foregroundColor, value: subtitleColor, range: NSRange(location: 0, length: mutableString.length)) + mutableString.addAttribute(.font, value: subtitleFont, range: NSRange(location: 0, length: mutableString.length)) + subtitle = mutableString + case .multiline: + self.titleLabelNode.maximumNumberOfLines = 0 + self.titleLabelNode.lineSpacing = 0.1 + } + let titleColor: UIColor switch self.item.textColor { case .primary: @@ -308,13 +318,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking self.titleLabelNode.isUserInteractionEnabled = self.titleLabelNode.tapAttributeAction != nil && self.item.action == nil - self.subtitleNode.attributedText = subtitle.flatMap { subtitle in - return NSAttributedString( - string: subtitle, - font: subtitleFont, - textColor: subtitleColor - ) - } + self.subtitleNode.attributedText = subtitle var iconSize: CGSize? if let iconSource = self.item.iconSource { diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index f889644ca3..e1d9da2667 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -1064,6 +1064,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let contentNode = itemContentNode { var contentFrame = CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentNode.containingItem.view.bounds.size) if case let .extracted(extracted) = self.source { + if extracted.adjustContentHorizontally { + contentFrame.origin.x = combinedActionsFrame.minX + } if extracted.centerVertically { if combinedActionsFrame.height.isZero { contentFrame.origin.y = floorToScreenPixels((layout.size.height - contentFrame.height) / 2.0) @@ -1160,7 +1163,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX let contentWidth = contentNode.containingItem.view.bounds.size.width let contentHeight = contentNode.containingItem.view.bounds.size.height - if case let .extracted(extracted) = self.source, extracted.centerVertically { + if case let .extracted(extracted) = self.source, extracted.adjustContentHorizontally { + let fixedContentX = self.actionsContainerNode.frame.minX + animationInContentXDistance = fixedContentX - contentX + } else if case let .extracted(extracted) = self.source, extracted.centerVertically { if actionsSize.height.isZero { var initialContentRect = contentRect initialContentRect.origin.y += extracted.initialAppearanceOffset.y @@ -1429,7 +1435,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo var animationInContentXDistance: CGFloat = 0.0 let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX let contentWidth = contentNode.containingItem.view.bounds.size.width - if case let .extracted(extracted) = self.source, extracted.centerVertically { + if case let .extracted(extracted) = self.source, extracted.adjustContentHorizontally { + let fixedContentX = self.actionsContainerNode.frame.minX + animationInContentXDistance = contentX - fixedContentX + } else if case let .extracted(extracted) = self.source, extracted.centerVertically { if actionsSize.height.isZero { // let fixedContentY = floorToScreenPixels((layout.size.height - contentHeight) / 2.0) animationInContentYDistance = 0.0 //contentY - fixedContentY diff --git a/submodules/Display/Source/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift index bc47c4107c..bc7206ceeb 100644 --- a/submodules/Display/Source/GenerateImage.swift +++ b/submodules/Display/Source/GenerateImage.swift @@ -330,7 +330,7 @@ public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor return tintedImage } -public func generateGradientTintedImage(image: UIImage?, colors: [UIColor]) -> UIImage? { +public func generateGradientTintedImage(image: UIImage?, colors: [UIColor], direction: GradientImageDirection = .vertical) -> UIImage? { guard let image = image else { return nil } @@ -357,7 +357,21 @@ public func generateGradientTintedImage(image: UIImage?, colors: [UIColor]) -> U let colorSpace = DeviceGraphicsContextSettings.shared.colorSpace let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: imageRect.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions()) + let start: CGPoint + let end: CGPoint + switch direction { + case .horizontal: + start = .zero + end = CGPoint(x: imageRect.width, y: 0.0) + case .vertical: + start = CGPoint(x: 0.0, y: imageRect.height) + end = .zero + case .diagonal: + start = CGPoint(x: 0.0, y: 0.0) + end = CGPoint(x: imageRect.width, y: imageRect.height) + } + + context.drawLinearGradient(gradient, start: start, end: end, options: CGGradientDrawingOptions()) } else if !colors.isEmpty { context.setFillColor(colors[0].cgColor) context.fill(imageRect) diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift index e2c9d4e880..0299a883cf 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift @@ -1327,8 +1327,6 @@ private extension UIBezierPath { } } - - extension UIImageView { func setDrawingAnimatedImage(data: Data) { DispatchQueue.global().async { @@ -1354,48 +1352,3 @@ extension UIImageView { self.animationRepeatCount = 0 } } - -//private func prerenderEntityTransformations(entity: DrawingEntity, image: UIImage, colorSpace: CGColorSpace) -> UIImage { -// let imageSize = image.size -// -// let angle: CGFloat -// var scale: CGFloat -// let position: CGPoint -// -// if let entity = entity as? DrawingStickerEntity { -// angle = -entity.rotation -// scale = entity.scale -// position = entity.position -// } else { -// fatalError() -// } -// -// let rotatedSize = CGSize( -// width: abs(imageSize.width * cos(angle)) + abs(imageSize.height * sin(angle)), -// height: abs(imageSize.width * sin(angle)) + abs(imageSize.height * cos(angle)) -// ) -// let newSize = CGSize(width: rotatedSize.width * scale, height: rotatedSize.height * scale) -// -// let newImage = generateImage(newSize, contextGenerator: { size, context in -// context.setAllowsAntialiasing(true) -// context.setShouldAntialias(true) -// context.interpolationQuality = .high -// context.clear(CGRect(origin: .zero, size: size)) -// context.translateBy(x: newSize.width * 0.5, y: newSize.height * 0.5) -// context.rotate(by: angle) -// context.scaleBy(x: scale, y: scale) -// let drawRect = CGRect( -// x: -imageSize.width * 0.5, -// y: -imageSize.height * 0.5, -// width: imageSize.width, -// height: imageSize.height -// ) -// if let cgImage = image.cgImage { -// context.draw(cgImage, in: drawRect) -// } -// }, scale: 1.0)! -// -// let _ = position -// -// return newImage -//} diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift index 9dbb573c81..539b78af18 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift @@ -1995,7 +1995,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, ASS let changeEmailActionButtonFrame: CGRect let resendCodeActionFrame: CGRect let resendCodeActionButtonFrame: CGRect - if changeEmailActionSize.width + resendCodeActionSize.width > layout.size.width - buttonFrame.minX * 2.0 { + if changeEmailActionSize.width + resendCodeActionSize.width > layout.size.width - buttonFrame.minX * 2.0 - 32.0 { changeEmailActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.minY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height)) changeEmailActionFrame = CGRect(origin: CGPoint(x: changeEmailActionButtonFrame.minX + floor((changeEmailActionButtonFrame.width - changeEmailActionSize.width) / 2.0), y: changeEmailActionButtonFrame.minY + floor((changeEmailActionButtonFrame.height - changeEmailActionSize.height) / 2.0)), size: changeEmailActionSize) resendCodeActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.maxY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height)) diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index 67827a24f1..67292f2528 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -24,6 +24,8 @@ import UndoUI import GalleryUI import PeerAvatarGalleryUI import Postbox +import ShareController +import ContextUI private enum DeviceContactInfoAction { case sendMessage @@ -33,7 +35,7 @@ private enum DeviceContactInfoAction { } private final class DeviceContactInfoControllerArguments { - let context: AccountContext + let accountContext: AccountContext? let isPlain: Bool let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void let updatePhone: (Int64, String) -> Void @@ -50,8 +52,8 @@ private final class DeviceContactInfoControllerArguments { let updateShareViaException: (Bool) -> Void let openAvatar: (EnginePeer) -> Void - init(context: AccountContext, isPlain: Bool, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void, displayCopyContextMenu: @escaping (DeviceContactInfoEntryTag, String) -> Void, updateShareViaException: @escaping (Bool) -> Void, openAvatar: @escaping (EnginePeer) -> Void) { - self.context = context + init(accountContext: AccountContext?, isPlain: Bool, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void, displayCopyContextMenu: @escaping (DeviceContactInfoEntryTag, String) -> Void, updateShareViaException: @escaping (Bool) -> Void, openAvatar: @escaping (EnginePeer) -> Void) { + self.accountContext = accountContext self.isPlain = isPlain self.updateEditingName = updateEditingName self.updatePhone = updatePhone @@ -404,7 +406,10 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { let arguments = arguments as! DeviceContactInfoControllerArguments switch self { case let .info(_, _, _, dateTimeFormat, peer, state, jobSummary, _, hiddenAvatar): - return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .contact, peer: peer, presence: nil, label: jobSummary, memberCount: nil, state: state, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks(withTopInset: false, withExtendedBottomInset: true), editingNameUpdated: { editingName in + guard let accountContext = arguments.accountContext else { + fatalError() + } + return ItemListAvatarAndNameInfoItem(accountContext: accountContext, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .contact, peer: peer, presence: nil, label: jobSummary, memberCount: nil, state: state, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks(withTopInset: false, withExtendedBottomInset: true), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) }, avatarTapped: { if peer.smallProfileImage != nil { @@ -625,7 +630,7 @@ private func filteredContactData(contactData: DeviceContactExtendedData, exclude return DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumbers: phoneNumbers), middleName: contactData.middleName, prefix: contactData.prefix, suffix: contactData.suffix, organization: includeJob ? contactData.organization : "", jobTitle: includeJob ? contactData.jobTitle : "", department: includeJob ? contactData.department : "", emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: includeBirthday ? contactData.birthdayDate : nil, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles, note: includeNote ? contactData.note : "") } -private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, presentationData: PresentationData, peer: EnginePeer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool, hiddenAvatar: TelegramMediaImageRepresentation?) -> [DeviceContactInfoEntry] { +private func deviceContactInfoEntries(context: ShareControllerAccountContext, presentationData: PresentationData, peer: EnginePeer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool, hiddenAvatar: TelegramMediaImageRepresentation?) -> [DeviceContactInfoEntry] { var entries: [DeviceContactInfoEntry] = [] var editingName: ItemListAvatarAndNameInfoItemName? @@ -738,16 +743,20 @@ private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, var addressIndex = 0 for address in contactData.addresses { - let signal = geocodeLocation(address: address.asPostalAddress) - |> mapToSignal { coordinates -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in - if let (latitude, longitude) = coordinates { - let resource = MapSnapshotMediaResource(latitude: latitude, longitude: longitude, width: 90, height: 90) - return chatMapSnapshotImage(engine: engine, resource: resource) - } else { - return .single({ _ in return nil }) + let signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> + if let context = context as? ShareControllerAppAccountContext { + signal = geocodeLocation(address: address.asPostalAddress) + |> mapToSignal { coordinates -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in + if let (latitude, longitude) = coordinates { + let resource = MapSnapshotMediaResource(latitude: latitude, longitude: longitude, width: 90, height: 90) + return chatMapSnapshotImage(engine: context.context.engine, resource: resource) + } else { + return .single({ _ in return nil }) + } } + } else { + signal = .single({ _ in return nil }) } - entries.append(.address(entries.count, addressIndex, presentationData.theme, localizedGenericContactFieldLabel(label: address.label, strings: presentationData.strings), address, signal, selecting ? !state.excludedComponents.contains(.address(address)) : nil)) addressIndex += 1 } @@ -828,7 +837,7 @@ private final class DeviceContactInfoController: ItemListController, MFMessageCo } } -public func deviceContactInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { +public func deviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { var initialState = DeviceContactInfoState() if case let .create(peer, contactData, _, _, _) = subject { var peerPhoneNumber: String? @@ -876,7 +885,11 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta var displayCopyContextMenuImpl: ((DeviceContactInfoEntryTag, String) -> Void)? + let presentationData = environment.presentationData let callImpl: (String) -> Void = { number in + guard let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } let user: Signal if let peer = subject.peer { user = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) @@ -890,10 +903,10 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } else { user = .single(nil) } + let _ = (user |> deliverOnMainQueue).start(next: { user in if let user = user, let phone = user.phone, formatPhoneNumber(phone) == formatPhoneNumber(number) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationData: presentationData) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() @@ -932,7 +945,13 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta shareViaException = shareViaExceptionValue } - let arguments = DeviceContactInfoControllerArguments(context: context, isPlain: !isShare, updateEditingName: { editingName in + let accountContext: AccountContext? + if let context = context as? ShareControllerAppAccountContext { + accountContext = context.context + } else { + accountContext = nil + } + let arguments = DeviceContactInfoControllerArguments(accountContext: accountContext, isPlain: !isShare, updateEditingName: { editingName in updateState { state in var state = state if let _ = state.editingState { @@ -952,6 +971,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta return state } }, updatePhoneLabel: { id, currentLabel in + guard let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } pushControllerImpl?(phoneLabelController(context: context, currentLabel: currentLabel, completion: { value in updateState { state in var state = state @@ -1000,7 +1022,6 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta if subject.contactData.basicData.phoneNumbers.count == 1 { inviteAction(subject.contactData.basicData.phoneNumbers[0].value) } else { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationData: presentationData) let dismissAction: () -> Void = { [weak controller] in controller?.dismissAnimated() @@ -1020,7 +1041,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } case .createContact: - pushControllerImpl?(deviceContactInfoController(context: context, subject: .create(peer: subject.peer, contactData: subject.contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + pushControllerImpl?(deviceContactInfoController(context: context, environment: environment, subject: .create(peer: subject.peer, contactData: subject.contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in dismissImpl?(false) }), completed: nil, cancelled: nil)) case .addToExisting: @@ -1059,9 +1080,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta }) let hiddenAvatarPromise = Promise(nil) - let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData + let updatedPresentationData = updatedPresentationData?.signal ?? environment.updatedPresentationData let previousEditingPhoneIds = Atomic?>(value: nil) - let signal = combineLatest(presentationData, statePromise.get(), contactData, hiddenAvatarPromise.get()) + let signal = combineLatest(updatedPresentationData, statePromise.get(), contactData, hiddenAvatarPromise.get()) |> map { presentationData, state, peerAndContactData, hiddenAvatar -> (ItemListControllerState, (ItemListNodeState, Any)) in var presentationData = presentationData let updatedTheme = presentationData.theme.withModalBlocksBackground() @@ -1116,6 +1137,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta rightNavigationButton = ItemListNavigationButton(content: .text(isShare ? presentationData.strings.Common_Done : presentationData.strings.Compose_Create), style: .bold, enabled: (isShare || !filteredPhoneNumbers.isEmpty) && composedContactData != nil, action: { if let composedContactData = composedContactData { + guard let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } var addToPrivacyExceptions = false updateState { state in var state = state @@ -1129,7 +1153,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta if share, filteredPhoneNumbers.count <= 1, let peer = peer { addContactDisposable.set((context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions) |> deliverOnMainQueue).start(error: { _ in - presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + presentControllerImpl?(textAlertController(context: context, updatedPresentationData: (environment.presentationData, updatedPresentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) }, completed: { let _ = (contactDataManager.createContactWithData(composedContactData) |> deliverOnMainQueue).start(next: { contactIdAndData in @@ -1250,7 +1274,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta focusItemTag = DeviceContactInfoEntryTag.editingPhone(insertedPhoneId) } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(account: context.account, engine: context.engine, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones, hiddenAvatar: hiddenAvatar), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(context: context, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones, hiddenAvatar: hiddenAvatar), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag) return (controllerState, (listState, arguments)) } @@ -1258,24 +1282,27 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta actionsDisposable.dispose() } - let controller = DeviceContactInfoController(context: context, state: signal) + let controller = DeviceContactInfoController(presentationData: ItemListPresentationData(environment.presentationData), updatedPresentationData: environment.updatedPresentationData |> map { ItemListPresentationData($0) }, state: signal, tabBarItem: nil) controller.navigationPresentation = .modal addToExistingImpl = { [weak controller] in - guard let controller = controller else { + guard let controller, let accountContext = (context as? ShareControllerAppAccountContext)?.context else { return } - addContactToExisting(context: context, parentController: controller, contactData: subject.contactData, completion: { peer, contactId, contactData in - replaceControllerImpl?(deviceContactInfoController(context: context, subject: .vcard(peer?._asPeer(), contactId, contactData), completed: nil, cancelled: nil)) + addContactToExisting(context: accountContext, parentController: controller, contactData: subject.contactData, completion: { peer, contactId, contactData in + replaceControllerImpl?(deviceContactInfoController(context: context, environment: environment, subject: .vcard(peer?._asPeer(), contactId, contactData), completed: nil, cancelled: nil)) }) } openChatImpl = { [weak controller] peerId in + guard let controller, let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer else { + |> deliverOnMainQueue).start(next: { [weak controller] peer in + guard let peer, let controller else { return } - if let navigationController = (controller?.navigationController as? NavigationController) { + if let navigationController = (controller.navigationController as? NavigationController) { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer))) } }) @@ -1301,7 +1328,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } } inviteImpl = { [weak controller] numbers in - controller?.inviteContact(presentationData: context.sharedContext.currentPresentationData.with { $0 }, numbers: numbers) + controller?.inviteContact(presentationData: environment.presentationData, numbers: numbers) } openAddressImpl = { [weak controller] address in guard let _ = controller else { @@ -1309,7 +1336,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } } openUrlImpl = { [weak controller] url in - guard let controller = controller else { + guard let controller, let context = (context as? ShareControllerAppAccountContext)?.context else { return } context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: controller.navigationController as? NavigationController, dismissInput: { [weak controller] in @@ -1319,7 +1346,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta displayCopyContextMenuImpl = { [weak controller] tag, value in if let strongController = controller { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let presentationData = environment.presentationData var resultItemNode: ListViewItemNode? let _ = strongController.frameForItemNode({ itemNode in if let itemNode = itemNode as? ItemListTextWithLabelItemNode { @@ -1350,6 +1377,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } } openAvatarImpl = { [weak controller] peer in + guard let context = (context as? ShareControllerAppAccountContext)?.context else { + return + } let avatarController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { _, _ in }) hiddenAvatarPromise.set( @@ -1413,7 +1443,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie let _ = (dataSignal |> deliverOnMainQueue).start(next: { peer, stableId in guard let stableId = stableId else { - parentController.present(deviceContactInfoController(context: context, subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + parentController.present(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in }), completed: nil, cancelled: nil), in: .window(.root)) return } @@ -1459,7 +1489,7 @@ func addContactOptionsController(context: AccountContext, peer: EnginePeer?, con controller.setItemGroups([ ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Profile_CreateNewContact, action: { [weak controller] in - controller?.present(context.sharedContext.makeDeviceContactInfoController(context: context, subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in + controller?.present(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in }), completed: nil, cancelled: nil), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) dismissAction() }), @@ -1476,3 +1506,32 @@ func addContactOptionsController(context: AccountContext, peer: EnginePeer?, con ]) return controller } + +public func pushContactContextOptionsController(context: AccountContext, contextController: ContextControllerProtocol, presentationData: PresentationData, peer: EnginePeer?, contactData: DeviceContactExtendedData, parentController: ViewController, push: @escaping (ViewController) -> Void) { + var items: [ContextMenuItem] = [] + items.append( + .action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in + c?.popItems() + })) + ) + items.append(.separator) + items.append( + .action(ContextMenuActionItem(text: presentationData.strings.Chat_Context_Phone_CreateNewContact, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + push(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in + }), completed: nil, cancelled: nil)) + })) + ) + items.append( + .action(ContextMenuActionItem(text: presentationData.strings.Chat_Context_Phone_AddToExistingContact, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MoveToContacts"), color: theme.contextMenu.primaryColor) }, action: { [weak parentController] _, f in + f(.default) + guard let parentController else { + return + } + addContactToExisting(context: context, parentController: parentController, contactData: contactData, completion: { peer, contactId, contactData in + }) + })) + ) + contextController.pushItems(items: .single(ContextController.Items(content: .list(items)))) +} diff --git a/submodules/PeerInfoUI/Sources/UserInfoController.swift b/submodules/PeerInfoUI/Sources/UserInfoController.swift index 5a2f13d12c..3da170adfe 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoController.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoController.swift @@ -58,8 +58,8 @@ public func openAddPersonContactImpl(context: AccountContext, updatedPresentatio if let statusSettings = statusSettings { shareViaException = statusSettings.contains(.addExceptionWhenAddingContact) } - - pushController(deviceContactInfoController(context: context, updatedPresentationData: updatedPresentationData, subject: .create(peer: user, contactData: contactData, isSharing: true, shareViaException: shareViaException, completion: { peer, stableId, contactData in + + pushController(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), updatedPresentationData: updatedPresentationData, subject: .create(peer: user, contactData: contactData, isSharing: true, shareViaException: shareViaException, completion: { peer, stableId, contactData in if let peer = peer as? TelegramUser { completion() diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index e77387018c..09447ffa43 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -304,18 +304,6 @@ private func collectExternalShareItems(strings: PresentationStrings, dateTimeFor }) } -public protocol ShareControllerEnvironment: AnyObject { - var presentationData: PresentationData { get } - var updatedPresentationData: Signal { get } - var isMainApp: Bool { get } - var energyUsageSettings: EnergyUsageSettings { get } - - var mediaManager: MediaManager? { get } - - func setAccountUserInterfaceInUse(id: AccountRecordId) -> Disposable - func donateSendMessageIntent(account: ShareControllerAccountContext, peerIds: [EnginePeer.Id]) -} - public final class ShareControllerAppEnvironment: ShareControllerEnvironment { let sharedContext: SharedAccountContext @@ -353,19 +341,6 @@ public final class ShareControllerAppEnvironment: ShareControllerEnvironment { } } -public protocol ShareControllerAccountContext: AnyObject { - var accountId: AccountRecordId { get } - var accountPeerId: EnginePeer.Id { get } - var stateManager: AccountStateManager { get } - var engineData: TelegramEngine.EngineData { get } - var animationCache: AnimationCache { get } - var animationRenderer: MultiAnimationRenderer { get } - var contentSettings: ContentSettings { get } - var appConfiguration: AppConfiguration { get } - - func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> -} - public final class ShareControllerAppAccountContext: ShareControllerAccountContext { public let context: AccountContext diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 693baadba3..abec599e04 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -349,7 +349,6 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate var disabledPeerSelected: ((EnginePeer) -> Void)? let ready = Promise() - private var didSetReady = false private var controllerInteraction: ShareControllerInteraction? diff --git a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift index 335db1704d..f5ddb8d0ad 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift @@ -175,7 +175,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode { break case .ignore: return .fail - case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji: + case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji: return .waitForSingleTap } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift index c34766a424..242acf7eaa 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift @@ -141,6 +141,7 @@ public struct ChatMessageBubbleContentTapAction { public enum Content { case none case url(Url) + case phone(String) case textMention(String) case peerMention(peerId: PeerId, mention: String, openProfile: Bool) case botCommand(String) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 3996a553ef..e8b5c74f3a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -770,24 +770,24 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } self.mainContextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtractedToContextPreview, _ in - guard let strongSelf = self, let _ = strongSelf.item else { + guard let self, let _ = self.item else { return } - for contentNode in strongSelf.contentNodes { + for contentNode in self.contentNodes { contentNode.willUpdateIsExtractedToContextPreview(isExtractedToContextPreview) } } self.mainContextSourceNode.isExtractedToContextPreviewUpdated = { [weak self] isExtractedToContextPreview in - guard let strongSelf = self else { + guard let self else { return } - strongSelf.backgroundWallpaperNode.setMaskMode(strongSelf.backgroundMaskMode) - strongSelf.backgroundNode.setMaskMode(strongSelf.backgroundMaskMode) - if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect { - strongSelf.updateAbsoluteRect(rect, within: size) + self.backgroundWallpaperNode.setMaskMode(self.backgroundMaskMode) + self.backgroundNode.setMaskMode(self.backgroundMaskMode) + if !isExtractedToContextPreview, let (rect, size) = self.absoluteRect { + self.updateAbsoluteRect(rect, within: size) } - - for contentNode in strongSelf.contentNodes { + + for contentNode in self.contentNodes { contentNode.updateIsExtractedToContextPreview(isExtractedToContextPreview) } } @@ -811,7 +811,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.applyAbsoluteOffsetSpringInternal(value: value, duration: duration, damping: damping) } } - + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -1150,15 +1150,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let contentNodePoint = strongSelf.view.convert(point, to: contentNode.view) let tapAction = contentNode.tapActionAtPoint(contentNodePoint, gesture: .tap, isEstimating: true) switch tapAction.content { - case .none: - if let _ = strongSelf.item?.controllerInteraction.tapMessage { - return .waitForSingleTap - } - break - case .ignore: - return .fail - case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji: + case .none: + if let _ = strongSelf.item?.controllerInteraction.tapMessage { return .waitForSingleTap + } + break + case .ignore: + return .fail + case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji: + return .waitForSingleTap } } @@ -4589,6 +4589,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url.url, concealed: url.concealed, message: item.content.firstMessage, allowInlineWebpageResolution: url.allowInlineWebpageResolution, progress: tapAction.activate?())) }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) } + case let .phone(number): + return .action(InternalBubbleTapAction.Action({ [weak self] in + guard let self, let item = self.item, let contentNode = self.contextContentNodeForPhoneNumber(number) else { + return + } + + self.addSubnode(contentNode) + + item.controllerInteraction.openPhoneContextMenu(ChatControllerInteraction.OpenPhone(number: number, message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) + }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) case let .peerMention(peerId, _, openProfile): return .action(InternalBubbleTapAction.Action { [weak self] in if let item = self?.item { @@ -4751,6 +4761,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else { disableDefaultPressAnimation = true } + case let .phone(number): + return .action(InternalBubbleTapAction.Action({ [weak self] in + guard let self, let item = self.item, let contentNode = self.contextContentNodeForPhoneNumber(number) else { + return + } + + self.addSubnode(contentNode) + + item.controllerInteraction.openPhoneContextMenu(ChatControllerInteraction.OpenPhone(number: number, message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) + }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) case let .peerMention(peerId, mention, _): return .action(InternalBubbleTapAction.Action { item.controllerInteraction.longTap(.peerMention(peerId, mention), message) @@ -4818,6 +4838,39 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return nil } + private func contextContentNodeForPhoneNumber(_ number: String) -> ContextExtractedContentContainingNode? { + guard let item = self.item else { + return nil + } + let containingNode = ContextExtractedContentContainingNode() + + let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) + + let textNode = ImmediateTextNode() + textNode.attributedText = NSAttributedString(string: number, font: Font.regular(item.presentationData.fontSize.baseDisplaySize), textColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.linkTextColor : item.presentationData.theme.theme.chat.message.outgoing.linkTextColor) + let textSize = textNode.updateLayout(CGSize(width: 1000.0, height: 100.0)) + + let backgroundNode = ASDisplayNode() + backgroundNode.backgroundColor = (incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill).first ?? .black + backgroundNode.clipsToBounds = true + backgroundNode.cornerRadius = 10.0 + + let insets = UIEdgeInsets(top: 5.0, left: 8.0, bottom: 5.0, right: 8.0) + let backgroundSize = CGSize(width: textSize.width + insets.left + insets.right, height: textSize.height + insets.top + insets.bottom) + backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize) + textNode.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: textSize) + backgroundNode.addSubnode(textNode) + + containingNode.frame = CGRect(origin: CGPoint(x: self.backgroundNode.frame.minX + 3.0, y: 1.0), size: CGSize(width: backgroundSize.width, height: backgroundSize.height + 20.0)) + containingNode.contentNode.frame = CGRect(origin: .zero, size: backgroundSize) + containingNode.contentRect = CGRect(origin: .zero, size: backgroundSize) + containingNode.contentNode.addSubnode(backgroundNode) + + containingNode.contentNode.alpha = 0.0 + + return containingNode + } + private func traceSelectionNodes(parent: ASDisplayNode, point: CGPoint) -> ASDisplayNode? { if let parent = parent as? FileMessageSelectionNode, parent.bounds.contains(point) { return parent diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 8c29fa309a..2e71cd7cd8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -857,7 +857,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { urlRange = urlRangeValue concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText) } - return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)), activate: { [weak self] in + + var content: ChatMessageBubbleContentTapAction.Content + if url.hasPrefix("tel:") { + content = .phone(url.replacingOccurrences(of: "tel:", with: "")) + } else { + content = .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)) + } + + return ChatMessageBubbleContentTapAction(content: content, activate: { [weak self] in guard let self else { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index ab878010c0..d40feae501 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -614,6 +614,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, openRecommendedChannelContextMenu: { _, _, _ in }, openGroupBoostInfo: { _, _ in }, openStickerEditor: { + }, openPhoneContextMenu: { _ in + }, openAgeRestrictedMessageMedia: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 7d426b3957..1b83215ed9 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -148,6 +148,22 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol } } + public struct OpenPhone { + public var number: String + public var message: Message + public var contentNode: ContextExtractedContentContainingNode + public var messageNode: ASDisplayNode + public var progress: Promise? + + public init(number: String, message: Message, contentNode: ContextExtractedContentContainingNode, messageNode: ASDisplayNode, progress: Promise? = nil) { + self.number = number + self.message = message + self.contentNode = contentNode + self.messageNode = messageNode + self.progress = progress + } + } + public let openMessage: (Message, OpenMessageParams) -> Bool public let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void public let openPeerMention: (String, Promise?) -> Void @@ -242,6 +258,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let openRecommendedChannelContextMenu: (EnginePeer, UIView, ContextGesture?) -> Void public let openGroupBoostInfo: (EnginePeer.Id?, Int) -> Void public let openStickerEditor: () -> Void + public let openPhoneContextMenu: (OpenPhone) -> Void + public let openAgeRestrictedMessageMedia: (Message, @escaping () -> Void) -> Void public let requestMessageUpdate: (MessageId, Bool) -> Void public let cancelInteractiveKeyboardGestures: () -> Void @@ -367,6 +385,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol openRecommendedChannelContextMenu: @escaping (EnginePeer, UIView, ContextGesture?) -> Void, openGroupBoostInfo: @escaping (EnginePeer.Id?, Int) -> Void, openStickerEditor: @escaping () -> Void, + openPhoneContextMenu: @escaping (OpenPhone) -> Void, + openAgeRestrictedMessageMedia: @escaping (Message, @escaping () -> Void) -> Void, requestMessageUpdate: @escaping (MessageId, Bool) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, dismissTextInput: @escaping () -> Void, @@ -472,6 +492,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol self.openRecommendedChannelContextMenu = openRecommendedChannelContextMenu self.openGroupBoostInfo = openGroupBoostInfo self.openStickerEditor = openStickerEditor + self.openPhoneContextMenu = openPhoneContextMenu + self.openAgeRestrictedMessageMedia = openAgeRestrictedMessageMedia self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 763117fce0..c6bc345962 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -3326,6 +3326,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, openRecommendedChannelContextMenu: { _, _, _ in }, openGroupBoostInfo: { _, _ in }, openStickerEditor: { + }, openPhoneContextMenu: { _ in + }, openAgeRestrictedMessageMedia: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift index ef463ef0f3..d4ac31fb09 100644 --- a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift @@ -492,19 +492,13 @@ public class ShareRootControllerImpl { let displayShare: () -> Void = { var cancelImpl: (() -> Void)? - let _ = cancelImpl let beginShare: () -> Void = { let requestUserInteraction: ([UnpreparedShareItemContent]) -> Signal<[PreparedShareItemContent], NoError> = { content in return Signal { [weak self] subscriber in switch content[0] { case let .contact(data): - #if !DEBUG - //qwefqwfqwefw - #endif - let _ = data - let _ = self - /*let controller = deviceContactInfoController(context: context, subject: .filter(peer: nil, contactId: nil, contactData: data, completion: { peer, contactData in + let controller = deviceContactInfoController(context: context, environment: environment, subject: .filter(peer: nil, contactId: nil, contactData: data, completion: { peer, contactData in let phone = contactData.basicData.phoneNumbers[0].value if let vCardData = contactData.serializedVCard() { subscriber.putNext([.media(.media(.standalone(media: TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: vCardData))))]) @@ -517,7 +511,7 @@ public class ShareRootControllerImpl { if let strongSelf = self, let window = strongSelf.mainWindow { controller.presentationArguments = ViewControllerPresentationArguments(presentationAnimation: .modalSheet) window.present(controller, on: .root) - }*/ + } break } return EmptyDisposable diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 69539761e3..4f6685fa82 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -1762,7 +1762,7 @@ final class StoryItemSetContainerSendMessage { self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) } else { - let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: component.context, subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in + let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: component.context), environment: ShareControllerAppEnvironment(sharedContext: component.context.sharedContext), subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in guard let self, let view else { return } diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddUser.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddUser.imageset/Contents.json index d7f8a263fa..c184976a0d 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddUser.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddUser.imageset/Contents.json @@ -1,12 +1,12 @@ { "images" : [ { - "idiom" : "universal", - "filename" : "ic_lt_adduser.pdf" + "filename" : "ic_lt_adduser.pdf", + "idiom" : "universal" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/18on_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/18on_24.pdf new file mode 100644 index 0000000000..356e934807 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/18on_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/Contents.json new file mode 100644 index 0000000000..eda08930c8 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "18on_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/18off_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/18off_24.pdf new file mode 100644 index 0000000000..974496474d Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/18off_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/Contents.json new file mode 100644 index 0000000000..b48dcd4e15 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "18off_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/Contents.json new file mode 100644 index 0000000000..353e2a5ade --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tlogo_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/tlogo_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/tlogo_24.pdf new file mode 100644 index 0000000000..3293521e27 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Telegram.imageset/tlogo_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/Contents.json new file mode 100644 index 0000000000..7ce72aef99 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "eyeoff_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/eyeoff_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/eyeoff_30.pdf new file mode 100644 index 0000000000..adc6a47cc6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/AgeRestricted.imageset/eyeoff_30.pdf @@ -0,0 +1,143 @@ +%PDF-1.7 + +1 0 obj + << /Filter /FlateDecode + /Type /XObject + /Length 2 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 24.000000 24.000000 ] + >> +stream +xmͮE FR.X0 +&ss7S=~{_ۻO~z]Yrmt1LJ2wm`nxc*IldA2>HRֈ|/hز UǐV/hoj,Ym\ċYyoYBk)g)Whtwoky﫠{%@^frěP 4":yQ3,`vҭk'J.VgN[ zlQ۠H'dRS3RҦ.@o?;#;D/^PĘALyF`Ugٖq"KuFF|"9zsff1@LJ!+3"? +r/䱭B g +fDjR\l^VE(RU&cS9bX\QUڢ(-] }64jF"rs r:Z'WJ$c9Rdc(p +F7}[,mlW/- UjQYEE7tZ{lQb urּ;wm™T5tƅ80JLgGYV:J"'Rt C QWA "C5Xwr@4;Vsy}>* T\<4i.z&n~2pqjS7caB-z5{@:Dsvw0)tDa'_u-Kb#9\}HS:u(\X!U\͉BsʞXUUNJnXDvV]A|s1O3| +endstream +endobj + +2 0 obj + 868 +endobj + +3 0 obj + << /Type /XObject + /Length 4 0 R + /Group << /Type /Group + /S /Transparency + >> + /Subtype /Form + /Resources << >> + /BBox [ 0.000000 0.000000 24.000000 24.000000 ] + >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.000000 12.800001 m +0.000000 16.720367 0.000000 18.680552 0.762954 20.177933 c +1.434068 21.495068 2.504932 22.565931 3.822066 23.237045 c +5.319448 24.000000 7.279633 24.000000 11.200000 24.000000 c +12.800001 24.000000 l +16.720367 24.000000 18.680552 24.000000 20.177933 23.237045 c +21.495068 22.565931 22.565931 21.495068 23.237045 20.177933 c +24.000000 18.680552 24.000000 16.720367 24.000000 12.800000 c +24.000000 11.199999 l +24.000000 7.279633 24.000000 5.319448 23.237045 3.822067 c +22.565931 2.504932 21.495068 1.434069 20.177933 0.762955 c +18.680552 0.000000 16.720367 0.000000 12.800000 0.000000 c +11.199999 0.000000 l +7.279632 0.000000 5.319448 0.000000 3.822066 0.762955 c +2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c +0.000000 5.319448 0.000000 7.279633 0.000000 11.200000 c +0.000000 12.800001 l +h +f +n +Q + +endstream +endobj + +4 0 obj + 944 +endobj + +5 0 obj + << /XObject << /X1 1 0 R >> + /ExtGState << /E1 << /SMask << /Type /Mask + /G 3 0 R + /S /Alpha + >> + /Type /ExtGState + >> >> + >> +endobj + +6 0 obj + << /Length 7 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +/E1 gs +/X1 Do +Q + +endstream +endobj + +7 0 obj + 46 +endobj + +8 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 5 0 R + /Contents 6 0 R + /Parent 9 0 R + >> +endobj + +9 0 obj + << /Kids [ 8 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +10 0 obj + << /Pages 9 0 R + /Type /Catalog + >> +endobj + +xref +0 11 +0000000000 65535 f +0000000010 00000 n +0000001152 00000 n +0000001174 00000 n +0000002366 00000 n +0000002388 00000 n +0000002686 00000 n +0000002788 00000 n +0000002809 00000 n +0000002982 00000 n +0000003056 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 10 0 R + /Size 11 +>> +startxref +3116 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Contents.json b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Contents.json index 38f0c81fc2..6e965652df 100644 --- a/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Contents.json @@ -1,9 +1,9 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "provides-namespace" : true } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/Contents.json new file mode 100644 index 0000000000..d46c98ac2d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagsearch_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/tagsearch_24.pdf b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/tagsearch_24.pdf new file mode 100644 index 0000000000..893341aa10 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Components/Search Bar/Hashtag.imageset/tagsearch_24.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/Contents.json new file mode 100644 index 0000000000..ba6d692fc9 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "mock.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/mock.png b/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/mock.png new file mode 100644 index 0000000000..b06e45609d Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Mock.imageset/mock.png differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Contents.json new file mode 100644 index 0000000000..5f6829d46c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Mock2.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Mock2.png b/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Mock2.png new file mode 100644 index 0000000000..431954074b Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Mock2.imageset/Mock2.png differ diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift new file mode 100644 index 0000000000..ddabaf6e01 --- /dev/null +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift @@ -0,0 +1,241 @@ +import Foundation +import UIKit +import SwiftSignalKit +import Postbox +import TelegramCore +import AsyncDisplayKit +import Display +import TelegramNotices +import ContextUI +import AccountContext +import ChatMessageItemView +import ChatMessageItemCommon +import AvatarNode +import UndoUI +import MessageUI +import PeerInfoUI + +extension ChatControllerImpl: MFMessageComposeViewControllerDelegate { + func openPhoneContextMenu(number: String, peer: EnginePeer?, message: Message, contentNode: ContextExtractedContentContainingNode, messageNode: ASDisplayNode, frame: CGRect, anyRecognizer: UIGestureRecognizer?, location: CGPoint?) -> Void { + if self.presentationInterfaceState.interfaceState.selectionState != nil { + return + } + + self.dismissAllTooltips() + + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer + let gesture: ContextGesture? = anyRecognizer as? ContextGesture + + if let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) { + (self.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures() + self.chatDisplayNode.cancelInteractiveKeyboardGestures() + var updatedMessages = messages + for i in 0 ..< updatedMessages.count { + if updatedMessages[i].id == message.id { + let message = updatedMessages.remove(at: i) + updatedMessages.insert(message, at: 0) + break + } + } + + self.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() + + let source: ContextContentSource + if let location = location { + source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) + } else { + source = .extracted(ChatMessagePhoneContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) +// source = .extracted(ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, selectAll: false)) + } + + var items: [ContextMenuItem] = [] + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_AddToContacts, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + guard let self, let c else { + return + } + let basicData = DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [ + DeviceContactPhoneNumberData(label: "", value: number) + ]) + let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + + pushContactContextOptionsController(context: self.context, contextController: c, presentationData: self.presentationData, peer: nil, contactData: contactData, parentController: self, push: { [weak self] c in + self?.push(c) + }) + })) + ) + items.append(.separator) + if let peer { + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_SendMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + guard let self else { + return + } + self.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil) + })) + ) + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_TelegramVoiceCall, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.controllerInteraction?.callPeer(peer.id, false) + })) + ) + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_TelegramVideoCall, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoCall"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.controllerInteraction?.callPeer(peer.id, true) + })) + ) + } else { + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_InviteToTelegram, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Telegram"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.inviteToTelegram(numbers: [number]) + })) + ) + } + if number.hasPrefix("+888") { + + } else { + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_CallViaCarrier, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PhoneCall"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.openUrl("tel:\(number)", concealed: false) + })) + ) + } + + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_CopyNumber, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + + UIPasteboard.general.string = number + + self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_PhoneCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })) + ) + + items.append(.separator) + if let peer { + let avatarSize = CGSize(width: 28.0, height: 28.0) + let avatarSignal = peerAvatarCompleteImage(account: self.context.account, peer: peer, size: avatarSize) + + let subtitle = NSMutableAttributedString(string: self.presentationData.strings.Chat_Context_Phone_ViewProfile + " >") + if let range = subtitle.string.range(of: ">"), let arrowImage = UIImage(bundleImageName: "Item List/InlineTextRightArrow") { + subtitle.addAttribute(.attachment, value: arrowImage, range: NSRange(range, in: subtitle.string)) + subtitle.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: subtitle.string)) + } + + items.append( + .action(ContextMenuActionItem(text: peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), textLayout: .secondLineWithAttributedValue(subtitle), icon: { theme in return nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), iconPosition: .left, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + self.openPeer(peer: peer, navigation: .info(nil), fromMessage: nil) + })) + ) + } else { + let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Phone_NotOnTelegram, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)) + ) + } + + self.canReadHistory.set(false) + + let controller = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false) + controller.dismissed = { [weak self] in + self?.canReadHistory.set(true) + } + + self.window?.presentInGlobalOverlay(controller) + } + } + + private func inviteToTelegram(numbers: [String]) { + if MFMessageComposeViewController.canSendText() { + let composer = MFMessageComposeViewController() + composer.messageComposeDelegate = self + composer.recipients = Array(Set(numbers)) + let url = self.presentationData.strings.InviteText_URL + let body = self.presentationData.strings.InviteText_SingleContact(url).string + composer.body = body + self.messageComposeController = composer + if let window = self.view.window { + window.rootViewController?.present(composer, animated: true) + } + } + } + + @objc public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { + self.messageComposeController = nil + + controller.dismiss(animated: true, completion: nil) + } +} + +private final class ChatMessagePhoneContextExtractedContentSource: ContextExtractedContentSource { + let keepInPlace: Bool = false + let ignoreContentTouches: Bool = true + let blurBackground: Bool = true + let adjustContentHorizontally = true + + private weak var chatNode: ChatControllerNode? + private let contentNode: ContextExtractedContentContainingNode + + var shouldBeDismissed: Signal { + return .single(false) + } + + init(chatNode: ChatControllerNode, contentNode: ContextExtractedContentContainingNode) { + self.chatNode = chatNode + self.contentNode = contentNode + } + + func takeView() -> ContextControllerTakeViewInfo? { + guard let chatNode = self.chatNode else { + return nil + } + + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(node: self.contentNode.contentNode, alpha: 1.0) + + return ContextControllerTakeViewInfo(containingItem: .node(self.contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + guard let chatNode = self.chatNode else { + return nil + } + + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(node: self.contentNode.contentNode, alpha: 0.0, completion: { _ in + self.contentNode.removeFromSupernode() + }) + + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + } +} diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 745b21d29b..9202c64e95 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -123,6 +123,7 @@ import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem import AdsInfoScreen +import MessageUI public enum ChatControllerPeekActions { case standard @@ -224,6 +225,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var validLayout: ContainerViewLayout? public weak var parentController: ViewController? + public weak var customNavigationController: NavigationController? let currentChatListFilter: Int32? let chatNavigationStack: [ChatNavigationStackItem] @@ -593,6 +595,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var networkSpeedEventsDisposable: Disposable? + var messageComposeController: MFMessageComposeViewController? + public var alwaysShowSearchResultsAsList: Bool = false { didSet { self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) @@ -2295,27 +2299,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } }, openUrl: { [weak self] urlData in - if let strongSelf = self { - let url = urlData.url - let concealed = urlData.concealed - let message = urlData.message - let progress = urlData.progress - let forceExternal = urlData.external ?? false - - var skipConcealedAlert = false - if let author = message?.author, author.isVerified { - skipConcealedAlert = true - } - - if let message, let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute { - strongSelf.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId) - } - - if let performOpenURL = strongSelf.performOpenURL { - performOpenURL(message, url, progress) - } else { - strongSelf.openUrl(url, concealed: concealed, forceExternal: forceExternal, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution, progress: progress) - } + guard let strongSelf = self else { + return + } + let url = urlData.url + let concealed = urlData.concealed + let message = urlData.message + let progress = urlData.progress + let forceExternal = urlData.external ?? false + + var skipConcealedAlert = false + if let author = message?.author, author.isVerified { + skipConcealedAlert = true + } + + if let message, let adAttribute = message.attributes.first(where: { $0 is AdMessageAttribute }) as? AdMessageAttribute { + strongSelf.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId) + } + + if let performOpenURL = strongSelf.performOpenURL { + performOpenURL(message, url, progress) + } else { + strongSelf.openUrl(url, concealed: concealed, forceExternal: forceExternal, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution, progress: progress) } }, shareCurrentLocation: { [weak self] in if let strongSelf = self { @@ -4629,6 +4634,34 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } self.openStickerEditor() + }, openPhoneContextMenu: { [weak self] phoneData in + guard let self else { + return + } + phoneData.progress?.set(.single(true)) + let _ = (self.context.engine.peers.resolvePeerByPhone(phone: phoneData.number) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self else { + return + } + phoneData.progress?.set(.single(false)) + + self.openPhoneContextMenu(number: phoneData.number, peer: peer, message: phoneData.message, contentNode: phoneData.contentNode, messageNode: phoneData.messageNode, frame: phoneData.messageNode.bounds, anyRecognizer: nil, location: nil) + }) + }, openAgeRestrictedMessageMedia: { [weak self] message, reveal in + guard let self else { + return + } + let controller = chatAgeRestrictionAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, completion: { [weak self] alwaysShow in + guard let self else { + return + } + if alwaysShow { + self.present(UndoOverlayController(presentationData: self.presentationData, content: .info(title: nil, text: "You can update the visibility of sensitive media in [Data and Storage > Show 18+ Content]().", timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, action: { _ in return false }), in: .current) + } + reveal() + }) + self.present(controller, in: .window(.root)) }, requestMessageUpdate: { [weak self] id, scroll in if let self { self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll) @@ -9511,7 +9544,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G func addPeerContact() { if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser, let peerStatusSettings = self.presentationInterfaceState.contactStatus?.peerStatusSettings, let contactData = DeviceContactExtendedData(peer: EnginePeer(peer)) { - self.present(context.sharedContext.makeDeviceContactInfoController(context: context, subject: .create(peer: peer, contactData: contactData, isSharing: true, shareViaException: peerStatusSettings.contains(.addExceptionWhenAddingContact), completion: { [weak self] peer, stableId, contactData in + self.present(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: self.context), environment: ShareControllerAppEnvironment(sharedContext: self.context.sharedContext), subject: .create(peer: peer, contactData: contactData, isSharing: true, shareViaException: peerStatusSettings.contains(.addExceptionWhenAddingContact), completion: { [weak self] peer, stableId, contactData in guard let strongSelf = self else { return } @@ -10556,6 +10589,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else if case let .overlay(navigationController) = self.presentationInterfaceState.mode { return navigationController } else { + if let navigationController = self.customNavigationController { + return navigationController + } return nil } } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 88d5c47191..4c1938382f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -32,6 +32,7 @@ import TelegramCallsUI import AutomaticBusinessMessageSetupScreen import MediaEditorScreen import CameraScreen +import ShareController extension ChatControllerImpl { enum AttachMenuSubject { @@ -520,7 +521,7 @@ extension ChatControllerImpl { enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) } else { - let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { return } @@ -1590,7 +1591,7 @@ extension ChatControllerImpl { let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) strongSelf.sendMessages([message]) } else { - let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { return } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift index 62ae3df62d..c0e39f7d9f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift @@ -176,7 +176,8 @@ extension ChatControllerImpl { }), in: .current) } - self.push(calendarScreen) + self.effectiveNavigationController?.pushViewController(calendarScreen) + dismissCalendarScreen = { [weak calendarScreen] in calendarScreen?.dismiss(completion: nil) } diff --git a/submodules/TelegramUI/Sources/ComposeController.swift b/submodules/TelegramUI/Sources/ComposeController.swift index d62b23f868..de5db88e8d 100644 --- a/submodules/TelegramUI/Sources/ComposeController.swift +++ b/submodules/TelegramUI/Sources/ComposeController.swift @@ -13,6 +13,7 @@ import SearchUI import TelegramPermissionsUI import AppBundle import DeviceAccess +import ShareController public class ComposeControllerImpl: ViewController, ComposeController { private let context: AccountContext @@ -200,7 +201,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { switch status { case .allowed: let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + (strongSelf.navigationController as? NavigationController)?.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in guard let strongSelf = self else { return } @@ -211,7 +212,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { } } } else { - (strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil), animated: true) + (strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil), animated: true) } }), completed: nil, cancelled: nil)) case .notDetermined: diff --git a/submodules/TelegramUI/Sources/OpenAddContact.swift b/submodules/TelegramUI/Sources/OpenAddContact.swift index b4c860dbc9..75b4622005 100644 --- a/submodules/TelegramUI/Sources/OpenAddContact.swift +++ b/submodules/TelegramUI/Sources/OpenAddContact.swift @@ -7,6 +7,7 @@ import AccountContext import AlertUI import PresentationDataUtils import PeerInfoUI +import ShareController func openAddContactImpl(context: AccountContext, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!!$_", present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void = {}) { let _ = (DeviceAccess.authorizationStatus(subject: .contacts) @@ -15,13 +16,13 @@ func openAddContactImpl(context: AccountContext, firstName: String = "", lastNam switch value { case .allowed: let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: label, value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - present(deviceContactInfoController(context: context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + present(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in if let peer = peer { if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushController(infoController) } } else { - pushController(deviceContactInfoController(context: context, subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) + pushController(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) } }), completed: completed, cancelled: nil), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) case .notDetermined: diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index b3534e55db..cbb4c97017 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -330,7 +330,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } else { contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName, lastName: contact.lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: contact.phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") } - let controller = deviceContactInfoController(context: params.context, updatedPresentationData: params.updatedPresentationData, subject: .vcard(peer?._asPeer(), nil, contactData), completed: nil, cancelled: nil) + let controller = deviceContactInfoController(context: ShareControllerAppAccountContext(context: params.context), environment: ShareControllerAppEnvironment(sharedContext: params.context.sharedContext), updatedPresentationData: params.updatedPresentationData, subject: .vcard(peer?._asPeer(), nil, contactData), completed: nil, cancelled: nil) params.navigationController?.pushViewController(controller) }) return true diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 5f67c834e4..8f794520d8 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -175,6 +175,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, openRecommendedChannelContextMenu: { _, _, _ in }, openGroupBoostInfo: { _, _ in }, openStickerEditor: { + }, openPhoneContextMenu: { _ in + }, openAgeRestrictedMessageMedia: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index a74e286ae2..dc17442a91 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1598,8 +1598,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, forceExternal: forceExternal, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, sendEmoji: sendEmoji, requestMessageActionUrlAuth: requestMessageActionUrlAuth, joinVoiceChat: joinVoiceChat, present: present, dismissInput: dismissInput, contentContext: contentContext, progress: progress, completion: completion) } - public func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { - return deviceContactInfoController(context: context, subject: subject, completed: completed, cancelled: cancelled) + public func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { + return deviceContactInfoController(context: context, environment: environment, subject: subject, completed: completed, cancelled: cancelled) } public func makePeersNearbyController(context: AccountContext) -> ViewController { @@ -1769,6 +1769,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, openRecommendedChannelContextMenu: { _, _, _ in }, openGroupBoostInfo: { _, _ in }, openStickerEditor: { + }, openPhoneContextMenu: { _ in + }, openAgeRestrictedMessageMedia: { _, _ in }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 51b3bcf008..3682f6ead3 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -234,24 +234,22 @@ public struct WebAppParameters { } } -public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> [String: Any] { - let backgroundColor = presentationTheme.list.plainBackgroundColor.rgb - let secondaryBackgroundColor = presentationTheme.list.blocksBackgroundColor.rgb +public func generateWebAppThemeParams(_ theme: PresentationTheme) -> [String: Any] { return [ - "bg_color": Int32(bitPattern: backgroundColor), - "secondary_bg_color": Int32(bitPattern: secondaryBackgroundColor), - "text_color": Int32(bitPattern: presentationTheme.list.itemPrimaryTextColor.rgb), - "hint_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb), - "link_color": Int32(bitPattern: presentationTheme.list.itemAccentColor.rgb), - "button_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.fillColor.rgb), - "button_text_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.foregroundColor.rgb), - "header_bg_color": Int32(bitPattern: presentationTheme.rootController.navigationBar.opaqueBackgroundColor.rgb), - "accent_text_color": Int32(bitPattern: presentationTheme.list.itemAccentColor.rgb), - "section_bg_color": Int32(bitPattern: presentationTheme.list.itemBlocksBackgroundColor.rgb), - "section_header_text_color": Int32(bitPattern: presentationTheme.list.freeTextColor.rgb), - "subtitle_text_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb), - "destructive_text_color": Int32(bitPattern: presentationTheme.list.itemDestructiveColor.rgb), - "section_separator_color": Int32(bitPattern: presentationTheme.list.itemBlocksSeparatorColor.rgb) + "bg_color": Int32(bitPattern: theme.list.plainBackgroundColor.rgb), + "secondary_bg_color": Int32(bitPattern: theme.list.blocksBackgroundColor.rgb), + "text_color": Int32(bitPattern: theme.list.itemPrimaryTextColor.rgb), + "hint_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "link_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "button_color": Int32(bitPattern: theme.list.itemCheckColors.fillColor.rgb), + "button_text_color": Int32(bitPattern: theme.list.itemCheckColors.foregroundColor.rgb), + "header_bg_color": Int32(bitPattern: theme.rootController.navigationBar.opaqueBackgroundColor.rgb), + "accent_text_color": Int32(bitPattern: theme.list.itemAccentColor.rgb), + "section_bg_color": Int32(bitPattern: theme.list.itemBlocksBackgroundColor.rgb), + "section_header_text_color": Int32(bitPattern: theme.list.freeTextColor.rgb), + "subtitle_text_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb), + "destructive_text_color": Int32(bitPattern: theme.list.itemDestructiveColor.rgb), + "section_separator_color": Int32(bitPattern: theme.list.itemBlocksSeparatorColor.rgb) ] }