Phone numbers improvements

This commit is contained in:
Ilya Laktyushin 2024-05-16 00:50:11 +04:00
parent 55c38c8590
commit 6de753b14a
51 changed files with 914 additions and 233 deletions

View File

@ -12190,3 +12190,19 @@ Sorry for the inconvenience.";
"EmojiPacks.UnarchiveEmojiPacksConfirmation_1" = "Unarchive %@ Pack"; "EmojiPacks.UnarchiveEmojiPacksConfirmation_1" = "Unarchive %@ Pack";
"EmojiPacks.UnarchiveEmojiPacksConfirmation_any" = "Unarchive %@ Packs"; "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";

View File

@ -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 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<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController? func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController?
func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> 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 makePeersNearbyController(context: AccountContext) -> ViewController
func makeComposeController(context: AccountContext) -> ViewController func makeComposeController(context: AccountContext) -> ViewController
func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController 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<PresentationData, NoError> { 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])
}

View File

@ -971,6 +971,7 @@ public protocol ChatController: ViewController {
var chatLocation: ChatLocation { get } var chatLocation: ChatLocation { get }
var canReadHistory: ValuePromise<Bool> { get } var canReadHistory: ValuePromise<Bool> { get }
var parentController: ViewController? { get set } var parentController: ViewController? { get set }
var customNavigationController: NavigationController? { get set }
var purposefulAction: (() -> Void)? { get set } var purposefulAction: (() -> Void)? { get set }

View File

@ -28,13 +28,22 @@ public final class BotCheckoutController: ViewController {
} }
public static func fetch(context: AccountContext, source: BotPaymentInvoiceSource) -> Signal<InputData, FetchError> { public static func fetch(context: AccountContext, source: BotPaymentInvoiceSource) -> Signal<InputData, FetchError> {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let theme = context.sharedContext.currentPresentationData.with { $0 }.theme
let themeParams: [String: Any] = [ let themeParams: [String: Any] = [
"bg_color": Int32(bitPattern: presentationData.theme.list.plainBackgroundColor.argb), "bg_color": Int32(bitPattern: theme.list.plainBackgroundColor.rgb),
"text_color": Int32(bitPattern: presentationData.theme.list.itemPrimaryTextColor.argb), "secondary_bg_color": Int32(bitPattern: theme.list.blocksBackgroundColor.rgb),
"link_color": Int32(bitPattern: presentationData.theme.list.itemAccentColor.argb), "text_color": Int32(bitPattern: theme.list.itemPrimaryTextColor.rgb),
"button_color": Int32(bitPattern: presentationData.theme.list.itemCheckColors.fillColor.argb), "hint_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb),
"button_text_color": Int32(bitPattern: presentationData.theme.list.itemCheckColors.foregroundColor.argb) "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) return context.engine.payments.fetchBotPaymentForm(source: source, themeParams: themeParams)

View File

@ -23,6 +23,7 @@ import StoryContainerScreen
import ChatListHeaderComponent import ChatListHeaderComponent
import TelegramIntents import TelegramIntents
import UndoUI import UndoUI
import ShareController
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController private let controller: ViewController
@ -309,7 +310,7 @@ public class ContactsController: ViewController {
guard let strongSelf = self, let value = value else { guard let strongSelf = self, let value = value else {
return 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 { if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
} }
@ -741,7 +742,7 @@ public class ContactsController: ViewController {
case .allowed: case .allowed:
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", 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 { 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 { guard let strongSelf = self else {
return return
} }
@ -755,7 +756,7 @@ public class ContactsController: ViewController {
} }
} else { } else {
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { 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)) }), completed: nil, cancelled: nil))

View File

@ -121,6 +121,19 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
statusNode.attributedText = NSAttributedString(string: value, font: subtitleFont, textColor: presentationData.theme.contextMenu.secondaryColor) statusNode.attributedText = NSAttributedString(string: value, font: subtitleFont, textColor: presentationData.theme.contextMenu.secondaryColor)
statusNode.maximumNumberOfLines = 1 statusNode.maximumNumberOfLines = 1
self.statusNode = statusNode 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: case .multiline:
self.textNode.maximumNumberOfLines = 0 self.textNode.maximumNumberOfLines = 0
self.statusNode = nil 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) 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 { switch self.action.textLayout {
case let .secondLineWithValue(value): 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) 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: default:
break break
} }

View File

@ -36,6 +36,7 @@ public enum ContextMenuActionItemTextLayout {
case singleLine case singleLine
case twoLinesMax case twoLinesMax
case secondLineWithValue(String) case secondLineWithValue(String)
case secondLineWithAttributedValue(NSAttributedString)
case multiline case multiline
} }
@ -2150,6 +2151,7 @@ public protocol ContextExtractedContentSource: AnyObject {
var initialAppearanceOffset: CGPoint { get } var initialAppearanceOffset: CGPoint { get }
var centerVertically: Bool { get } var centerVertically: Bool { get }
var keepInPlace: Bool { get } var keepInPlace: Bool { get }
var adjustContentHorizontally: Bool { get }
var adjustContentForSideInset: Bool { get } var adjustContentForSideInset: Bool { get }
var ignoreContentTouches: Bool { get } var ignoreContentTouches: Bool { get }
var blurBackground: Bool { get } var blurBackground: Bool { get }
@ -2170,6 +2172,10 @@ public extension ContextExtractedContentSource {
return false return false
} }
var adjustContentHorizontally: Bool {
return false
}
var adjustContentForSideInset: Bool { var adjustContentForSideInset: Bool {
return false return false
} }

