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

This commit is contained in:
Ilya Laktyushin 2024-04-22 19:28:37 +04:00
commit ca415e3145
32 changed files with 308 additions and 104 deletions

View File

@ -12132,3 +12132,5 @@ Sorry for the inconvenience.";
"Channel.AdminLogFilter.Section.SettingsGroup" = "Group Settings";
"Channel.AdminLogFilter.Section.SettingsChannel" = "Channel Settings";
"Channel.AdminLogFilter.Section.Messages" = "Messages";
"Premium.Gift.ContactSelection.AddBirthday" = "Add Your Birthday";

View File

@ -93,7 +93,7 @@ public final class ContactMultiselectionControllerParams {
public let context: AccountContext
public let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
public let mode: ContactMultiselectionControllerMode
public let options: [ContactListAdditionalOption]
public let options: Signal<[ContactListAdditionalOption], NoError>
public let filters: [ContactListFilter]
public let onlyWriteable: Bool
public let isGroupInvitation: Bool
@ -105,7 +105,7 @@ public final class ContactMultiselectionControllerParams {
public let openProfile: ((EnginePeer) -> Void)?
public let sendMessage: ((EnginePeer) -> Void)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isGroupInvitation: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: Signal<[ContactListAdditionalOption], NoError> = .single([]), filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isGroupInvitation: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.mode = mode

View File

@ -760,7 +760,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
selectedChats: Set(filterData.includePeers.peers),
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
chatListFilters: allFilters
)), options: [], filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in
)), filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in
if count >= premiumLimit {
let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: min(premiumLimit, count), action: {
return true
@ -913,7 +913,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
selectedChats: Set(filterData.excludePeers),
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
chatListFilters: allFilters
)), options: [], filters: [], alwaysEnabled: true, limit: 100))
)), filters: [], alwaysEnabled: true, limit: 100))
controller.navigationPresentation = .modal
let _ = (controller.result
|> take(1)

View File

@ -1770,25 +1770,32 @@ public final class ContactListNode: ASDisplayNode {
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty {
isEmpty = true
}
let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers.map { $0.peer }, topPeersPresentation: displayTopPeers, interaction: interaction)
let previous = previousEntries.swap(entries)
let previousSelection = previousSelectionState.swap(selectionState)
let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds)
var hadPermissionInfo = false
var previousOptionsCount = 0
if let previous = previous {
for entry in previous {
if case .permissionInfo = entry {
hadPermissionInfo = true
break
}
if case .option = entry {
previousOptionsCount += 1
}
}
}
var hasPermissionInfo = false
var optionsCount = 0
for entry in entries {
if case .permissionInfo = entry {
hasPermissionInfo = true
break
}
if case .option = entry {
optionsCount += 1
}
}
@ -1799,6 +1806,8 @@ public final class ContactListNode: ASDisplayNode {
animation = .insertion
} else if hadPermissionInfo != hasPermissionInfo {
animation = .insertion
} else if optionsCount < previousOptionsCount {
animation = .insertion
} else {
animation = .none
}

View File

@ -313,7 +313,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
}
} else if let file = content.file {
if content.type == "telegram_background" {
if let wallpaper = parseWallpaperUrl(content.url) {
if let wallpaper = parseWallpaperUrl(sharedContext: item.context.sharedContext, url: content.url) {
switch wallpaper {
case let .slug(slug, _, colors, intensity, angle):
previewWallpaperFileReference = .message(message: MessageReference(message), media: file)

View File

@ -38,6 +38,7 @@ private struct LocationViewTransaction {
let updates: [ListViewUpdateItem]
let gotTravelTimes: Bool
let count: Int
let animated: Bool
}
private enum LocationViewEntryId: Hashable {
@ -197,14 +198,14 @@ private enum LocationViewEntry: Comparable, Identifiable {
}
}
private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?, gotTravelTimes: Bool) -> LocationViewTransaction {
private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntries: [LocationViewEntry], context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?, gotTravelTimes: Bool, animated: Bool) -> LocationViewTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates, gotTravelTimes: gotTravelTimes, count: toEntries.count)
return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates, gotTravelTimes: gotTravelTimes, count: toEntries.count, animated: animated)
}
enum LocationViewLocation: Equatable {
@ -573,7 +574,27 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let previousState = previousState.swap(state)
let previousHadTravelTimes = previousHadTravelTimes.swap(!travelTimes.isEmpty)
let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction, gotTravelTimes: !travelTimes.isEmpty && !previousHadTravelTimes)
var animated = false
var previousActionsCount = 0
var actionsCount = 0
if let previousEntries {
for entry in previousEntries {
if case .toggleLiveLocation = entry {
previousActionsCount += 1
}
}
}
for entry in entries {
if case .toggleLiveLocation = entry {
actionsCount += 1
}
}
if actionsCount < previousActionsCount {
animated = true
}
let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction, gotTravelTimes: !travelTimes.isEmpty && !previousHadTravelTimes, animated: animated)
strongSelf.enqueueTransition(transition)
strongSelf.headerNode.updateState(mapMode: state.mapMode, trackingMode: state.trackingMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: proximityNotification, animated: false)
@ -787,7 +808,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
scrollToItem = nil
}
let options = ListViewDeleteAndInsertOptions()
var options = ListViewDeleteAndInsertOptions()
if transition.animated {
options.insert(.AnimateInsertion)
}
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in
})
}

