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 updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
private let chatLocation: ChatLocation
|
||||
private var buttons: [AttachmentButtonType]
|
||||
private let buttons: [AttachmentButtonType]
|
||||
private let initialButton: AttachmentButtonType
|
||||
|
||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||
get {
|
||||
@ -268,7 +269,20 @@ public class AttachmentController: ViewController {
|
||||
|
||||
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) {
|
||||
@ -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 {
|
||||
guard self.currentType != type else {
|
||||
if self.animating {
|
||||
@ -583,13 +578,12 @@ public class AttachmentController: ViewController {
|
||||
completion(nil, nil)
|
||||
}
|
||||
|
||||
private var buttonsDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatLocation: ChatLocation, buttons: Signal<[AttachmentButtonType], NoError>) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery) {
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.chatLocation = chatLocation
|
||||
self.buttons = []
|
||||
self.buttons = buttons
|
||||
self.initialButton = initialButton
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
@ -602,21 +596,6 @@ public class AttachmentController: ViewController {
|
||||
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) {
|
||||
@ -632,10 +611,6 @@ public class AttachmentController: ViewController {
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
public func switchTo(_ type: AttachmentButtonType) {
|
||||
(self.displayNode as! Node).switchTo(type)
|
||||
}
|
||||
|
||||
public func _dismiss() {
|
||||
super.dismiss(animated: false, completion: {})
|
||||
}
|
||||
|
@ -10497,6 +10497,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
|
||||
let inputIsActive = self.presentationInterfaceState.inputMode == .text
|
||||
|
||||
self.chatDisplayNode.dismissInput()
|
||||
@ -10535,254 +10537,220 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
isScheduledMessages = true
|
||||
}
|
||||
|
||||
var switchToBotImpl: ((AttachmentButtonType) -> Void)?
|
||||
var switchToBotId = botId
|
||||
let buttons: Signal<[AttachmentButtonType], NoError>
|
||||
let buttons: Signal<([AttachmentButtonType], AttachmentButtonType?), NoError>
|
||||
if let _ = peer as? TelegramUser, !isScheduledMessages {
|
||||
buttons = .single(availableButtons)
|
||||
|> then(
|
||||
self.context.engine.messages.attachMenuBots()
|
||||
|> map { attachMenuBots in
|
||||
var buttons = availableButtons
|
||||
for bot in attachMenuBots.reversed() {
|
||||
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
|
||||
buttons = self.context.engine.messages.attachMenuBots()
|
||||
|> map { attachMenuBots in
|
||||
var buttons = availableButtons
|
||||
var initialButton: AttachmentButtonType?
|
||||
if botId == nil {
|
||||
initialButton = .gallery
|
||||
}
|
||||
) |> afterNext { buttons in
|
||||
if let botId = switchToBotId, let button = buttons.first(where: {
|
||||
if case let .app(otherBotId, _,_) = $0, botId == otherBotId {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
for bot in attachMenuBots.reversed() {
|
||||
let peerTitle = EnginePeer(bot.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
let button: AttachmentButtonType = .app(bot.peer.id, peerTitle, bot.icon)
|
||||
buttons.insert(button, at: 1)
|
||||
|
||||
if initialButton == nil && bot.peer.id == botId {
|
||||
initialButton = button
|
||||
}
|
||||
}) {
|
||||
Queue.mainQueue().justDispatch {
|
||||
switchToBotImpl?(button)
|
||||
}
|
||||
switchToBotId = nil
|
||||
}
|
||||
return (buttons, initialButton)
|
||||
}
|
||||
} else {
|
||||
buttons = .single(availableButtons)
|
||||
buttons = .single((availableButtons, .gallery))
|
||||
}
|
||||
|
||||
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
||||
|
||||
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
|
||||
let _ = (buttons
|
||||
|> deliverOnMainQueue).start(next: { [weak self] buttons, initialButton in
|
||||
guard let strongSelf = self else {
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
|
||||
guard let initialButton = initialButton else {
|
||||
if let botId = botId {
|
||||
let _ = (strongSelf.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: botId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let strongSelf = self, let peer = peer 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) }
|
||||
})
|
||||
let _ = (strongSelf.context.engine.messages.requestWebView(peerId: peer.id, botId: botId, url: nil, themeParams: nil, replyToMessageId: nil)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
if let strongSelf = self, case let .requestConfirmation(botIcon) = result {
|
||||
if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canBeAddedToAttachMenu) {
|
||||
let controller = addWebAppToAttachmentController(context: context, peerName: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peerIcon: botIcon, completion: {
|
||||
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
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
||||
|
||||
let currentMediaController = Atomic<MediaPickerScreen?>(value: nil)
|
||||
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
||||
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
|
||||
|
||||
let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: strongSelf.chatLocation, buttons: buttons, initialButton: initialButton)
|
||||
attachmentController.requestController = { [weak self, weak attachmentController] type, completion in
|
||||
guard let strongSelf = self else {
|
||||
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 {
|
||||
var enqueueMessages: [EnqueueMessage] = []
|
||||
if let textEnqueueMessage = textEnqueueMessage {
|
||||
enqueueMessages.append(textEnqueueMessage)
|
||||
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()
|
||||
}
|
||||
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)
|
||||
})
|
||||
})
|
||||
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
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
if let media = media {
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
@ -10791,71 +10759,131 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}
|
||||
}, 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)
|
||||
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
|
||||
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
|
||||
}
|
||||
}))
|
||||
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)
|
||||
strongSelf.controllerNavigationDisposable.set(nil)
|
||||
}
|
||||
completion(controller, nil)
|
||||
strongSelf.controllerNavigationDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
let present = {
|
||||
self.present(attachmentController, in: .window(.root))
|
||||
self.attachmentController = attachmentController
|
||||
}
|
||||
|
||||
if inputIsActive {
|
||||
Queue.mainQueue().after(0.15, {
|
||||
let present = {
|
||||
strongSelf.present(attachmentController, in: .window(.root))
|
||||
strongSelf.attachmentController = attachmentController
|
||||
}
|
||||
|
||||
if inputIsActive {
|
||||
Queue.mainQueue().after(0.15, {
|
||||
present()
|
||||
})
|
||||
} else {
|
||||
present()
|
||||
})
|
||||
} else {
|
||||
present()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
var animated: Bool = animated
|
||||
var animated = animated
|
||||
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)
|
||||
} else if var fetchStatus = self.fetchStatus {
|
||||
@ -1497,6 +1497,14 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
self.statusNode = nil
|
||||
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
|
||||
if removeStatusNode {
|
||||
statusNode?.removeFromSupernode()
|
||||
|
@ -554,7 +554,11 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
let _ = (context.engine.messages.attachMenuBots()
|
||||
|> deliverOnMainQueue).start(next: { attachMenuBots in
|
||||
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 {
|
||||
let _ = (context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
@ -581,6 +585,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
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 {
|
||||
var section: ResolvedUrlSettingsSection?
|
||||
switch path {
|
||||
case "theme":
|
||||
case "themes":
|
||||
section = .theme
|
||||
case "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 var requestAttachmentMenuExpansion: () -> Void = { }
|
||||
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 placeholderNode: ShimmerEffectNode?
|
||||
|
||||
private let loadingProgressNode: LoadingProgressNode
|
||||
|
||||
private let context: AccountContext
|
||||
var presentationData: PresentationData
|
||||
private let present: (ViewController, Any?) -> Void
|
||||
@ -73,6 +137,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.presentationData = controller.presentationData
|
||||
self.present = present
|
||||
|
||||
self.loadingProgressNode = LoadingProgressNode(color: presentationData.theme.rootController.tabBar.selectedIconColor)
|
||||
|
||||
super.init()
|
||||
|
||||
if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 {
|
||||
@ -133,12 +199,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}
|
||||
webView.allowsBackForwardNavigationGestures = false
|
||||
webView.scrollView.delegate = self
|
||||
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: [], context: nil)
|
||||
self.webView = webView
|
||||
|
||||
let placeholderNode = ShimmerEffectNode()
|
||||
self.addSubnode(placeholderNode)
|
||||
self.placeholderNode = placeholderNode
|
||||
|
||||
if controller.buttonText == nil {
|
||||
self.addSubnode(self.loadingProgressNode)
|
||||
}
|
||||
|
||||
if let iconFile = controller.iconFile {
|
||||
let _ = freeMediaFileInteractiveFetched(account: self.context.account, fileReference: .standalone(media: iconFile)).start()
|
||||
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 {
|
||||
self.queryId = queryId
|
||||
if let url = controller.url {
|
||||
self.queryId = controller.queryId
|
||||
if let parsedUrl = URL(string: url) {
|
||||
self.webView?.load(URLRequest(url: parsedUrl))
|
||||
}
|
||||
|
||||
self.keepAliveDisposable = (keepAliveSignal
|
||||
|> deliverOnMainQueue).start(error: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controller?.dismiss()
|
||||
}
|
||||
}, completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controller?.dismiss()
|
||||
}
|
||||
})
|
||||
if let keepAliveSignal = controller.keepAliveSignal {
|
||||
self.keepAliveDisposable = (keepAliveSignal
|
||||
|> deliverOnMainQueue).start(error: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controller?.dismiss()
|
||||
}
|
||||
}, completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controller?.dismiss()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
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
|
||||
@ -205,6 +278,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
deinit {
|
||||
self.iconDisposable?.dispose()
|
||||
self.keepAliveDisposable?.dispose()
|
||||
|
||||
self.webView?.removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress))
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -272,14 +347,23 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
|
||||
let height: CGFloat
|
||||
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 {
|
||||
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)
|
||||
transition.updateFrame(node: placeholderNode, frame: placeholderFrame)
|
||||
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