View File

@ -229,20 +229,6 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor 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 forcedHeight: CGFloat?
var titleVerticalOffset: CGFloat? var titleVerticalOffset: CGFloat?
let titleFont: UIFont let titleFont: UIFont
@ -265,6 +251,30 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)
let subtitleColor = presentationData.theme.contextMenu.secondaryColor 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 let titleColor: UIColor
switch self.item.textColor { switch self.item.textColor {
case .primary: case .primary:
@ -308,13 +318,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
self.titleLabelNode.isUserInteractionEnabled = self.titleLabelNode.tapAttributeAction != nil && self.item.action == nil self.titleLabelNode.isUserInteractionEnabled = self.titleLabelNode.tapAttributeAction != nil && self.item.action == nil
self.subtitleNode.attributedText = subtitle.flatMap { subtitle in self.subtitleNode.attributedText = subtitle
return NSAttributedString(
string: subtitle,
font: subtitleFont,
textColor: subtitleColor
)
}
var iconSize: CGSize? var iconSize: CGSize?
if let iconSource = self.item.iconSource { if let iconSource = self.item.iconSource {

View File

@ -1064,6 +1064,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if let contentNode = itemContentNode { 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) 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 case let .extracted(extracted) = self.source {
if extracted.adjustContentHorizontally {
contentFrame.origin.x = combinedActionsFrame.minX
}
if extracted.centerVertically { if extracted.centerVertically {
if combinedActionsFrame.height.isZero { if combinedActionsFrame.height.isZero {
contentFrame.origin.y = floorToScreenPixels((layout.size.height - contentFrame.height) / 2.0) 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 contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX
let contentWidth = contentNode.containingItem.view.bounds.size.width let contentWidth = contentNode.containingItem.view.bounds.size.width
let contentHeight = contentNode.containingItem.view.bounds.size.height 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 { if actionsSize.height.isZero {
var initialContentRect = contentRect var initialContentRect = contentRect
initialContentRect.origin.y += extracted.initialAppearanceOffset.y initialContentRect.origin.y += extracted.initialAppearanceOffset.y
@ -1429,7 +1435,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
var animationInContentXDistance: CGFloat = 0.0 var animationInContentXDistance: CGFloat = 0.0
let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX let contentX = contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX
let contentWidth = contentNode.containingItem.view.bounds.size.width 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 { if actionsSize.height.isZero {
// let fixedContentY = floorToScreenPixels((layout.size.height - contentHeight) / 2.0) // let fixedContentY = floorToScreenPixels((layout.size.height - contentHeight) / 2.0)
animationInContentYDistance = 0.0 //contentY - fixedContentY animationInContentYDistance = 0.0 //contentY - fixedContentY

View File

@ -330,7 +330,7 @@ public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor
return tintedImage 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 { guard let image = image else {
return nil return nil
} }
@ -357,7 +357,21 @@ public func generateGradientTintedImage(image: UIImage?, colors: [UIColor]) -> U
let colorSpace = DeviceGraphicsContextSettings.shared.colorSpace let colorSpace = DeviceGraphicsContextSettings.shared.colorSpace
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! 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 { } else if !colors.isEmpty {
context.setFillColor(colors[0].cgColor) context.setFillColor(colors[0].cgColor)
context.fill(imageRect) context.fill(imageRect)

View File

@ -1327,8 +1327,6 @@ private extension UIBezierPath {
} }
} }
extension UIImageView { extension UIImageView {
func setDrawingAnimatedImage(data: Data) { func setDrawingAnimatedImage(data: Data) {
DispatchQueue.global().async { DispatchQueue.global().async {
@ -1354,48 +1352,3 @@ extension UIImageView {
self.animationRepeatCount = 0 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
//}

View File

@ -1995,7 +1995,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, ASS
let changeEmailActionButtonFrame: CGRect let changeEmailActionButtonFrame: CGRect
let resendCodeActionFrame: CGRect let resendCodeActionFrame: CGRect
let resendCodeActionButtonFrame: 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)) 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) 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)) resendCodeActionButtonFrame = CGRect(origin: CGPoint(x: buttonFrame.minX, y: buttonFrame.maxY), size: CGSize(width: buttonFrame.width, height: buttonFrame.height))

View File

@ -24,6 +24,8 @@ import UndoUI
import GalleryUI import GalleryUI
import PeerAvatarGalleryUI import PeerAvatarGalleryUI
import Postbox import Postbox
import ShareController
import ContextUI
private enum DeviceContactInfoAction { private enum DeviceContactInfoAction {
case sendMessage case sendMessage
@ -33,7 +35,7 @@ private enum DeviceContactInfoAction {
} }
private final class DeviceContactInfoControllerArguments { private final class DeviceContactInfoControllerArguments {
let context: AccountContext let accountContext: AccountContext?
let isPlain: Bool let isPlain: Bool
let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void
let updatePhone: (Int64, String) -> Void let updatePhone: (Int64, String) -> Void
@ -50,8 +52,8 @@ private final class DeviceContactInfoControllerArguments {
let updateShareViaException: (Bool) -> Void let updateShareViaException: (Bool) -> Void
let openAvatar: (EnginePeer) -> 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) { 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.context = context self.accountContext = accountContext
self.isPlain = isPlain self.isPlain = isPlain
self.updateEditingName = updateEditingName self.updateEditingName = updateEditingName
self.updatePhone = updatePhone self.updatePhone = updatePhone
@ -404,7 +406,10 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
let arguments = arguments as! DeviceContactInfoControllerArguments let arguments = arguments as! DeviceContactInfoControllerArguments
switch self { switch self {
case let .info(_, _, _, dateTimeFormat, peer, state, jobSummary, _, hiddenAvatar): 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) arguments.updateEditingName(editingName)
}, avatarTapped: { }, avatarTapped: {
if peer.smallProfileImage != nil { 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 : "") 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 entries: [DeviceContactInfoEntry] = []
var editingName: ItemListAvatarAndNameInfoItemName? var editingName: ItemListAvatarAndNameInfoItemName?
@ -738,16 +743,20 @@ private func deviceContactInfoEntries(account: Account, engine: TelegramEngine,
var addressIndex = 0 var addressIndex = 0
for address in contactData.addresses { for address in contactData.addresses {
let signal = geocodeLocation(address: address.asPostalAddress) let signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>
if let context = context as? ShareControllerAppAccountContext {
signal = geocodeLocation(address: address.asPostalAddress)
|> mapToSignal { coordinates -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in |> mapToSignal { coordinates -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
if let (latitude, longitude) = coordinates { if let (latitude, longitude) = coordinates {
let resource = MapSnapshotMediaResource(latitude: latitude, longitude: longitude, width: 90, height: 90) let resource = MapSnapshotMediaResource(latitude: latitude, longitude: longitude, width: 90, height: 90)
return chatMapSnapshotImage(engine: engine, resource: resource) return chatMapSnapshotImage(engine: context.context.engine, resource: resource)
} else { } else {
return .single({ _ in return nil }) 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)) 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 addressIndex += 1
} }
@ -828,7 +837,7 @@ private final class DeviceContactInfoController: ItemListController, MFMessageCo
} }
} }
public func deviceContactInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { public func deviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController {
var initialState = DeviceContactInfoState() var initialState = DeviceContactInfoState()
if case let .create(peer, contactData, _, _, _) = subject { if case let .create(peer, contactData, _, _, _) = subject {
var peerPhoneNumber: String? var peerPhoneNumber: String?
@ -876,7 +885,11 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
var displayCopyContextMenuImpl: ((DeviceContactInfoEntryTag, String) -> Void)? var displayCopyContextMenuImpl: ((DeviceContactInfoEntryTag, String) -> Void)?
let presentationData = environment.presentationData
let callImpl: (String) -> Void = { number in let callImpl: (String) -> Void = { number in
guard let context = (context as? ShareControllerAppAccountContext)?.context else {
return
}
let user: Signal<TelegramUser?, NoError> let user: Signal<TelegramUser?, NoError>
if let peer = subject.peer { if let peer = subject.peer {
user = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) user = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id))
@ -890,10 +903,10 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
} else { } else {
user = .single(nil) user = .single(nil)
} }
let _ = (user let _ = (user
|> deliverOnMainQueue).start(next: { user in |> deliverOnMainQueue).start(next: { user in
if let user = user, let phone = user.phone, formatPhoneNumber(phone) == formatPhoneNumber(number) { 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 controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
@ -932,7 +945,13 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
shareViaException = shareViaExceptionValue 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 updateState { state in
var state = state var state = state
if let _ = state.editingState { if let _ = state.editingState {
@ -952,6 +971,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
return state return state
} }
}, updatePhoneLabel: { id, currentLabel in }, updatePhoneLabel: { id, currentLabel in
guard let context = (context as? ShareControllerAppAccountContext)?.context else {
return
}
pushControllerImpl?(phoneLabelController(context: context, currentLabel: currentLabel, completion: { value in pushControllerImpl?(phoneLabelController(context: context, currentLabel: currentLabel, completion: { value in
updateState { state in updateState { state in
var state = state var state = state
@ -1000,7 +1022,6 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
if subject.contactData.basicData.phoneNumbers.count == 1 { if subject.contactData.basicData.phoneNumbers.count == 1 {
inviteAction(subject.contactData.basicData.phoneNumbers[0].value) inviteAction(subject.contactData.basicData.phoneNumbers[0].value)
} else { } else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationData: presentationData) let controller = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak controller] in let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
@ -1020,7 +1041,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }
case .createContact: 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) dismissImpl?(false)
}), completed: nil, cancelled: nil)) }), completed: nil, cancelled: nil))
case .addToExisting: case .addToExisting:
@ -1059,9 +1080,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
}) })
let hiddenAvatarPromise = Promise<TelegramMediaImageRepresentation?>(nil) let hiddenAvatarPromise = Promise<TelegramMediaImageRepresentation?>(nil)
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let updatedPresentationData = updatedPresentationData?.signal ?? environment.updatedPresentationData
let previousEditingPhoneIds = Atomic<Set<Int64>?>(value: nil) let previousEditingPhoneIds = Atomic<Set<Int64>?>(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 |> map { presentationData, state, peerAndContactData, hiddenAvatar -> (ItemListControllerState, (ItemListNodeState, Any)) in
var presentationData = presentationData var presentationData = presentationData
let updatedTheme = presentationData.theme.withModalBlocksBackground() 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: { 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 { if let composedContactData = composedContactData {
guard let context = (context as? ShareControllerAppAccountContext)?.context else {
return
}
var addToPrivacyExceptions = false var addToPrivacyExceptions = false
updateState { state in updateState { state in
var state = state var state = state
@ -1129,7 +1153,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
if share, filteredPhoneNumbers.count <= 1, let peer = peer { 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) 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 |> 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: { }, completed: {
let _ = (contactDataManager.createContactWithData(composedContactData) let _ = (contactDataManager.createContactWithData(composedContactData)
|> deliverOnMainQueue).start(next: { contactIdAndData in |> deliverOnMainQueue).start(next: { contactIdAndData in
@ -1250,7 +1274,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
focusItemTag = DeviceContactInfoEntryTag.editingPhone(insertedPhoneId) 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)) return (controllerState, (listState, arguments))
} }
@ -1258,24 +1282,27 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
actionsDisposable.dispose() 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 controller.navigationPresentation = .modal
addToExistingImpl = { [weak controller] in addToExistingImpl = { [weak controller] in
guard let controller = controller else { guard let controller, let accountContext = (context as? ShareControllerAppAccountContext)?.context else {
return return
} }
addContactToExisting(context: context, parentController: controller, contactData: subject.contactData, completion: { peer, contactId, contactData in addContactToExisting(context: accountContext, parentController: controller, contactData: subject.contactData, completion: { peer, contactId, contactData in
replaceControllerImpl?(deviceContactInfoController(context: context, subject: .vcard(peer?._asPeer(), contactId, contactData), completed: nil, cancelled: nil)) replaceControllerImpl?(deviceContactInfoController(context: context, environment: environment, subject: .vcard(peer?._asPeer(), contactId, contactData), completed: nil, cancelled: nil))
}) })
} }
openChatImpl = { [weak controller] peerId in 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)) let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in |> deliverOnMainQueue).start(next: { [weak controller] peer in
guard let peer = peer else { guard let peer, let controller else {
return 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))) 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 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 openAddressImpl = { [weak controller] address in
guard let _ = controller else { guard let _ = controller else {
@ -1309,7 +1336,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
} }
} }
openUrlImpl = { [weak controller] url in openUrlImpl = { [weak controller] url in
guard let controller = controller else { guard let controller, let context = (context as? ShareControllerAppAccountContext)?.context else {
return 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 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 displayCopyContextMenuImpl = { [weak controller] tag, value in
if let strongController = controller { if let strongController = controller {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = environment.presentationData
var resultItemNode: ListViewItemNode? var resultItemNode: ListViewItemNode?
let _ = strongController.frameForItemNode({ itemNode in let _ = strongController.frameForItemNode({ itemNode in
if let itemNode = itemNode as? ItemListTextWithLabelItemNode { if let itemNode = itemNode as? ItemListTextWithLabelItemNode {
@ -1350,6 +1377,9 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta
} }
} }
openAvatarImpl = { [weak controller] peer in openAvatarImpl = { [weak controller] peer in
guard let context = (context as? ShareControllerAppAccountContext)?.context else {
return
}
let avatarController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { _, _ in let avatarController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { _, _ in
}) })
hiddenAvatarPromise.set( hiddenAvatarPromise.set(
@ -1413,7 +1443,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie
let _ = (dataSignal let _ = (dataSignal
|> deliverOnMainQueue).start(next: { peer, stableId in |> deliverOnMainQueue).start(next: { peer, stableId in
guard let stableId = stableId else { 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)) }), completed: nil, cancelled: nil), in: .window(.root))
return return
} }
@ -1459,7 +1489,7 @@ func addContactOptionsController(context: AccountContext, peer: EnginePeer?, con
controller.setItemGroups([ controller.setItemGroups([
ActionSheetItemGroup(items: [ ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Profile_CreateNewContact, action: { [weak controller] in 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)) }), completed: nil, cancelled: nil), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
dismissAction() dismissAction()
}), }),
@ -1476,3 +1506,32 @@ func addContactOptionsController(context: AccountContext, peer: EnginePeer?, con
]) ])
return controller 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))))
}

View File

@ -59,7 +59,7 @@ public func openAddPersonContactImpl(context: AccountContext, updatedPresentatio
shareViaException = statusSettings.contains(.addExceptionWhenAddingContact) 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 { if let peer = peer as? TelegramUser {
completion() completion()

View File

@ -304,18 +304,6 @@ private func collectExternalShareItems(strings: PresentationStrings, dateTimeFor
}) })
} }
public protocol ShareControllerEnvironment: AnyObject {
var presentationData: PresentationData { get }
var updatedPresentationData: Signal<PresentationData, NoError> { 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 { public final class ShareControllerAppEnvironment: ShareControllerEnvironment {
let sharedContext: SharedAccountContext 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 final class ShareControllerAppAccountContext: ShareControllerAccountContext {
public let context: AccountContext public let context: AccountContext

View File

@ -349,7 +349,6 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
var disabledPeerSelected: ((EnginePeer) -> Void)? var disabledPeerSelected: ((EnginePeer) -> Void)?
let ready = Promise<Bool>() let ready = Promise<Bool>()
private var didSetReady = false
private var controllerInteraction: ShareControllerInteraction? private var controllerInteraction: ShareControllerInteraction?

View File

@ -175,7 +175,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
break break
case .ignore: case .ignore:
return .fail 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 return .waitForSingleTap
} }
} }

View File

@ -141,6 +141,7 @@ public struct ChatMessageBubbleContentTapAction {
public enum Content { public enum Content {
case none case none
case url(Url) case url(Url)
case phone(String)
case textMention(String) case textMention(String)
case peerMention(peerId: PeerId, mention: String, openProfile: Bool) case peerMention(peerId: PeerId, mention: String, openProfile: Bool)
case botCommand(String) case botCommand(String)

View File

@ -770,24 +770,24 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
self.mainContextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtractedToContextPreview, _ in self.mainContextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtractedToContextPreview, _ in
guard let strongSelf = self, let _ = strongSelf.item else { guard let self, let _ = self.item else {
return return
} }
for contentNode in strongSelf.contentNodes { for contentNode in self.contentNodes {
contentNode.willUpdateIsExtractedToContextPreview(isExtractedToContextPreview) contentNode.willUpdateIsExtractedToContextPreview(isExtractedToContextPreview)
} }
} }
self.mainContextSourceNode.isExtractedToContextPreviewUpdated = { [weak self] isExtractedToContextPreview in self.mainContextSourceNode.isExtractedToContextPreviewUpdated = { [weak self] isExtractedToContextPreview in
guard let strongSelf = self else { guard let self else {
return return
} }
strongSelf.backgroundWallpaperNode.setMaskMode(strongSelf.backgroundMaskMode) self.backgroundWallpaperNode.setMaskMode(self.backgroundMaskMode)
strongSelf.backgroundNode.setMaskMode(strongSelf.backgroundMaskMode) self.backgroundNode.setMaskMode(self.backgroundMaskMode)
if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect { if !isExtractedToContextPreview, let (rect, size) = self.absoluteRect {
strongSelf.updateAbsoluteRect(rect, within: size) self.updateAbsoluteRect(rect, within: size)
} }
for contentNode in strongSelf.contentNodes { for contentNode in self.contentNodes {
contentNode.updateIsExtractedToContextPreview(isExtractedToContextPreview) contentNode.updateIsExtractedToContextPreview(isExtractedToContextPreview)
} }
} }
@ -1157,7 +1157,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
break break
case .ignore: case .ignore:
return .fail 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 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?())) item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url.url, concealed: url.concealed, message: item.content.firstMessage, allowInlineWebpageResolution: url.allowInlineWebpageResolution, progress: tapAction.activate?()))
}, contextMenuOnLongPress: !tapAction.hasLongTapAction)) }, 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): case let .peerMention(peerId, _, openProfile):
return .action(InternalBubbleTapAction.Action { [weak self] in return .action(InternalBubbleTapAction.Action { [weak self] in
if let item = self?.item { if let item = self?.item {
@ -4751,6 +4761,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} else { } else {
disableDefaultPressAnimation = true 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, _): case let .peerMention(peerId, mention, _):
return .action(InternalBubbleTapAction.Action { return .action(InternalBubbleTapAction.Action {
item.controllerInteraction.longTap(.peerMention(peerId, mention), message) item.controllerInteraction.longTap(.peerMention(peerId, mention), message)
@ -4818,6 +4838,39 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
return nil 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? { private func traceSelectionNodes(parent: ASDisplayNode, point: CGPoint) -> ASDisplayNode? {
if let parent = parent as? FileMessageSelectionNode, parent.bounds.contains(point) { if let parent = parent as? FileMessageSelectionNode, parent.bounds.contains(point) {
return parent return parent

View File

@ -857,7 +857,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
urlRange = urlRangeValue urlRange = urlRangeValue
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText) 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 { guard let self else {
return nil return nil
} }

View File

@ -614,6 +614,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, openRecommendedChannelContextMenu: { _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in
}, openGroupBoostInfo: { _, _ in }, openGroupBoostInfo: { _, _ in
}, openStickerEditor: { }, openStickerEditor: {
}, openPhoneContextMenu: { _ in
}, openAgeRestrictedMessageMedia: { _, _ in
}, requestMessageUpdate: { _, _ in }, requestMessageUpdate: { _, _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, dismissTextInput: { }, dismissTextInput: {

View File

@ -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<Bool>?
public init(number: String, message: Message, contentNode: ContextExtractedContentContainingNode, messageNode: ASDisplayNode, progress: Promise<Bool>? = nil) {
self.number = number
self.message = message
self.contentNode = contentNode
self.messageNode = messageNode
self.progress = progress
}
}
public let openMessage: (Message, OpenMessageParams) -> Bool public let openMessage: (Message, OpenMessageParams) -> Bool
public let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void public let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void
public let openPeerMention: (String, Promise<Bool>?) -> Void public let openPeerMention: (String, Promise<Bool>?) -> Void
@ -242,6 +258,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public let openRecommendedChannelContextMenu: (EnginePeer, UIView, ContextGesture?) -> Void public let openRecommendedChannelContextMenu: (EnginePeer, UIView, ContextGesture?) -> Void
public let openGroupBoostInfo: (EnginePeer.Id?, Int) -> Void public let openGroupBoostInfo: (EnginePeer.Id?, Int) -> Void
public let openStickerEditor: () -> 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 requestMessageUpdate: (MessageId, Bool) -> Void
public let cancelInteractiveKeyboardGestures: () -> Void public let cancelInteractiveKeyboardGestures: () -> Void
@ -367,6 +385,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
openRecommendedChannelContextMenu: @escaping (EnginePeer, UIView, ContextGesture?) -> Void, openRecommendedChannelContextMenu: @escaping (EnginePeer, UIView, ContextGesture?) -> Void,
openGroupBoostInfo: @escaping (EnginePeer.Id?, Int) -> Void, openGroupBoostInfo: @escaping (EnginePeer.Id?, Int) -> Void,
openStickerEditor: @escaping () -> Void, openStickerEditor: @escaping () -> Void,
openPhoneContextMenu: @escaping (OpenPhone) -> Void,
openAgeRestrictedMessageMedia: @escaping (Message, @escaping () -> Void) -> Void,
requestMessageUpdate: @escaping (MessageId, Bool) -> Void, requestMessageUpdate: @escaping (MessageId, Bool) -> Void,
cancelInteractiveKeyboardGestures: @escaping () -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void,
dismissTextInput: @escaping () -> Void, dismissTextInput: @escaping () -> Void,
@ -472,6 +492,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
self.openRecommendedChannelContextMenu = openRecommendedChannelContextMenu self.openRecommendedChannelContextMenu = openRecommendedChannelContextMenu
self.openGroupBoostInfo = openGroupBoostInfo self.openGroupBoostInfo = openGroupBoostInfo
self.openStickerEditor = openStickerEditor self.openStickerEditor = openStickerEditor
self.openPhoneContextMenu = openPhoneContextMenu
self.openAgeRestrictedMessageMedia = openAgeRestrictedMessageMedia
self.requestMessageUpdate = requestMessageUpdate self.requestMessageUpdate = requestMessageUpdate
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures

View File

@ -3326,6 +3326,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, openRecommendedChannelContextMenu: { _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in
}, openGroupBoostInfo: { _, _ in }, openGroupBoostInfo: { _, _ in
}, openStickerEditor: { }, openStickerEditor: {
}, openPhoneContextMenu: { _ in
}, openAgeRestrictedMessageMedia: { _, _ in
}, requestMessageUpdate: { _, _ in }, requestMessageUpdate: { _, _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, dismissTextInput: { }, dismissTextInput: {

View File

@ -492,19 +492,13 @@ public class ShareRootControllerImpl {
let displayShare: () -> Void = { let displayShare: () -> Void = {
var cancelImpl: (() -> Void)? var cancelImpl: (() -> Void)?
let _ = cancelImpl
let beginShare: () -> Void = { let beginShare: () -> Void = {
let requestUserInteraction: ([UnpreparedShareItemContent]) -> Signal<[PreparedShareItemContent], NoError> = { content in let requestUserInteraction: ([UnpreparedShareItemContent]) -> Signal<[PreparedShareItemContent], NoError> = { content in
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
switch content[0] { switch content[0] {
case let .contact(data): case let .contact(data):
#if !DEBUG let controller = deviceContactInfoController(context: context, environment: environment, subject: .filter(peer: nil, contactId: nil, contactData: data, completion: { peer, contactData in
//qwefqwfqwefw
#endif
let _ = data
let _ = self
/*let controller = deviceContactInfoController(context: context, subject: .filter(peer: nil, contactId: nil, contactData: data, completion: { peer, contactData in
let phone = contactData.basicData.phoneNumbers[0].value let phone = contactData.basicData.phoneNumbers[0].value
if let vCardData = contactData.serializedVCard() { 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))))]) 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 { if let strongSelf = self, let window = strongSelf.mainWindow {
controller.presentationArguments = ViewControllerPresentationArguments(presentationAnimation: .modalSheet) controller.presentationArguments = ViewControllerPresentationArguments(presentationAnimation: .modalSheet)
window.present(controller, on: .root) window.present(controller, on: .root)
}*/ }
break break
} }
return EmptyDisposable return EmptyDisposable

View File

@ -1762,7 +1762,7 @@ final class StoryItemSetContainerSendMessage {
self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
} else { } 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 { guard let self, let view else {
return return
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 …÷ý½F¢R¶Ë.×<16>X<03>0
é&ŠÄóçs§§§ós7S÷Œ<C592><7F>=ï~{ÿÿ¿<C3BF>÷þþËþë_Û»×<C397>´~üíýùøùz]ßY±Ôrmtµ1öLJí2ûþñéñÀ™ÃÄwm¹¨ôý`äên»x“c*Ilê˜d»A2§ö¼¹>H·RÖˆ§û“|/hز ÙÍUÇ<55>®¸Vä/ho°j•ªà,ïñYm\®ÄÖçòYñžyoÐYÝÝõêBŒ¬kЧî)g)ÝWˆÀ¯çhtÙwokyï« ìêš{´%@^ñf“è±rŸÄšP 4"ø:yøÐQ®3,˜´v¸Ò­k'žôJ†å.ÚVg¸[<5B>äË zl“QÛ ·H¸‰'®ÚÒdRSµ3R±Ò¦«Ï.¸þ@oÛ?›·¹¬;­#;ŠŽD/ÙÌ^PĘALyFæš`<60>‡UŸg…Ù¥Èâq"ëKuFÞF|"9zsff1@LJ!+3"•Â
À<EFBFBD><EFBFBD>µ„è/ä±­–Bˆ ƒg
fD¡jR\Ðl^VEîÄ(R<>U&£cøS9÷b×X·€\QúÀUÚ¢(-ƒ] ƒ}Ÿ64jF"¬ˆæ¡s¢¯ º—r<E28094>¤ë:éðZÑ'ÄW¶²¼ðJ$c9R¸<52>dc(p
F7¨þÁàè<>}[,m<>þ”l<E2809D>œW/š- U±±±jQYEéE7Òä´ôtZ{lƒÆQb ƒu<72>Ö¼Ñ;wm™T5t ¬Æ…Ü8¹0ÎJLgGYV˜¶:”‡ÜJ"'R t¥œ C ƒ£QWA<57>õ  ·¥ÌÏèì<C3A8>"C ËÁ5Xôæ“ÕwªÇéÚr@4;´äVsyÒ}–Áî>*¿Ž T\<ù“†4i¨.«z&n§¦~Ö2pqžšÒÕjáÎSƒç7c«aB<61>-zÀ5{@:ÃDµsvw0)tDƒaÉ'_u-<2D>K†¤b#9µ<E280BA>\}áHS­­:Òu(–û\…X¤Ï¨Š!U—…\”͉”BµsëÊžXUŠUí…N<4A>nXDvÄVˆÕ]§A…|Ýs1ñ÷OÛÇí<C387>í3Ü
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

View File

@ -1,7 +1,7 @@
{ {
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
}, },
"properties" : { "properties" : {
"provides-namespace" : true "provides-namespace" : true

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -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<Bool, NoError> {
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))
}
}

View File

@ -123,6 +123,7 @@ import PeerNameColorScreen
import ChatEmptyNode import ChatEmptyNode
import ChatMediaInputStickerGridItem import ChatMediaInputStickerGridItem
import AdsInfoScreen import AdsInfoScreen
import MessageUI
public enum ChatControllerPeekActions { public enum ChatControllerPeekActions {
case standard case standard
@ -224,6 +225,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var validLayout: ContainerViewLayout? var validLayout: ContainerViewLayout?
public weak var parentController: ViewController? public weak var parentController: ViewController?
public weak var customNavigationController: NavigationController?
let currentChatListFilter: Int32? let currentChatListFilter: Int32?
let chatNavigationStack: [ChatNavigationStackItem] let chatNavigationStack: [ChatNavigationStackItem]
@ -593,6 +595,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var networkSpeedEventsDisposable: Disposable? var networkSpeedEventsDisposable: Disposable?
var messageComposeController: MFMessageComposeViewController?
public var alwaysShowSearchResultsAsList: Bool = false { public var alwaysShowSearchResultsAsList: Bool = false {
didSet { didSet {
self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList)
@ -2295,7 +2299,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
}, openUrl: { [weak self] urlData in }, openUrl: { [weak self] urlData in
if let strongSelf = self { guard let strongSelf = self else {
return
}
let url = urlData.url let url = urlData.url
let concealed = urlData.concealed let concealed = urlData.concealed
let message = urlData.message let message = urlData.message
@ -2316,7 +2322,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else { } else {
strongSelf.openUrl(url, concealed: concealed, forceExternal: forceExternal, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution, progress: progress) strongSelf.openUrl(url, concealed: concealed, forceExternal: forceExternal, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution, progress: progress)
} }
}
}, shareCurrentLocation: { [weak self] in }, shareCurrentLocation: { [weak self] in
if let strongSelf = self { if let strongSelf = self {
if case .pinnedMessages = strongSelf.presentationInterfaceState.subject { if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
@ -4629,6 +4634,34 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
self.openStickerEditor() 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 }, requestMessageUpdate: { [weak self] id, scroll in
if let self { if let self {
self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll) self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll)
@ -9511,7 +9544,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
func addPeerContact() { func addPeerContact() {
if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser, let peerStatusSettings = self.presentationInterfaceState.contactStatus?.peerStatusSettings, let contactData = DeviceContactExtendedData(peer: EnginePeer(peer)) { 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 { guard let strongSelf = self else {
return return
} }
@ -10556,6 +10589,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else if case let .overlay(navigationController) = self.presentationInterfaceState.mode { } else if case let .overlay(navigationController) = self.presentationInterfaceState.mode {
return navigationController return navigationController
} else { } else {
if let navigationController = self.customNavigationController {
return navigationController
}
return nil return nil
} }
} }

View File

@ -32,6 +32,7 @@ import TelegramCallsUI
import AutomaticBusinessMessageSetupScreen import AutomaticBusinessMessageSetupScreen
import MediaEditorScreen import MediaEditorScreen
import CameraScreen import CameraScreen
import ShareController
extension ChatControllerImpl { extension ChatControllerImpl {
enum AttachMenuSubject { 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: [])) 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)) strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
} else { } 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 { guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else {
return 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: []) 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]) strongSelf.sendMessages([message])
} else { } 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 { guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else {
return return
} }

View File

@ -176,7 +176,8 @@ extension ChatControllerImpl {
}), in: .current) }), in: .current)
} }
self.push(calendarScreen) self.effectiveNavigationController?.pushViewController(calendarScreen)
dismissCalendarScreen = { [weak calendarScreen] in dismissCalendarScreen = { [weak calendarScreen] in
calendarScreen?.dismiss(completion: nil) calendarScreen?.dismiss(completion: nil)
} }

View File

@ -13,6 +13,7 @@ import SearchUI
import TelegramPermissionsUI import TelegramPermissionsUI
import AppBundle import AppBundle
import DeviceAccess import DeviceAccess
import ShareController
public class ComposeControllerImpl: ViewController, ComposeController { public class ComposeControllerImpl: ViewController, ComposeController {
private let context: AccountContext private let context: AccountContext
@ -200,7 +201,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
switch status { switch status {
case .allowed: case .allowed:
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", 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 { guard let strongSelf = self else {
return return
} }
@ -211,7 +212,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
} }
} }
} else { } 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)) }), completed: nil, cancelled: nil))
case .notDetermined: case .notDetermined:

View File

@ -7,6 +7,7 @@ import AccountContext
import AlertUI import AlertUI
import PresentationDataUtils import PresentationDataUtils
import PeerInfoUI import PeerInfoUI
import ShareController
func openAddContactImpl(context: AccountContext, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!<Mobile>!$_", present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void = {}) { func openAddContactImpl(context: AccountContext, firstName: String = "", lastName: String = "", phoneNumber: String, label: String = "_$!<Mobile>!$_", present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void = {}) {
let _ = (DeviceAccess.authorizationStatus(subject: .contacts) let _ = (DeviceAccess.authorizationStatus(subject: .contacts)
@ -15,13 +16,13 @@ func openAddContactImpl(context: AccountContext, firstName: String = "", lastNam
switch value { switch value {
case .allowed: 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: "") 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 peer = peer {
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
pushController(infoController) pushController(infoController)
} }
} else { } 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)) }), completed: completed, cancelled: nil), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
case .notDetermined: case .notDetermined:

View File

@ -330,7 +330,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
} else { } else {
contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName, lastName: contact.lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: contact.phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName, lastName: contact.lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", 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) params.navigationController?.pushViewController(controller)
}) })
return true return true

View File

@ -175,6 +175,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}, openRecommendedChannelContextMenu: { _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in
}, openGroupBoostInfo: { _, _ in }, openGroupBoostInfo: { _, _ in
}, openStickerEditor: { }, openStickerEditor: {
}, openPhoneContextMenu: { _ in
}, openAgeRestrictedMessageMedia: { _, _ in
}, requestMessageUpdate: { _, _ in }, requestMessageUpdate: { _, _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, dismissTextInput: { }, dismissTextInput: {

View File

@ -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) 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 { public func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController {
return deviceContactInfoController(context: context, subject: subject, completed: completed, cancelled: cancelled) return deviceContactInfoController(context: context, environment: environment, subject: subject, completed: completed, cancelled: cancelled)
} }
public func makePeersNearbyController(context: AccountContext) -> ViewController { public func makePeersNearbyController(context: AccountContext) -> ViewController {
@ -1769,6 +1769,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, openRecommendedChannelContextMenu: { _, _, _ in }, openRecommendedChannelContextMenu: { _, _, _ in
}, openGroupBoostInfo: { _, _ in }, openGroupBoostInfo: { _, _ in
}, openStickerEditor: { }, openStickerEditor: {
}, openPhoneContextMenu: { _ in
}, openAgeRestrictedMessageMedia: { _, _ in
}, requestMessageUpdate: { _, _ in }, requestMessageUpdate: { _, _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, dismissTextInput: { }, dismissTextInput: {

View File

@ -234,24 +234,22 @@ public struct WebAppParameters {
} }
} }
public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> [String: Any] { public func generateWebAppThemeParams(_ theme: PresentationTheme) -> [String: Any] {
let backgroundColor = presentationTheme.list.plainBackgroundColor.rgb
let secondaryBackgroundColor = presentationTheme.list.blocksBackgroundColor.rgb
return [ return [
"bg_color": Int32(bitPattern: backgroundColor), "bg_color": Int32(bitPattern: theme.list.plainBackgroundColor.rgb),
"secondary_bg_color": Int32(bitPattern: secondaryBackgroundColor), "secondary_bg_color": Int32(bitPattern: theme.list.blocksBackgroundColor.rgb),
"text_color": Int32(bitPattern: presentationTheme.list.itemPrimaryTextColor.rgb), "text_color": Int32(bitPattern: theme.list.itemPrimaryTextColor.rgb),
"hint_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb), "hint_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb),
"link_color": Int32(bitPattern: presentationTheme.list.itemAccentColor.rgb), "link_color": Int32(bitPattern: theme.list.itemAccentColor.rgb),
"button_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.fillColor.rgb), "button_color": Int32(bitPattern: theme.list.itemCheckColors.fillColor.rgb),
"button_text_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.foregroundColor.rgb), "button_text_color": Int32(bitPattern: theme.list.itemCheckColors.foregroundColor.rgb),
"header_bg_color": Int32(bitPattern: presentationTheme.rootController.navigationBar.opaqueBackgroundColor.rgb), "header_bg_color": Int32(bitPattern: theme.rootController.navigationBar.opaqueBackgroundColor.rgb),
"accent_text_color": Int32(bitPattern: presentationTheme.list.itemAccentColor.rgb), "accent_text_color": Int32(bitPattern: theme.list.itemAccentColor.rgb),
"section_bg_color": Int32(bitPattern: presentationTheme.list.itemBlocksBackgroundColor.rgb), "section_bg_color": Int32(bitPattern: theme.list.itemBlocksBackgroundColor.rgb),
"section_header_text_color": Int32(bitPattern: presentationTheme.list.freeTextColor.rgb), "section_header_text_color": Int32(bitPattern: theme.list.freeTextColor.rgb),
"subtitle_text_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb), "subtitle_text_color": Int32(bitPattern: theme.list.itemSecondaryTextColor.rgb),
"destructive_text_color": Int32(bitPattern: presentationTheme.list.itemDestructiveColor.rgb), "destructive_text_color": Int32(bitPattern: theme.list.itemDestructiveColor.rgb),
"section_separator_color": Int32(bitPattern: presentationTheme.list.itemBlocksSeparatorColor.rgb) "section_separator_color": Int32(bitPattern: theme.list.itemBlocksSeparatorColor.rgb)
] ]
} }