View File

@ -518,7 +518,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
)
|> deliverOnMainQueue).start(next: { chatPeer, exportedInvitation, members in
let disabledIds = members?.compactMap({$0.peer.id}) ?? []
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: [], filters: [.excludeSelf, .disable(disabledIds)], onlyWriteable: true, isGroupInvitation: true))
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), filters: [.excludeSelf, .disable(disabledIds)], onlyWriteable: true, isGroupInvitation: true))
addMembersDisposable.set((
contactsController.result

View File

@ -2276,7 +2276,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
nextImpl = { [weak controller] in
if let controller = controller {
if case .initialSetup = mode {
let selectionController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .channelCreation, options: [], onlyWriteable: true))
let selectionController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .channelCreation, onlyWriteable: true))
(controller.navigationController as? NavigationController)?.replaceAllButRootController(selectionController, animated: true)
let _ = (selectionController.result
|> deliverOnMainQueue).start(next: { [weak selectionController] result in

View File

@ -310,10 +310,10 @@ public enum ProxySettingsControllerMode {
public func proxySettingsController(context: AccountContext, mode: ProxySettingsControllerMode = .default) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
return proxySettingsController(accountManager: context.sharedContext.accountManager, context: context, postbox: context.account.postbox, network: context.account.network, mode: mode, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData)
return proxySettingsController(accountManager: context.sharedContext.accountManager, sharedContext: context.sharedContext, context: context, postbox: context.account.postbox, network: context.account.network, mode: mode, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData)
}
public func proxySettingsController(accountManager: AccountManager<TelegramAccountManagerTypes>, context: AccountContext? = nil, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal<PresentationData, NoError>) -> ViewController {
public func proxySettingsController(accountManager: AccountManager<TelegramAccountManagerTypes>, sharedContext: SharedAccountContext, context: AccountContext? = nil, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal<PresentationData, NoError>) -> ViewController {
var pushControllerImpl: ((ViewController) -> Void)?
var dismissImpl: (() -> Void)?
let stateValue = Atomic(value: ProxySettingsControllerState())
@ -341,7 +341,7 @@ public func proxySettingsController(accountManager: AccountManager<TelegramAccou
return current
}).start()
}, addNewServer: {
pushControllerImpl?(proxyServerSettingsController(presentationData: presentationData, updatedPresentationData: updatedPresentationData, accountManager: accountManager, network: network, currentSettings: nil))
pushControllerImpl?(proxyServerSettingsController(sharedContext: sharedContext, presentationData: presentationData, updatedPresentationData: updatedPresentationData, accountManager: accountManager, network: network, currentSettings: nil))
}, activateServer: { server in
let _ = updateProxySettingsInteractively(accountManager: accountManager, { current in
var current = current
@ -354,7 +354,7 @@ public func proxySettingsController(accountManager: AccountManager<TelegramAccou
return current
}).start()
}, editServer: { server in
pushControllerImpl?(proxyServerSettingsController(presentationData: presentationData, updatedPresentationData: updatedPresentationData, accountManager: accountManager, network: network, currentSettings: server))
pushControllerImpl?(proxyServerSettingsController(sharedContext: sharedContext, presentationData: presentationData, updatedPresentationData: updatedPresentationData, accountManager: accountManager, network: network, currentSettings: server))
}, removeServer: { server in
let _ = updateProxySettingsInteractively(accountManager: accountManager, { current in
var current = current

View File

@ -265,10 +265,10 @@ private func proxyServerSettings(with state: ProxyServerSettingsControllerState)
public func proxyServerSettingsController(context: AccountContext, currentSettings: ProxyServerSettings? = nil) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
return proxyServerSettingsController(context: context, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, accountManager: context.sharedContext.accountManager, network: context.account.network, currentSettings: currentSettings)
return proxyServerSettingsController(sharedContext: context.sharedContext, context: context, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, accountManager: context.sharedContext.accountManager, network: context.account.network, currentSettings: currentSettings)
}
func proxyServerSettingsController(context: AccountContext? = nil, presentationData: PresentationData, updatedPresentationData: Signal<PresentationData, NoError>, accountManager: AccountManager<TelegramAccountManagerTypes>, network: Network, currentSettings: ProxyServerSettings?) -> ViewController {
func proxyServerSettingsController(sharedContext: SharedAccountContext, context: AccountContext? = nil, presentationData: PresentationData, updatedPresentationData: Signal<PresentationData, NoError>, accountManager: AccountManager<TelegramAccountManagerTypes>, network: Network, currentSettings: ProxyServerSettings?) -> ViewController {
var currentMode: ProxyServerSettingsControllerMode = .socks5
var currentUsername: String?
var currentPassword: String?
@ -285,7 +285,7 @@ func proxyServerSettingsController(context: AccountContext? = nil, presentationD
currentMode = .mtp
}
} else {
if let proxy = parseProxyUrl(UIPasteboard.general.string ?? "") {
if let proxy = parseProxyUrl(sharedContext: sharedContext, url: UIPasteboard.general.string ?? "") {
if let secret = proxy.secret, let parsedSecret = MTProxySecret.parseData(secret) {
pasteboardSettings = ProxyServerSettings(host: proxy.host, port: proxy.port, connection: .mtp(secret: parsedSecret.serialize()))
} else {

View File

@ -315,7 +315,6 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32,
chatListFilters: nil,
displayAutoremoveTimeout: true
)),
options: [],
filters: [.excludeSelf],
isPeerEnabled: { peer in
var canManage = false

View File

@ -1141,7 +1141,7 @@ public func selectivePrivacySettingsController(
onlyUsers: false,
disableChannels: true,
disableBots: false
)), options: [], filters: [.excludeSelf]))
)), filters: [.excludeSelf]))
addPeerDisposable.set((controller.result
|> take(1)
|> deliverOnMainQueue).start(next: { [weak controller] result in

View File

@ -355,7 +355,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
onlyUsers: false,
disableChannels: true,
disableBots: false
)), options: [], alwaysEnabled: true))
)), alwaysEnabled: true))
addPeerDisposable.set((controller.result
|> take(1)
|> deliverOnMainQueue).start(next: { [weak controller] result in

View File

@ -93,8 +93,7 @@ final class AccountTaskManager {
tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .emoji).start())
tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .status).start())
tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .avatar).start())
tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .chatStickers).start())
tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .greetingStickers).start())
tasks.add(managedSynchronizeEmojiSearchCategories(postbox: self.stateManager.postbox, network: self.stateManager.network, kind: .combinedChatStickers).start())
tasks.add(managedSynchronizeAttachMenuBots(accountPeerId: self.accountPeerId, postbox: self.stateManager.postbox, network: self.stateManager.network, force: true).start())
tasks.add(managedSynchronizeNotificationSoundList(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedChatListFilters(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())

View File

@ -8,8 +8,7 @@ public final class EmojiSearchCategories: Equatable, Codable {
case emoji = 0
case status = 1
case avatar = 2
case chatStickers = 3
case greetingStickers = 4
case combinedChatStickers = 3
}
public struct Group: Codable, Equatable {
@ -17,16 +16,25 @@ public final class EmojiSearchCategories: Equatable, Codable {
case id
case title
case identifiers
case kind
}
public enum Kind: Int32, Codable {
case generic
case greeting
case premium
}
public var id: Int64
public var title: String
public var identifiers: [String]
public var kind: Kind
public init(id: Int64, title: String, identifiers: [String]) {
public init(id: Int64, title: String, identifiers: [String], kind: Kind) {
self.id = id
self.title = title
self.identifiers = identifiers
self.kind = kind
}
public init(from decoder: Decoder) throws {
@ -35,6 +43,16 @@ public final class EmojiSearchCategories: Equatable, Codable {
self.id = try container.decode(Int64.self, forKey: .id)
self.title = try container.decode(String.self, forKey: .title)
self.identifiers = try container.decode([String].self, forKey: .identifiers)
self.kind = ((try container.decodeIfPresent(Int32.self, forKey: .kind)).flatMap(Kind.init(rawValue:))) ?? .generic
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.title, forKey: .title)
try container.encode(self.identifiers, forKey: .identifiers)
try container.encode(self.kind.rawValue, forKey: .kind)
}
}
@ -130,9 +148,8 @@ func managedSynchronizeEmojiSearchCategories(postbox: Postbox, network: Network,
|> `catch` { _ -> Signal<Api.messages.EmojiGroups, NoError> in
return .single(.emojiGroupsNotModified)
}
//TODO:localize
case .chatStickers, .greetingStickers:
signal = network.request(Api.functions.messages.getEmojiGroups(hash: current?.hash ?? 0))
case .combinedChatStickers:
signal = network.request(Api.functions.messages.getEmojiStickerGroups(hash: current?.hash ?? 0))
|> `catch` { _ -> Signal<Api.messages.EmojiGroups, NoError> in
return .single(.emojiGroupsNotModified)
}
@ -150,10 +167,24 @@ func managedSynchronizeEmojiSearchCategories(postbox: Postbox, network: Network,
case let .emojiGroup(title, iconEmojiId, emoticons):
return EmojiSearchCategories.Group(
id: iconEmojiId,
title: title, identifiers: emoticons
title: title,
identifiers: emoticons,
kind: .generic
)
case let .emojiGroupGreeting(title, iconEmojiId, emoticons):
return EmojiSearchCategories.Group(
id: iconEmojiId,
title: title,
identifiers: emoticons,
kind: .greeting
)
case let .emojiGroupPremium(title, iconEmojiId):
return EmojiSearchCategories.Group(
id: iconEmojiId,
title: title,
identifiers: [],
kind: .premium
)
case .emojiGroupGreeting, .emojiGroupPremium:
return nil
}
}
)

View File

@ -1193,21 +1193,23 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
recognizer.highlight = { [weak self] point in
if let strongSelf = self {
if let replyInfoNode = strongSelf.replyInfoNode {
var translatedPoint: CGPoint?
let convertedNodeFrame = replyInfoNode.view.convert(replyInfoNode.bounds, to: strongSelf.view)
if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) {
translatedPoint = strongSelf.view.convert(point, to: replyInfoNode.view)
if strongSelf.selectionNode == nil {
if let replyInfoNode = strongSelf.replyInfoNode {
var translatedPoint: CGPoint?
let convertedNodeFrame = replyInfoNode.view.convert(replyInfoNode.bounds, to: strongSelf.view)
if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) {
translatedPoint = strongSelf.view.convert(point, to: replyInfoNode.view)
}
replyInfoNode.updateTouchesAtPoint(translatedPoint)
}
replyInfoNode.updateTouchesAtPoint(translatedPoint)
}
if let forwardInfoNode = strongSelf.forwardInfoNode {
var translatedPoint: CGPoint?
let convertedNodeFrame = forwardInfoNode.view.convert(forwardInfoNode.bounds, to: strongSelf.view)
if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) {
translatedPoint = strongSelf.view.convert(point, to: forwardInfoNode.view)
if let forwardInfoNode = strongSelf.forwardInfoNode {
var translatedPoint: CGPoint?
let convertedNodeFrame = forwardInfoNode.view.convert(forwardInfoNode.bounds, to: strongSelf.view)
if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) {
translatedPoint = strongSelf.view.convert(point, to: forwardInfoNode.view)
}
forwardInfoNode.updateTouchesAtPoint(translatedPoint)
}
forwardInfoNode.updateTouchesAtPoint(translatedPoint)
}
for contentNode in strongSelf.contentNodes {
var translatedPoint: CGPoint?

View File

@ -85,6 +85,8 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
private var highlightColor: UIColor?
private var linkHighlightingNode: LinkHighlightingNode?
private var previousPeer: Peer?
public var openPsa: ((String, ASDisplayNode) -> Void)?
override public init() {
@ -114,6 +116,32 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
}
}
public func getBoundingRects() -> [CGRect] {
var initialRects: [CGRect] = []
let addRects: (TextNode, CGPoint, CGFloat) -> Void = { textNode, offset, additionalWidth in
guard let cachedLayout = textNode.cachedLayout else {
return
}
for rect in cachedLayout.linesRects() {
var rect = rect
rect.size.width += rect.origin.x + additionalWidth
rect.origin.x = 0.0
initialRects.append(rect.offsetBy(dx: offset.x, dy: offset.y))
}
}
let offsetY: CGFloat = -12.0
if let titleNode = self.titleNode {
addRects(titleNode, CGPoint(x: titleNode.frame.minX, y: offsetY + titleNode.frame.minY), 0.0)
if let nameNode = self.nameNode {
addRects(nameNode, CGPoint(x: titleNode.frame.minX, y: offsetY + nameNode.frame.minY), nameNode.frame.minX - titleNode.frame.minX)
}
}
return initialRects
}
public func updateTouchesAtPoint(_ point: CGPoint?) {
var isHighlighted = false
if point != nil {
@ -167,14 +195,19 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode)
let nameNodeLayout = TextNode.asyncLayout(maybeNode?.nameNode)
let previousPeer = maybeNode?.previousPeer
return { context, presentationData, strings, type, peer, authorName, psaType, storyData, constrainedSize in
let originalPeer = peer
let peer = peer ?? previousPeer
let fontSize = floor(presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)
let prefixFont = Font.regular(fontSize)
let peerFont = Font.medium(fontSize)
let peerString: String
if let peer = peer {
if let authorName = authorName {
if let authorName = authorName, originalPeer === peer {
peerString = "\(EnginePeer(peer).displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)) (\(authorName))"
} else {
peerString = EnginePeer(peer).displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)
@ -343,18 +376,16 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth, height: constrainedSize.height), alignment: .natural, cutout: cutout, insets: UIEdgeInsets()))
var authorAvatarInset: CGFloat = 0.0
authorAvatarInset = 20.0
var nameLayoutAndApply: (TextNodeLayout, () -> TextNode)?
if let authorString {
nameLayoutAndApply = nameNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: authorString, font: peerFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth, height: constrainedSize.height), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
nameLayoutAndApply = nameNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: authorString, font: peer != nil ? peerFont : prefixFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth - authorAvatarInset, height: constrainedSize.height), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
}
let titleAuthorSpacing: CGFloat = 0.0
var authorAvatarInset: CGFloat = 0.0
if peer != nil {
authorAvatarInset = 20.0
}
let resultSize: CGSize
if let nameLayoutAndApply {
resultSize = CGSize(
@ -379,6 +410,8 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
node.theme = presentationData.theme.theme
node.highlightColor = titleColor.withMultipliedAlpha(0.1)
node.previousPeer = peer
let titleNode = titleApply()
titleNode.displaysAsynchronously = !presentationData.isPreview
@ -398,7 +431,7 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
}
nameNode.frame = CGRect(origin: CGPoint(x: leftOffset + authorAvatarInset, y: titleLayout.size.height + titleAuthorSpacing), size: nameLayout.size)
if let peer, authorAvatarInset != 0.0 {
if authorAvatarInset != 0.0 {
let avatarNode: AvatarNode
if let current = node.avatarNode {
avatarNode = current
@ -410,7 +443,13 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
let avatarSize = CGSize(width: 16.0, height: 16.0)
avatarNode.frame = CGRect(origin: CGPoint(x: leftOffset, y: titleLayout.size.height + titleAuthorSpacing), size: avatarSize)
avatarNode.updateSize(size: avatarSize)
avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize)
if let peer {
avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize)
} else if let authorName, !authorName.isEmpty {
avatarNode.setCustomLetters([String(authorName[authorName.startIndex])])
} else {
avatarNode.setCustomLetters([" "])
}
} else {
if let avatarNode = node.avatarNode {
node.avatarNode = nil

View File

@ -58,6 +58,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
private var forwardInfoNode: ChatMessageForwardInfoNode?
private var forwardBackgroundContent: WallpaperBubbleBackgroundNode?
private var forwardBackgroundMaskNode: LinkHighlightingNode?
private var actionButtonsNode: ChatMessageActionButtonsNode?
private var reactionButtonsNode: ChatMessageReactionButtonsNode?
@ -1110,7 +1111,9 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
var forwardBackgroundFrame: CGRect?
if let forwardAreaFrame {
forwardBackgroundFrame = forwardAreaFrame.insetBy(dx: -6.0, dy: -3.0)
var forwardBackgroundFrameValue = forwardAreaFrame.insetBy(dx: -6.0, dy: -3.0)
forwardBackgroundFrameValue.size.height += 2.0
forwardBackgroundFrame = forwardBackgroundFrameValue
}
var replyBackgroundFrame: CGRect?
@ -1147,7 +1150,17 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
}
if let backgroundContent = strongSelf.forwardBackgroundContent, let forwardBackgroundFrame {
backgroundContent.cornerRadius = 4.0
let forwardBackgroundMaskNode: LinkHighlightingNode
if let current = strongSelf.forwardBackgroundMaskNode {
forwardBackgroundMaskNode = current
} else {
forwardBackgroundMaskNode = LinkHighlightingNode(color: .black)
forwardBackgroundMaskNode.inset = 4.0
forwardBackgroundMaskNode.outerRadius = 12.0
strongSelf.forwardBackgroundMaskNode = forwardBackgroundMaskNode
backgroundContent.view.mask = forwardBackgroundMaskNode.view
}
backgroundContent.frame = forwardBackgroundFrame
if let (rect, containerSize) = strongSelf.absoluteRect {
var backgroundFrame = backgroundContent.frame
@ -1155,6 +1168,28 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
if let forwardInfoNode = strongSelf.forwardInfoNode {
forwardBackgroundMaskNode.frame = backgroundContent.bounds.offsetBy(dx: forwardInfoNode.frame.minX - backgroundContent.frame.minX, dy: forwardInfoNode.frame.minY - backgroundContent.frame.minY)
var backgroundRects = forwardInfoNode.getBoundingRects()
for i in 0 ..< backgroundRects.count {
backgroundRects[i].origin.x -= 2.0
backgroundRects[i].size.width += 4.0
}
for i in 0 ..< backgroundRects.count {
if i != 0 {
if abs(backgroundRects[i - 1].maxX - backgroundRects[i].maxX) < 16.0 {
let maxMaxX = max(backgroundRects[i - 1].maxX, backgroundRects[i].maxX)
backgroundRects[i - 1].size.width = max(0.0, maxMaxX - backgroundRects[i - 1].origin.x)
backgroundRects[i].size.width = max(0.0, maxMaxX - backgroundRects[i].origin.x)
}
}
}
forwardBackgroundMaskNode.updateRects(backgroundRects)
}
} else if let forwardBackgroundMaskNode = strongSelf.forwardBackgroundMaskNode {
strongSelf.forwardBackgroundMaskNode = nil
forwardBackgroundMaskNode.view.removeFromSuperview()
}
let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0

View File

@ -315,7 +315,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
var colors: [UInt32] = []
var rotation: Int32?
var intensity: Int32?
if let wallpaper = parseWallpaperUrl(webpage.url), case let .slug(_, _, colorsValue, intensityValue, rotationValue) = wallpaper {
if let wallpaper = parseWallpaperUrl(sharedContext: item.context.sharedContext, url: webpage.url), case let .slug(_, _, colorsValue, intensityValue, rotationValue) = wallpaper {
colors = colorsValue
rotation = rotationValue
intensity = intensityValue
@ -353,7 +353,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
if type == "telegram_background" {
var colors: [UInt32] = []
var rotation: Int32?
if let wallpaper = parseWallpaperUrl(webpage.url) {
if let wallpaper = parseWallpaperUrl(sharedContext: item.context.sharedContext, url: webpage.url) {
if case let .color(color) = wallpaper {
colors = [color.rgb]
} else if case let .gradient(colorsValue, rotationValue) = wallpaper {

View File

@ -1663,10 +1663,36 @@ public extension EmojiPagerContentComponent {
switch subject {
case .groupPhotoEmojiSelection, .profilePhotoEmojiSelection:
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .avatar)
case .chatStickers:
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .chatStickers)
case .greetingStickers:
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .greetingStickers)
case .chatStickers, .greetingStickers:
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .combinedChatStickers)
|> map { result -> EmojiSearchCategories? in
guard let result else {
return nil
}
var groups: [EmojiSearchCategories.Group] = []
groups = result.groups
if case .greetingStickers = subject {
if let index = groups.firstIndex(where: { group in
return group.kind == .greeting
}) {
let group = groups.remove(at: index)
groups.insert(group, at: 0)
}
} else if case .chatStickers = subject {
if let index = groups.firstIndex(where: { group in
return group.kind == .premium
}) {
let group = groups.remove(at: index)
groups.append(group)
}
}
return EmojiSearchCategories(
hash: result.hash,
groups: groups
)
}
}
return combineLatest(

View File

@ -13097,7 +13097,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
}, clearHighlightAutomatically: true))
}
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: options, filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true))
let contactsController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, updatedPresentationData: updatedPresentationData, mode: .peerSelection(searchChatList: false, searchGroups: false, searchChannels: false), options: .single(options), filters: [.excludeSelf, .disable(recentIds)], onlyWriteable: true, isGroupInvitation: true))
contactsController.navigationPresentation = .modal
confirmationImpl = { [weak contactsController] peerId in

View File

@ -384,7 +384,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
chatListFilters: nil,
onlyUsers: true
)), options: [], filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in
)), filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in
}))
controller.navigationPresentation = .modal

