mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Web app improvements
This commit is contained in:
parent
90943b78b6
commit
67d34e1ee9
@ -97,7 +97,8 @@ public class AttachmentController: ViewController {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||||
private let chatLocation: ChatLocation
|
private let chatLocation: ChatLocation
|
||||||
private var buttons: [AttachmentButtonType]
|
private let buttons: [AttachmentButtonType]
|
||||||
|
private let initialButton: AttachmentButtonType
|
||||||
|
|
||||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||||
get {
|
get {
|
||||||
@ -268,7 +269,20 @@ public class AttachmentController: ViewController {
|
|||||||
|
|
||||||
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||||
|
|
||||||
let _ = self.switchToController(.gallery)
|
if let controller = self.controller {
|
||||||
|
let _ = self.switchToController(controller.initialButton)
|
||||||
|
if case let .app(botId, _, _) = controller.initialButton {
|
||||||
|
if let index = controller.buttons.firstIndex(where: {
|
||||||
|
if case let .app(otherBotId, _, _) = $0, otherBotId == botId {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
self.panel.updateSelectedIndex(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateSelectionCount(_ count: Int) {
|
private func updateSelectionCount(_ count: Int) {
|
||||||
@ -293,25 +307,6 @@ public class AttachmentController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func switchTo(_ type: AttachmentButtonType) {
|
|
||||||
guard let buttons = self.controller?.buttons else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if case let .app(botId, _, _) = type {
|
|
||||||
let index = buttons.firstIndex(where: {
|
|
||||||
if case let .app(otherBotId, _, _) = $0, otherBotId == botId {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if let index = index {
|
|
||||||
self.panel.updateSelectedIndex(index)
|
|
||||||
let _ = self.switchToController(buttons[index], animated: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func switchToController(_ type: AttachmentButtonType, animated: Bool = true) -> Bool {
|
func switchToController(_ type: AttachmentButtonType, animated: Bool = true) -> Bool {
|
||||||
guard self.currentType != type else {
|
guard self.currentType != type else {
|
||||||
if self.animating {
|
if self.animating {
|
||||||
@ -583,13 +578,12 @@ public class AttachmentController: ViewController {
|
|||||||
completion(nil, nil)
|
completion(nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var buttonsDisposable: Disposable?
|
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery) {
|
||||||
|
|
||||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatLocation: ChatLocation, buttons: Signal<[AttachmentButtonType], NoError>) {
|
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updatedPresentationData = updatedPresentationData
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.chatLocation = chatLocation
|
self.chatLocation = chatLocation
|
||||||
self.buttons = []
|
self.buttons = buttons
|
||||||
|
self.initialButton = initialButton
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
@ -602,21 +596,6 @@ public class AttachmentController: ViewController {
|
|||||||
strongSelf.node.scrollToTop()
|
strongSelf.node.scrollToTop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.buttonsDisposable = (buttons
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] buttons in
|
|
||||||
if let strongSelf = self {
|
|
||||||
let previousButtons = strongSelf.buttons
|
|
||||||
strongSelf.buttons = buttons
|
|
||||||
if let layout = strongSelf.validLayout {
|
|
||||||
strongSelf.containerLayoutUpdated(layout, transition: !previousButtons.isEmpty ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.buttonsDisposable?.dispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public required init(coder aDecoder: NSCoder) {
|
public required init(coder aDecoder: NSCoder) {
|
||||||
@ -632,10 +611,6 @@ public class AttachmentController: ViewController {
|
|||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func switchTo(_ type: AttachmentButtonType) {
|
|
||||||
(self.displayNode as! Node).switchTo(type)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func _dismiss() {
|
public func _dismiss() {
|
||||||
super.dismiss(animated: false, completion: {})
|
super.dismiss(animated: false, completion: {})
|
||||||
}
|
}
|
||||||
|
@ -10497,6 +10497,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let context = self.context
|
||||||
|
|
||||||
let inputIsActive = self.presentationInterfaceState.inputMode == .text
|
let inputIsActive = self.presentationInterfaceState.inputMode == .text
|
||||||
|
|
||||||
self.chatDisplayNode.dismissInput()
|
self.chatDisplayNode.dismissInput()
|
||||||
@ -10535,254 +10537,220 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
isScheduledMessages = true
|
isScheduledMessages = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var switchToBotImpl: ((AttachmentButtonType) -> Void)?
|
let buttons: Signal<([AttachmentButtonType], AttachmentButtonType?), NoError>
|
||||||
var switchToBotId = botId
|
|
||||||
let buttons: Signal<[AttachmentButtonType], NoError>
|
|
||||||
if let _ = peer as? TelegramUser, !isScheduledMessages {
|
if let _ = peer as? TelegramUser, !isScheduledMessages {
|
||||||
buttons = .single(availableButtons)
|
buttons = self.context.engine.messages.attachMenuBots()
|
||||||
|> then(
|
|> map { attachMenuBots in
|
||||||
self.context.engine.messages.attachMenuBots()
|
var buttons = availableButtons
|
||||||
|> map { attachMenuBots in
|
var initialButton: AttachmentButtonType?
|
||||||
var buttons = availableButtons
|
if botId == nil {
|
||||||
for bot in attachMenuBots.reversed() {
|
initialButton = .gallery
|
||||||
let peerTitle = EnginePeer(bot.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
|
||||||
buttons.insert(.app(bot.peer.id, peerTitle, bot.icon), at: 1)
|
|
||||||
}
|
|
||||||
return buttons
|
|
||||||
}
|
}
|
||||||
) |> afterNext { buttons in
|
for bot in attachMenuBots.reversed() {
|
||||||
if let botId = switchToBotId, let button = buttons.first(where: {
|
let peerTitle = EnginePeer(bot.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
if case let .app(otherBotId, _,_) = $0, botId == otherBotId {
|
let button: AttachmentButtonType = .app(bot.peer.id, peerTitle, bot.icon)
|
||||||
return true
|
buttons.insert(button, at: 1)
|
||||||
} else {
|
|
||||||
return false
|
if initialButton == nil && bot.peer.id == botId {
|
||||||
|
initialButton = button
|
||||||
}
|
}
|
||||||
}) {
|
|
||||||
Queue.mainQueue().justDispatch {
|
|
||||||
switchToBotImpl?(button)
|
|
||||||
}
|
|
||||||
switchToBotId = nil
|
|
||||||
}
|
}
|
||||||
|
return (buttons, initialButton)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
buttons = .single(availableButtons)
|
buttons = .single((availableButtons, .gallery))
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
let _ = (buttons
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] buttons, initialButton in
|
||||||
let currentMediaController = Atomic<MediaPickerScreen?>(value: nil)
|
|
||||||
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
|
||||||
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
|
|
||||||
|
|
||||||
let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, chatLocation: self.chatLocation, buttons: buttons)
|
|
||||||
switchToBotImpl = { [weak attachmentController] button in
|
|
||||||
attachmentController?.switchTo(button)
|
|
||||||
}
|
|
||||||
attachmentController.requestController = { [weak self, weak attachmentController] type, completion in
|
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch type {
|
|
||||||
case .gallery:
|
guard let initialButton = initialButton else {
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
if let botId = botId {
|
||||||
let existingController = currentMediaController.with { $0 }
|
let _ = (strongSelf.context.engine.data.get(
|
||||||
if let controller = existingController {
|
TelegramEngine.EngineData.Item.Peer.Peer(id: botId)
|
||||||
completion(controller, controller.mediaPickerContext)
|
)
|
||||||
controller.prepareForReuse()
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
return
|
guard let strongSelf = self, let peer = peer else {
|
||||||
}
|
|
||||||
strongSelf.presentMediaPicker(bannedSendMedia: bannedSendMedia, present: { controller, mediaPickerContext in
|
|
||||||
let _ = currentMediaController.swap(controller)
|
|
||||||
if !inputText.string.isEmpty {
|
|
||||||
mediaPickerContext?.setCaption(inputText)
|
|
||||||
}
|
|
||||||
completion(controller, mediaPickerContext)
|
|
||||||
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
|
||||||
attachmentController?.mediaPickerContext = mediaPickerContext
|
|
||||||
}, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
|
|
||||||
if !inputText.string.isEmpty {
|
|
||||||
self?.clearInputText()
|
|
||||||
}
|
|
||||||
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
|
||||||
})
|
|
||||||
case .file:
|
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
|
||||||
let existingController = currentFilesController.with { $0 }
|
|
||||||
if let controller = existingController {
|
|
||||||
completion(controller, nil)
|
|
||||||
controller.prepareForReuse()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let controller = attachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, bannedSendMedia: bannedSendMedia, presentGallery: { [weak self, weak attachmentController] in
|
|
||||||
attachmentController?.dismiss(animated: true)
|
|
||||||
self?.presentFileGallery()
|
|
||||||
}, presentFiles: { [weak self, weak attachmentController] in
|
|
||||||
attachmentController?.dismiss(animated: true)
|
|
||||||
self?.presentICloudFileGallery()
|
|
||||||
}, send: { [weak self] mediaReference in
|
|
||||||
guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: mediaReference, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil)
|
|
||||||
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: strongSelf.transformEnqueueMessages([message]))
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
|
||||||
if let strongSelf = self, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
|
||||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
let _ = currentFilesController.swap(controller)
|
|
||||||
completion(controller, nil)
|
|
||||||
case .location:
|
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
|
||||||
let existingController = currentLocationController.with { $0 }
|
|
||||||
if let controller = existingController {
|
|
||||||
completion(controller, nil)
|
|
||||||
controller.prepareForReuse()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let selfPeerId: PeerId
|
|
||||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
|
||||||
selfPeerId = peer.id
|
|
||||||
} else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.hasPermission(.canBeAnonymous) {
|
|
||||||
selfPeerId = peer.id
|
|
||||||
} else {
|
|
||||||
selfPeerId = strongSelf.context.account.peerId
|
|
||||||
}
|
|
||||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Peer? in
|
|
||||||
return transaction.getPeer(selfPeerId)
|
|
||||||
}
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] selfPeer in
|
|
||||||
guard let strongSelf = self, let selfPeer = selfPeer else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages
|
|
||||||
let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: peer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _ in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
let _ = (strongSelf.context.engine.messages.requestWebView(peerId: peer.id, botId: botId, url: nil, themeParams: nil, replyToMessageId: nil)
|
||||||
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: location), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil)
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
if let strongSelf = self, case let .requestConfirmation(botIcon) = result {
|
||||||
if let strongSelf = self {
|
if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canBeAddedToAttachMenu) {
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
let controller = addWebAppToAttachmentController(context: context, peerName: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peerIcon: botIcon, completion: {
|
||||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
let _ = context.engine.messages.addBotToAttachMenu(peerId: botId).start()
|
||||||
})
|
|
||||||
|
Queue.mainQueue().after(1.0, {
|
||||||
|
strongSelf.presentAttachmentBot(botId: botId)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
strongSelf.present(controller, in: .window(.root))
|
||||||
|
} else {
|
||||||
|
strongSelf.present(textAlertController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, nil)
|
})
|
||||||
strongSelf.sendMessages([message])
|
|
||||||
})
|
})
|
||||||
completion(controller, nil)
|
|
||||||
|
|
||||||
let _ = currentLocationController.swap(controller)
|
|
||||||
})
|
|
||||||
case .contact:
|
|
||||||
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true))
|
|
||||||
contactsController.presentScheduleTimePicker = { [weak self] completion in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.presentScheduleTimePicker(completion: completion)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
contactsController.navigationPresentation = .modal
|
return
|
||||||
completion(contactsController, contactsController.mediaPickerContext)
|
}
|
||||||
strongSelf.controllerNavigationDisposable.set((contactsController.result
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
||||||
if let strongSelf = self, let (peers, _, silent, scheduleTime, text) = peers {
|
|
||||||
var textEnqueueMessage: EnqueueMessage?
|
let currentMediaController = Atomic<MediaPickerScreen?>(value: nil)
|
||||||
if let text = text, text.length > 0 {
|
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
||||||
var attributes: [MessageAttribute] = []
|
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
|
||||||
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
|
|
||||||
if !entities.isEmpty {
|
let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: strongSelf.chatLocation, buttons: buttons, initialButton: initialButton)
|
||||||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
attachmentController.requestController = { [weak self, weak attachmentController] type, completion in
|
||||||
}
|
guard let strongSelf = self else {
|
||||||
textEnqueueMessage = .message(text: text.string, attributes: attributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil)
|
return
|
||||||
|
}
|
||||||
|
switch type {
|
||||||
|
case .gallery:
|
||||||
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
|
let existingController = currentMediaController.with { $0 }
|
||||||
|
if let controller = existingController {
|
||||||
|
completion(controller, controller.mediaPickerContext)
|
||||||
|
controller.prepareForReuse()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.presentMediaPicker(bannedSendMedia: bannedSendMedia, present: { controller, mediaPickerContext in
|
||||||
|
let _ = currentMediaController.swap(controller)
|
||||||
|
if !inputText.string.isEmpty {
|
||||||
|
mediaPickerContext?.setCaption(inputText)
|
||||||
}
|
}
|
||||||
if peers.count > 1 {
|
completion(controller, mediaPickerContext)
|
||||||
var enqueueMessages: [EnqueueMessage] = []
|
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
||||||
if let textEnqueueMessage = textEnqueueMessage {
|
attachmentController?.mediaPickerContext = mediaPickerContext
|
||||||
enqueueMessages.append(textEnqueueMessage)
|
}, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
|
||||||
|
if !inputText.string.isEmpty {
|
||||||
|
self?.clearInputText()
|
||||||
|
}
|
||||||
|
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
||||||
|
})
|
||||||
|
case .file:
|
||||||
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
|
let existingController = currentFilesController.with { $0 }
|
||||||
|
if let controller = existingController {
|
||||||
|
completion(controller, nil)
|
||||||
|
controller.prepareForReuse()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let controller = attachmentFileController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, bannedSendMedia: bannedSendMedia, presentGallery: { [weak self, weak attachmentController] in
|
||||||
|
attachmentController?.dismiss(animated: true)
|
||||||
|
self?.presentFileGallery()
|
||||||
|
}, presentFiles: { [weak self, weak attachmentController] in
|
||||||
|
attachmentController?.dismiss(animated: true)
|
||||||
|
self?.presentICloudFileGallery()
|
||||||
|
}, send: { [weak self] mediaReference in
|
||||||
|
guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: mediaReference, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil)
|
||||||
|
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: strongSelf.transformEnqueueMessages([message]))
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
|
if let strongSelf = self, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
|
||||||
|
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||||
}
|
}
|
||||||
for peer in peers {
|
})
|
||||||
var media: TelegramMediaContact?
|
})
|
||||||
switch peer {
|
let _ = currentFilesController.swap(controller)
|
||||||
case let .peer(contact, _, _):
|
completion(controller, nil)
|
||||||
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
|
case .location:
|
||||||
continue
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
}
|
let existingController = currentLocationController.with { $0 }
|
||||||
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
if let controller = existingController {
|
||||||
|
completion(controller, nil)
|
||||||
let phone = contactData.basicData.phoneNumbers[0].value
|
controller.prepareForReuse()
|
||||||
media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil)
|
return
|
||||||
case let .deviceContact(_, basicData):
|
}
|
||||||
guard !basicData.phoneNumbers.isEmpty else {
|
let selfPeerId: PeerId
|
||||||
continue
|
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||||
}
|
selfPeerId = peer.id
|
||||||
let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
} else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.hasPermission(.canBeAnonymous) {
|
||||||
|
selfPeerId = peer.id
|
||||||
let phone = contactData.basicData.phoneNumbers[0].value
|
} else {
|
||||||
media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil)
|
selfPeerId = strongSelf.context.account.peerId
|
||||||
|
}
|
||||||
|
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Peer? in
|
||||||
|
return transaction.getPeer(selfPeerId)
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] selfPeer in
|
||||||
|
guard let strongSelf = self, let selfPeer = selfPeer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages
|
||||||
|
let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: peer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||||
|
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: location), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil)
|
||||||
|
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||||
|
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}, nil)
|
||||||
|
strongSelf.sendMessages([message])
|
||||||
|
})
|
||||||
|
completion(controller, nil)
|
||||||
|
|
||||||
|
let _ = currentLocationController.swap(controller)
|
||||||
|
})
|
||||||
|
case .contact:
|
||||||
|
let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: true))
|
||||||
|
contactsController.presentScheduleTimePicker = { [weak self] completion in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.presentScheduleTimePicker(completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contactsController.navigationPresentation = .modal
|
||||||
|
completion(contactsController, contactsController.mediaPickerContext)
|
||||||
|
strongSelf.controllerNavigationDisposable.set((contactsController.result
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||||
|
if let strongSelf = self, let (peers, _, silent, scheduleTime, text) = peers {
|
||||||
|
var textEnqueueMessage: EnqueueMessage?
|
||||||
|
if let text = text, text.length > 0 {
|
||||||
|
var attributes: [MessageAttribute] = []
|
||||||
|
let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text))
|
||||||
|
if !entities.isEmpty {
|
||||||
|
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
||||||
|
}
|
||||||
|
textEnqueueMessage = .message(text: text.string, attributes: attributes, mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil)
|
||||||
|
}
|
||||||
|
if peers.count > 1 {
|
||||||
|
var enqueueMessages: [EnqueueMessage] = []
|
||||||
|
if let textEnqueueMessage = textEnqueueMessage {
|
||||||
|
enqueueMessages.append(textEnqueueMessage)
|
||||||
|
}
|
||||||
|
for peer in peers {
|
||||||
|
var media: TelegramMediaContact?
|
||||||
|
switch peer {
|
||||||
|
case let .peer(contact, _, _):
|
||||||
|
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||||
|
|
||||||
|
let phone = contactData.basicData.phoneNumbers[0].value
|
||||||
|
media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil)
|
||||||
|
case let .deviceContact(_, basicData):
|
||||||
|
guard !basicData.phoneNumbers.isEmpty else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||||
|
|
||||||
|
let phone = contactData.basicData.phoneNumbers[0].value
|
||||||
|
media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil)
|
||||||
|
}
|
||||||
|
|
||||||
if let media = media {
|
if let media = media {
|
||||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
|
||||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
|
||||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, nil)
|
|
||||||
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil)
|
|
||||||
enqueueMessages.append(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
|
|
||||||
} else if let peer = peers.first {
|
|
||||||
let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError>
|
|
||||||
switch peer {
|
|
||||||
case let .peer(contact, _, _):
|
|
||||||
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
|
||||||
let context = strongSelf.context
|
|
||||||
dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:]))
|
|
||||||
|> take(1)
|
|
||||||
|> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in
|
|
||||||
var stableId: String?
|
|
||||||
let queryPhoneNumber = formatPhoneNumber(phoneNumber)
|
|
||||||
outer: for (id, data) in basicData {
|
|
||||||
for phoneNumber in data.phoneNumbers {
|
|
||||||
if formatPhoneNumber(phoneNumber.value) == queryPhoneNumber {
|
|
||||||
stableId = id
|
|
||||||
break outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let stableId = stableId {
|
|
||||||
return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil))
|
|
||||||
|> take(1)
|
|
||||||
|> map { extendedData -> (Peer?, DeviceContactExtendedData?) in
|
|
||||||
return (contact, extendedData)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .single((contact, contactData))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case let .deviceContact(id, _):
|
|
||||||
dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil))
|
|
||||||
|> take(1)
|
|
||||||
|> map { extendedData -> (Peer?, DeviceContactExtendedData?) in
|
|
||||||
return (nil, extendedData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strongSelf.controllerNavigationDisposable.set((dataSignal
|
|
||||||
|> deliverOnMainQueue).start(next: { peerAndContactData in
|
|
||||||
if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 {
|
|
||||||
if contactData.isPrimitive {
|
|
||||||
let phone = contactData.basicData.phoneNumbers[0].value
|
|
||||||
let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil)
|
|
||||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -10791,71 +10759,131 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, nil)
|
}, nil)
|
||||||
|
let message = EnqueueMessage.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil)
|
||||||
var enqueueMessages: [EnqueueMessage] = []
|
enqueueMessages.append(message)
|
||||||
if let textEnqueueMessage = textEnqueueMessage {
|
|
||||||
enqueueMessages.append(textEnqueueMessage)
|
|
||||||
}
|
|
||||||
enqueueMessages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil))
|
|
||||||
strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
|
|
||||||
} else {
|
|
||||||
let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in
|
|
||||||
guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let phone = contactData.basicData.phoneNumbers[0].value
|
|
||||||
if let vCardData = contactData.serializedVCard() {
|
|
||||||
let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData)
|
|
||||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
|
||||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
|
||||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
var enqueueMessages: [EnqueueMessage] = []
|
|
||||||
if let textEnqueueMessage = textEnqueueMessage {
|
|
||||||
enqueueMessages.append(textEnqueueMessage)
|
|
||||||
}
|
|
||||||
enqueueMessages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil))
|
|
||||||
strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
|
|
||||||
}
|
|
||||||
}), completed: nil, cancelled: nil)
|
|
||||||
strongSelf.effectiveNavigationController?.pushViewController(contactController)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
|
||||||
|
} else if let peer = peers.first {
|
||||||
|
let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError>
|
||||||
|
switch peer {
|
||||||
|
case let .peer(contact, _, _):
|
||||||
|
guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!<Mobile>!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||||
|
let context = strongSelf.context
|
||||||
|
dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:]))
|
||||||
|
|> take(1)
|
||||||
|
|> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in
|
||||||
|
var stableId: String?
|
||||||
|
let queryPhoneNumber = formatPhoneNumber(phoneNumber)
|
||||||
|
outer: for (id, data) in basicData {
|
||||||
|
for phoneNumber in data.phoneNumbers {
|
||||||
|
if formatPhoneNumber(phoneNumber.value) == queryPhoneNumber {
|
||||||
|
stableId = id
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let stableId = stableId {
|
||||||
|
return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil))
|
||||||
|
|> take(1)
|
||||||
|
|> map { extendedData -> (Peer?, DeviceContactExtendedData?) in
|
||||||
|
return (contact, extendedData)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single((contact, contactData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .deviceContact(id, _):
|
||||||
|
dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil))
|
||||||
|
|> take(1)
|
||||||
|
|> map { extendedData -> (Peer?, DeviceContactExtendedData?) in
|
||||||
|
return (nil, extendedData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strongSelf.controllerNavigationDisposable.set((dataSignal
|
||||||
|
|> deliverOnMainQueue).start(next: { peerAndContactData in
|
||||||
|
if let strongSelf = self, let contactData = peerAndContactData.1, contactData.basicData.phoneNumbers.count != 0 {
|
||||||
|
if contactData.isPrimitive {
|
||||||
|
let phone = contactData.basicData.phoneNumbers[0].value
|
||||||
|
let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peerAndContactData.0?.id, vCardData: nil)
|
||||||
|
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||||
|
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||||
|
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
var enqueueMessages: [EnqueueMessage] = []
|
||||||
|
if let textEnqueueMessage = textEnqueueMessage {
|
||||||
|
enqueueMessages.append(textEnqueueMessage)
|
||||||
|
}
|
||||||
|
enqueueMessages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil))
|
||||||
|
strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
|
||||||
|
} else {
|
||||||
|
let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: strongSelf.context, subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in
|
||||||
|
guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let phone = contactData.basicData.phoneNumbers[0].value
|
||||||
|
if let vCardData = contactData.serializedVCard() {
|
||||||
|
let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData)
|
||||||
|
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||||
|
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||||
|
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
var enqueueMessages: [EnqueueMessage] = []
|
||||||
|
if let textEnqueueMessage = textEnqueueMessage {
|
||||||
|
enqueueMessages.append(textEnqueueMessage)
|
||||||
|
}
|
||||||
|
enqueueMessages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: replyMessageId, localGroupingKey: nil, correlationId: nil))
|
||||||
|
strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
|
||||||
|
}
|
||||||
|
}), completed: nil, cancelled: nil)
|
||||||
|
strongSelf.effectiveNavigationController?.pushViewController(contactController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}))
|
||||||
|
case .poll:
|
||||||
|
let controller = strongSelf.configurePollCreation()
|
||||||
|
completion(controller, nil)
|
||||||
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
|
case let .app(botId, botName, botIcon):
|
||||||
|
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||||
|
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id, botId: botId, botName: botName, url: nil, queryId: nil, buttonText: nil, keepAliveSignal: nil, replyToMessageId: replyMessageId, iconFile: botIcon)
|
||||||
|
controller.getNavigationController = { [weak self] in
|
||||||
|
return self?.effectiveNavigationController
|
||||||
}
|
}
|
||||||
}))
|
completion(controller, nil)
|
||||||
case .poll:
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
let controller = strongSelf.configurePollCreation()
|
|
||||||
completion(controller, nil)
|
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
|
||||||
case let .app(botId, botName, botIcon):
|
|
||||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
|
||||||
let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peer.id, botId: botId, botName: botName, url: nil, queryId: nil, buttonText: nil, keepAliveSignal: nil, replyToMessageId: replyMessageId, iconFile: botIcon)
|
|
||||||
controller.getNavigationController = { [weak self] in
|
|
||||||
return self?.effectiveNavigationController
|
|
||||||
}
|
}
|
||||||
completion(controller, nil)
|
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
|
||||||
}
|
}
|
||||||
}
|
let present = {
|
||||||
let present = {
|
strongSelf.present(attachmentController, in: .window(.root))
|
||||||
self.present(attachmentController, in: .window(.root))
|
strongSelf.attachmentController = attachmentController
|
||||||
self.attachmentController = attachmentController
|
}
|
||||||
}
|
|
||||||
|
if inputIsActive {
|
||||||
if inputIsActive {
|
Queue.mainQueue().after(0.15, {
|
||||||
Queue.mainQueue().after(0.15, {
|
present()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
present()
|
present()
|
||||||
})
|
}
|
||||||
} else {
|
})
|
||||||
present()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func oldPresentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) {
|
private func oldPresentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) {
|
||||||
|
@ -1269,7 +1269,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
}
|
}
|
||||||
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: string)
|
badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: string)
|
||||||
}
|
}
|
||||||
var animated: Bool = animated
|
var animated = animated
|
||||||
if let updatingMedia = attributes.updatingMedia, case .update = updatingMedia.media {
|
if let updatingMedia = attributes.updatingMedia, case .update = updatingMedia.media {
|
||||||
state = .progress(color: messageTheme.mediaOverlayControlColors.foregroundColor, lineWidth: nil, value: CGFloat(updatingMedia.progress), cancelEnabled: true, animateRotation: true)
|
state = .progress(color: messageTheme.mediaOverlayControlColors.foregroundColor, lineWidth: nil, value: CGFloat(updatingMedia.progress), cancelEnabled: true, animateRotation: true)
|
||||||
} else if var fetchStatus = self.fetchStatus {
|
} else if var fetchStatus = self.fetchStatus {
|
||||||
@ -1497,6 +1497,14 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
self.statusNode = nil
|
self.statusNode = nil
|
||||||
removeStatusNode = true
|
removeStatusNode = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var animated = animated
|
||||||
|
if case .download = statusNode.state, case .progress = state {
|
||||||
|
animated = true
|
||||||
|
} else if case .progress = statusNode.state, case .download = state {
|
||||||
|
animated = true
|
||||||
|
}
|
||||||
|
|
||||||
statusNode.transitionToState(state, animated: animated, completion: { [weak statusNode] in
|
statusNode.transitionToState(state, animated: animated, completion: { [weak statusNode] in
|
||||||
if removeStatusNode {
|
if removeStatusNode {
|
||||||
statusNode?.removeFromSupernode()
|
statusNode?.removeFromSupernode()
|
||||||
|
@ -554,7 +554,11 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
let _ = (context.engine.messages.attachMenuBots()
|
let _ = (context.engine.messages.attachMenuBots()
|
||||||
|> deliverOnMainQueue).start(next: { attachMenuBots in
|
|> deliverOnMainQueue).start(next: { attachMenuBots in
|
||||||
if let _ = attachMenuBots.firstIndex(where: { $0.peer.id == peerId }) {
|
if let _ = attachMenuBots.firstIndex(where: { $0.peer.id == peerId }) {
|
||||||
presentError(presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError)
|
if let navigationController = navigationController, case let .chat(chatPeerId, _) = urlContext {
|
||||||
|
let _ = context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: chatPeerId), attachBotId: peerId, useExisting: true))
|
||||||
|
} else {
|
||||||
|
presentError(presentationData.strings.WebApp_AddToAttachmentAlreadyAddedError)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let _ = (context.engine.data.get(
|
let _ = (context.engine.data.get(
|
||||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||||
@ -581,6 +585,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
presentError(presentationData.strings.Login_UnknownError)
|
presentError(presentationData.strings.Login_UnknownError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, error: { _ in
|
||||||
|
presentError(presentationData.strings.Login_UnknownError)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -710,7 +710,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
if let path = parsedUrl.pathComponents.last {
|
if let path = parsedUrl.pathComponents.last {
|
||||||
var section: ResolvedUrlSettingsSection?
|
var section: ResolvedUrlSettingsSection?
|
||||||
switch path {
|
switch path {
|
||||||
case "theme":
|
case "themes":
|
||||||
section = .theme
|
section = .theme
|
||||||
case "devices":
|
case "devices":
|
||||||
section = .devices
|
section = .devices
|
||||||
|
@ -45,6 +45,68 @@ public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) ->
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class LoadingProgressNode: ASDisplayNode {
|
||||||
|
var color: UIColor {
|
||||||
|
didSet {
|
||||||
|
self.foregroundNode.backgroundColor = self.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let foregroundNode: ASDisplayNode
|
||||||
|
|
||||||
|
init(color: UIColor) {
|
||||||
|
self.color = color
|
||||||
|
|
||||||
|
self.foregroundNode = ASDisplayNode()
|
||||||
|
self.foregroundNode.backgroundColor = color
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.foregroundNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _progress: CGFloat = 0.0
|
||||||
|
func updateProgress(_ progress: CGFloat, animated: Bool = false) {
|
||||||
|
if self._progress == progress && animated {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var animated = animated
|
||||||
|
if (progress < self._progress && animated) {
|
||||||
|
animated = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = self.bounds.size
|
||||||
|
|
||||||
|
self._progress = progress
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition
|
||||||
|
if animated && progress > 0.0 {
|
||||||
|
transition = .animated(duration: 0.7, curve: .spring)
|
||||||
|
} else {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
|
let alpaTransition: ContainedViewLayoutTransition
|
||||||
|
if animated {
|
||||||
|
alpaTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||||
|
} else {
|
||||||
|
alpaTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.foregroundNode, frame: CGRect(x: -2.0, y: 0.0, width: (size.width + 4.0) * progress, height: size.height))
|
||||||
|
|
||||||
|
let alpha: CGFloat = progress < 0.001 || progress > 0.999 ? 0.0 : 1.0
|
||||||
|
alpaTransition.updateAlpha(node: self.foregroundNode, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layout() {
|
||||||
|
super.layout()
|
||||||
|
|
||||||
|
self.foregroundNode.cornerRadius = self.frame.height / 2.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class WebAppController: ViewController, AttachmentContainable {
|
public final class WebAppController: ViewController, AttachmentContainable {
|
||||||
public var requestAttachmentMenuExpansion: () -> Void = { }
|
public var requestAttachmentMenuExpansion: () -> Void = { }
|
||||||
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||||
@ -59,6 +121,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
private var placeholderIcon: UIImage?
|
private var placeholderIcon: UIImage?
|
||||||
private var placeholderNode: ShimmerEffectNode?
|
private var placeholderNode: ShimmerEffectNode?
|
||||||
|
|
||||||
|
private let loadingProgressNode: LoadingProgressNode
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
var presentationData: PresentationData
|
var presentationData: PresentationData
|
||||||
private let present: (ViewController, Any?) -> Void
|
private let present: (ViewController, Any?) -> Void
|
||||||
@ -73,6 +137,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
self.presentationData = controller.presentationData
|
self.presentationData = controller.presentationData
|
||||||
self.present = present
|
self.present = present
|
||||||
|
|
||||||
|
self.loadingProgressNode = LoadingProgressNode(color: presentationData.theme.rootController.tabBar.selectedIconColor)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 {
|
if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 {
|
||||||
@ -133,12 +199,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
webView.allowsBackForwardNavigationGestures = false
|
webView.allowsBackForwardNavigationGestures = false
|
||||||
webView.scrollView.delegate = self
|
webView.scrollView.delegate = self
|
||||||
|
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: [], context: nil)
|
||||||
self.webView = webView
|
self.webView = webView
|
||||||
|
|
||||||
let placeholderNode = ShimmerEffectNode()
|
let placeholderNode = ShimmerEffectNode()
|
||||||
self.addSubnode(placeholderNode)
|
self.addSubnode(placeholderNode)
|
||||||
self.placeholderNode = placeholderNode
|
self.placeholderNode = placeholderNode
|
||||||
|
|
||||||
|
if controller.buttonText == nil {
|
||||||
|
self.addSubnode(self.loadingProgressNode)
|
||||||
|
}
|
||||||
|
|
||||||
if let iconFile = controller.iconFile {
|
if let iconFile = controller.iconFile {
|
||||||
let _ = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .standalone(media: iconFile)).start()
|
let _ = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .standalone(media: iconFile)).start()
|
||||||
self.iconDisposable = (svgIconImageFile(account: self.context.account, fileReference: .standalone(media: iconFile))
|
self.iconDisposable = (svgIconImageFile(account: self.context.account, fileReference: .standalone(media: iconFile))
|
||||||
@ -156,22 +227,24 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if let url = controller.url, let queryId = controller.queryId, let keepAliveSignal = controller.keepAliveSignal {
|
if let url = controller.url {
|
||||||
self.queryId = queryId
|
self.queryId = controller.queryId
|
||||||
if let parsedUrl = URL(string: url) {
|
if let parsedUrl = URL(string: url) {
|
||||||
self.webView?.load(URLRequest(url: parsedUrl))
|
self.webView?.load(URLRequest(url: parsedUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.keepAliveDisposable = (keepAliveSignal
|
if let keepAliveSignal = controller.keepAliveSignal {
|
||||||
|> deliverOnMainQueue).start(error: { [weak self] _ in
|
self.keepAliveDisposable = (keepAliveSignal
|
||||||
if let strongSelf = self {
|
|> deliverOnMainQueue).start(error: { [weak self] _ in
|
||||||
strongSelf.controller?.dismiss()
|
if let strongSelf = self {
|
||||||
}
|
strongSelf.controller?.dismiss()
|
||||||
}, completed: { [weak self] in
|
}
|
||||||
if let strongSelf = self {
|
}, completed: { [weak self] in
|
||||||
strongSelf.controller?.dismiss()
|
if let strongSelf = self {
|
||||||
}
|
strongSelf.controller?.dismiss()
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let _ = (context.engine.messages.requestWebView(peerId: controller.peerId, botId: controller.botId, url: controller.url, themeParams: generateWebAppThemeParams(presentationData.theme), replyToMessageId: controller.replyToMessageId)
|
let _ = (context.engine.messages.requestWebView(peerId: controller.peerId, botId: controller.botId, url: controller.url, themeParams: generateWebAppThemeParams(presentationData.theme), replyToMessageId: controller.replyToMessageId)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
@ -205,6 +278,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
deinit {
|
deinit {
|
||||||
self.iconDisposable?.dispose()
|
self.iconDisposable?.dispose()
|
||||||
self.keepAliveDisposable?.dispose()
|
self.keepAliveDisposable?.dispose()
|
||||||
|
|
||||||
|
self.webView?.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
@ -272,14 +347,23 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
|
|
||||||
let height: CGFloat
|
let height: CGFloat
|
||||||
if case .compact = layout.metrics.widthClass {
|
if case .compact = layout.metrics.widthClass {
|
||||||
height = layout.size.height - attachmentDefaultTopInset(layout: layout) - 56.0
|
height = layout.size.height - attachmentDefaultTopInset(layout: layout) - layout.intrinsicInsets.bottom - 14.0
|
||||||
} else {
|
} else {
|
||||||
height = layout.size.height - 56.0
|
height = layout.size.height - layout.intrinsicInsets.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
let placeholderFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - iconSize.width) / 2.0), y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)
|
let placeholderFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - iconSize.width) / 2.0), y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)
|
||||||
transition.updateFrame(node: placeholderNode, frame: placeholderFrame)
|
transition.updateFrame(node: placeholderNode, frame: placeholderFrame)
|
||||||
placeholderNode.updateAbsoluteRect(placeholderFrame, within: layout.size)
|
placeholderNode.updateAbsoluteRect(placeholderFrame, within: layout.size)
|
||||||
|
|
||||||
|
let loadingProgressHeight: CGFloat = 2.0
|
||||||
|
transition.updateFrame(node: self.loadingProgressNode, frame: CGRect(origin: CGPoint(x: 0.0, y: height - loadingProgressHeight), size: CGSize(width: layout.size.width, height: loadingProgressHeight)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if keyPath == "estimatedProgress", let webView = self.webView {
|
||||||
|
self.loadingProgressNode.updateProgress(webView.estimatedProgress, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user