View File

@ -242,7 +242,7 @@ final class BusinessRecipientListScreenComponent: Component {
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
chatListFilters: nil,
onlyUsers: true
)), options: [], filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in
)), filters: [], alwaysEnabled: true, limit: 100, reachedLimit: { _ in
}))
controller.navigationPresentation = .modal

View File

@ -2165,13 +2165,13 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
return (sharedApplicationContext.sharedContext, context, authContext)
}
}
|> deliverOnMainQueue).start(next: { _, context, authContext in
if let authContext = authContext, let confirmationCode = parseConfirmationCodeUrl(url) {
|> deliverOnMainQueue).start(next: { sharedContext, context, authContext in
if let authContext = authContext, let confirmationCode = parseConfirmationCodeUrl(sharedContext: sharedContext, url: url) {
authContext.rootController.applyConfirmationCode(confirmationCode)
} else if let context = context {
context.openUrl(url)
} else if let authContext = authContext {
if let proxyData = parseProxyUrl(url) {
if let proxyData = parseProxyUrl(sharedContext: sharedContext, url: url) {
authContext.rootController.view.endEditing(true)
let presentationData = authContext.sharedContext.currentPresentationData.with { $0 }
let controller = ProxyServerActionSheetController(presentationData: presentationData, accountManager: authContext.sharedContext.accountManager, postbox: authContext.account.postbox, network: authContext.account.network, server: proxyData, updatedPresentationData: nil)

View File

@ -4156,7 +4156,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if case .user = peerType, maxQuantity > 1 {
let presentationData = self.presentationData
var reachedLimitImpl: ((Int32) -> Void)?
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection, options: [], isPeerEnabled: { peer in
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection, isPeerEnabled: { peer in
if case let .user(user) = peer, user.botInfo == nil {
return true
} else {

View File

@ -856,7 +856,7 @@ extension ChatControllerImpl {
}
}, recognizedQRCode: { [weak self] code in
if let strongSelf = self {
if let (host, port, username, password, secret) = parseProxyUrl(code) {
if let (host, port, username, password, secret) = parseProxyUrl(sharedContext: strongSelf.context.sharedContext, url: code) {
strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil)
}
}
@ -1697,7 +1697,7 @@ extension ChatControllerImpl {
}
}, recognizedQRCode: { [weak self] code in
if let strongSelf = self {
if let (host, port, username, password, secret) = parseProxyUrl(code) {
if let (host, port, username, password, secret) = parseProxyUrl(sharedContext: strongSelf.context.sharedContext, url: code) {
strongSelf.openResolved(result: ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret), sourceMessageId: nil)
}
}

View File

@ -124,7 +124,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
self.contactsNode.openCreateNewGroup = { [weak self] in
if let strongSelf = self {
let controller = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .groupCreation, options: [], onlyWriteable: true))
let controller = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .groupCreation, onlyWriteable: true))
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller, completion: { [weak self] in
if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)

View File

@ -81,7 +81,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
private var limitsConfiguration: LimitsConfiguration?
private var limitsConfigurationDisposable: Disposable?
private var initialPeersDisposable: Disposable?
private let options: [ContactListAdditionalOption]
private let options: Signal<[ContactListAdditionalOption], NoError>
private let filters: [ContactListFilter]
private let onlyWriteable: Bool
private let isGroupInvitation: Bool

View File

@ -82,7 +82,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
private let onlyWriteable: Bool
private let isGroupInvitation: Bool
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: [ContactListAdditionalOption], filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) {
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?, options: Signal<[ContactListAdditionalOption], NoError>, filters: [ContactListFilter], onlyWriteable: Bool, isGroupInvitation: Bool, limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?, present: @escaping (ViewController, Any?) -> Void) {
self.navigationBar = navigationBar
self.context = context
@ -235,7 +235,13 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
} else {
displayTopPeers = .none
}
let contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, onlyWriteable: onlyWriteable, isGroupInvitation: isGroupInvitation, selectionState: ContactListNodeGroupSelectionState())
let presentation: Signal<ContactListPresentation, NoError> = options
|> map { options in
return .natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)
}
let contactListNode = ContactListNode(context: context, presentation: presentation, filters: filters, onlyWriteable: onlyWriteable, isGroupInvitation: isGroupInvitation, selectionState: ContactListNodeGroupSelectionState())
self.contentNode = .contacts(contactListNode)
if !selectedPeers.isEmpty {

View File

@ -25,8 +25,8 @@ public struct ParsedSecureIdUrl {
public let opaqueNonce: Data
}
public func parseProxyUrl(_ url: URL) -> ProxyServerSettings? {
guard let proxy = parseProxyUrl(url.absoluteString) else {
public func parseProxyUrl(sharedContext: SharedAccountContext, url: URL) -> ProxyServerSettings? {
guard let proxy = parseProxyUrl(sharedContext: sharedContext, url: url.absoluteString) else {
return nil
}
if let secret = proxy.secret, let _ = MTProxySecret.parseData(secret) {
@ -107,14 +107,14 @@ public func parseSecureIdUrl(_ url: URL) -> ParsedSecureIdUrl? {
return nil
}
public func parseConfirmationCodeUrl(_ url: URL) -> Int? {
public func parseConfirmationCodeUrl(sharedContext: SharedAccountContext, url: URL) -> Int? {
if url.pathComponents.count == 3 && url.pathComponents[1].lowercased() == "login" {
if let code = Int(url.pathComponents[2]) {
return code
}
}
if url.scheme == "tg" {
if let host = url.host, let query = url.query, let parsedUrl = parseInternalUrl(query: host + "?" + query) {
if let host = url.host, let query = url.query, let parsedUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query) {
switch parsedUrl {
case let .confirmationCode(code):
return code
@ -168,7 +168,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
return
}
guard let parsedUrl = parsedUrlValue else {
guard var parsedUrl = parsedUrlValue else {
return
}
@ -249,6 +249,20 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|> deliverOnMainQueue).startStandalone(next: handleResolvedUrl)
}
if context.sharedContext.immediateExperimentalUISettings.browserExperiment {
if let scheme = parsedUrl.scheme, (scheme == "tg" || scheme == context.sharedContext.applicationBindings.appSpecificScheme) {
if parsedUrl.host == "ipfs" {
if let value = URL(string: "ipfs:/" + parsedUrl.path) {
parsedUrl = value
}
}
} else if let scheme = parsedUrl.scheme, scheme == "https", parsedUrl.host == "t.me", parsedUrl.path.hasPrefix("/ipfs/") {
if let value = URL(string: "ipfs://" + String(parsedUrl.path[parsedUrl.path.index(parsedUrl.path.startIndex, offsetBy: "/ipfs/".count)...])) {
parsedUrl = value
}
}
}
if let scheme = parsedUrl.scheme, (scheme == "tg" || scheme == context.sharedContext.applicationBindings.appSpecificScheme) {
var convertedUrl: String?
if let query = parsedUrl.query {

View File

@ -2165,16 +2165,26 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mode = .premiumGifting(birthdays: nil, selectToday: false)
}
var contactOptions: [ContactListAdditionalOption] = []
let contactOptions: Signal<[ContactListAdditionalOption], NoError>
if currentBirthdays != nil || "".isEmpty {
contactOptions = [ContactListAdditionalOption(
title: "Add Your Birthday",
icon: .generic(UIImage(bundleImageName: "Contact List/AddBirthdayIcon")!),
action: {
presentBirthdayPickerImpl?()
},
clearHighlightAutomatically: true
)]
contactOptions = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId))
|> map { birthday in
if birthday == nil {
return [ContactListAdditionalOption(
title: presentationData.strings.Premium_Gift_ContactSelection_AddBirthday,
icon: .generic(UIImage(bundleImageName: "Contact List/AddBirthdayIcon")!),
action: {
presentBirthdayPickerImpl?()
},
clearHighlightAutomatically: true
)]
} else {
return []
}
}
|> deliverOnMainQueue
} else {
contactOptions = .single([])
}
var openProfileImpl: ((EnginePeer) -> Void)?
@ -2544,7 +2554,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
public func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController {
return proxySettingsController(accountManager: sharedContext.accountManager, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData)
return proxySettingsController(accountManager: sharedContext.accountManager, sharedContext: sharedContext, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData)
}
public func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController {

View File

@ -103,6 +103,7 @@ public enum ParsedInternalUrl {
case chatFolder(slug: String)
case premiumGiftCode(slug: String)
case messageLink(slug: String)
case externalUrl(url: String)
}
private enum ParsedUrl {
@ -110,7 +111,7 @@ private enum ParsedUrl {
case internalUrl(ParsedInternalUrl)
}
public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) -> ParsedInternalUrl? {
var query = query
if query.hasPrefix("s/") {
query = String(query[query.index(query.startIndex, offsetBy: 2)...])
@ -129,6 +130,12 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
if !pathComponents.isEmpty && !pathComponents[0].isEmpty {
let peerName: String = pathComponents[0]
if sharedContext.immediateExperimentalUISettings.browserExperiment {
if query.hasPrefix("ipfs/") {
return .externalUrl(url: "ipfs://" + String(query[query.index(query.startIndex, offsetBy: "ipfs/".count)...]))
}
}
if pathComponents[0].hasPrefix("+") || pathComponents[0].hasPrefix("%20") {
let component = pathComponents[0].replacingOccurrences(of: "%20", with: "+")
if component.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789+").inverted) == nil {
@ -1016,7 +1023,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
return .single(.result(.messageLink(link: result)))
}
})
case let .externalUrl(url):
return .single(.result(.externalUrl(url)))
}
}
@ -1046,20 +1054,20 @@ public func isTelegraPhLink(_ url: String) -> Bool {
return false
}
public func parseProxyUrl(_ url: String) -> (host: String, port: Int32, username: String?, password: String?, secret: Data?)? {
public func parseProxyUrl(sharedContext: SharedAccountContext, url: String) -> (host: String, port: Int32, username: String?, password: String?, secret: Data?)? {
let schemes = ["http://", "https://", ""]
for basePath in baseTelegramMePaths {
for scheme in schemes {
let basePrefix = scheme + basePath + "/"
if url.lowercased().hasPrefix(basePrefix) {
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])), case let .proxy(host, port, username, password, secret) = internalUrl {
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case let .proxy(host, port, username, password, secret) = internalUrl {
return (host, port, username, password, secret)
}
}
}
}
if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query {
if let internalUrl = parseInternalUrl(query: host + "?" + query), case let .proxy(host, port, username, password, secret) = internalUrl {
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case let .proxy(host, port, username, password, secret) = internalUrl {
return (host, port, username, password, secret)
}
}
@ -1067,20 +1075,20 @@ public func parseProxyUrl(_ url: String) -> (host: String, port: Int32, username
return nil
}
public func parseStickerPackUrl(_ url: String) -> String? {
public func parseStickerPackUrl(sharedContext: SharedAccountContext, url: String) -> String? {
let schemes = ["http://", "https://", ""]
for basePath in baseTelegramMePaths {
for scheme in schemes {
let basePrefix = scheme + basePath + "/"
if url.lowercased().hasPrefix(basePrefix) {
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])), case let .stickerPack(name, _) = internalUrl {
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case let .stickerPack(name, _) = internalUrl {
return name
}
}
}
}
if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query {
if let internalUrl = parseInternalUrl(query: host + "?" + query), case let .stickerPack(name, _) = internalUrl {
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case let .stickerPack(name, _) = internalUrl {
return name
}
}
@ -1088,20 +1096,20 @@ public func parseStickerPackUrl(_ url: String) -> String? {
return nil
}
public func parseWallpaperUrl(_ url: String) -> WallpaperUrlParameter? {
public func parseWallpaperUrl(sharedContext: SharedAccountContext, url: String) -> WallpaperUrlParameter? {
let schemes = ["http://", "https://", ""]
for basePath in baseTelegramMePaths {
for scheme in schemes {
let basePrefix = scheme + basePath + "/"
if url.lowercased().hasPrefix(basePrefix) {
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])), case let .wallpaper(wallpaper) = internalUrl {
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case let .wallpaper(wallpaper) = internalUrl {
return wallpaper
}
}
}
}
if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query {
if let internalUrl = parseInternalUrl(query: host + "?" + query), case let .wallpaper(wallpaper) = internalUrl {
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case let .wallpaper(wallpaper) = internalUrl {
return wallpaper
}
}
@ -1178,7 +1186,7 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String
url = basePrefix + String(url[scheme.endIndex...]).replacingOccurrences(of: ".\(basePath)/", with: "").replacingOccurrences(of: ".\(basePath)", with: "")
}
if url.lowercased().hasPrefix(basePrefix) {
if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])) {
if let internalUrl = parseInternalUrl(sharedContext: context.sharedContext, query: String(url[basePrefix.endIndex...])) {
return resolveInternalUrl(context: context, url: internalUrl)
|> map { result -> ResolveUrlResult in
switch result {