diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 41cc2943d5..8c386fe18d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13290,5 +13290,10 @@ Sorry for the inconvenience."; "MediaGallery.ToastVideoPip.Title" = "Video Minimized"; "MediaGallery.ToastVideoPip.Text" = "Swipe down on a video to close it."; -"Chat.ToastSubscribedToScheduledLiveStream.Text" = "You will be notified when the liver stream starts."; +"Chat.ToastSubscribedToScheduledLiveStream.Text" = "You will be notified when the live stream starts."; "Chat.TitleVideochatPanel.NotifyScheduledButton" = "Notify Me"; + +"WebApp.ShareMessage.Title" = "Share Message"; +"WebApp.ShareMessage.PreviewTitle" = "MESSAGE PREVIEW"; +"WebApp.ShareMessage.Info" = "%@ mini app suggests you to send this message to a chat you select."; +"WebApp.ShareMessage.Share" = "Share With..."; diff --git a/submodules/WebUI/Sources/FileDownload.swift b/submodules/WebUI/Sources/FileDownload.swift index 815a109a61..7de8729237 100644 --- a/submodules/WebUI/Sources/FileDownload.swift +++ b/submodules/WebUI/Sources/FileDownload.swift @@ -2,24 +2,39 @@ import Foundation import SwiftSignalKit final class FileDownload: NSObject, URLSessionDownloadDelegate { - private let fileSize: Int64? + let fileName: String + let fileSize: Int64? + let isMedia: Bool + private var urlSession: URLSession! private var completion: ((URL?, Error?) -> Void)? private var progressHandler: ((Double) -> Void)? + private var task: URLSessionDownloadTask! - init(from url: URL, fileSize: Int64?, progressHandler: @escaping (Double) -> Void, completion: @escaping (URL?, Error?) -> Void) { - self.progressHandler = progressHandler + private let progressPromise = ValuePromise(0.0) + var progressSignal: Signal { + return self.progressPromise.get() + } + + init(from url: URL, fileName: String, fileSize: Int64?, isMedia: Bool, progressHandler: @escaping (Double) -> Void, completion: @escaping (URL?, Error?) -> Void) { + self.fileName = fileName self.fileSize = fileSize + self.isMedia = isMedia self.completion = completion - + self.progressHandler = progressHandler super.init() let configuration = URLSessionConfiguration.default - urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main) + self.urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main) let downloadTask = self.urlSession.downloadTask(with: url) downloadTask.resume() + self.task = downloadTask + } + + func cancel() { + self.task.cancel() } func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { @@ -27,17 +42,18 @@ final class FileDownload: NSObject, URLSessionDownloadDelegate { if totalBytesExpectedToWrite == -1, let fileSize = self.fileSize { totalBytesExpectedToWrite = fileSize } - let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) - progressHandler?(progress) + let progress = max(0.0, min(1.0, Double(totalBytesWritten) / Double(totalBytesExpectedToWrite))) + self.progressHandler?(progress) + self.progressPromise.set(progress) } func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { - completion?(location, nil) + self.completion?(location, nil) } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let error = error { - completion?(nil, error) + self.completion?(nil, error) } } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 88e91b52c7..8bbfd27905 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -145,6 +145,8 @@ public final class WebAppController: ViewController, AttachmentContainable { public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } + static var activeDownloads: [FileDownload] = [] + fileprivate class Node: ViewControllerTracingNode, WKNavigationDelegate, WKUIDelegate, ASScrollViewDelegate { private weak var controller: WebAppController? @@ -2403,17 +2405,19 @@ public final class WebAppController: ViewController, AttachmentContainable { }) } - private var fileDownload: FileDownload? - private weak var fileDownloadTooltip: UndoOverlayController? + fileprivate weak var fileDownloadTooltip: UndoOverlayController? fileprivate func startDownload(url: String, fileName: String, fileSize: Int64?, isMedia: Bool) { guard let controller = self.controller else { return } self.webView?.sendEvent(name: "file_download_requested", data: "{status: \"downloading\"}") - self.fileDownload = FileDownload( + var removeImpl: (() -> Void)? + let fileDownload = FileDownload( from: URL(string: url)!, + fileName: fileName, fileSize: fileSize, + isMedia: isMedia, progressHandler: { [weak self] progress in guard let self else { return @@ -2435,6 +2439,8 @@ public final class WebAppController: ViewController, AttachmentContainable { }, completion: { [weak self] resultUrl, _ in if let resultUrl, let self { + removeImpl?() + let tooltipContent: UndoOverlayContent = .actionSucceeded(title: fileName, text: isMedia ? self.presentationData.strings.WebApp_Download_SavedToPhotos : self.presentationData.strings.WebApp_Download_SavedToFiles, cancel: nil, destructive: false) if isMedia { let saveToPhotos: (URL, Bool) -> Void = { url, isVideo in @@ -2499,6 +2505,13 @@ public final class WebAppController: ViewController, AttachmentContainable { } } ) + WebAppController.activeDownloads.append(fileDownload) + + removeImpl = { [weak fileDownload] in + if let fileDownload { + WebAppController.activeDownloads.removeAll(where: { $0 === fileDownload }) + } + } let text: String if let fileSize { @@ -2517,7 +2530,11 @@ public final class WebAppController: ViewController, AttachmentContainable { ), elevatedLayout: false, position: .top, - action: { _ in + action: { [weak fileDownload] action in + if case .undo = action, let fileDownload { + fileDownload.cancel() + removeImpl?() + } return true } ) @@ -3075,18 +3092,55 @@ public final class WebAppController: ViewController, AttachmentContainable { let hasSettings = self.hasSettings + let activeDownload = WebAppController.activeDownloads.first + let activeDownloadProgress: Signal + if let activeDownload { + activeDownloadProgress = activeDownload.progressSignal + |> map(Optional.init) + |> mapToThrottled { next -> Signal in + return .single(next) |> then(.complete() |> delay(0.2, queue: Queue.mainQueue())) + } + } else { + activeDownloadProgress = .single(nil) + } + let items = combineLatest(queue: Queue.mainQueue(), - context.engine.messages.attachMenuBots(), + context.engine.messages.attachMenuBots() |> take(1), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.botId)), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotCommands(id: self.botId)), - context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotPrivacyPolicyUrl(id: self.botId)) + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotPrivacyPolicyUrl(id: self.botId)), + activeDownloadProgress ) - |> take(1) - |> map { [weak self] attachMenuBots, botPeer, botCommands, privacyPolicyUrl -> ContextController.Items in + |> map { [weak self] attachMenuBots, botPeer, botCommands, privacyPolicyUrl, activeDownloadProgress -> ContextController.Items in var items: [ContextMenuItem] = [] - let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) }) + if let activeDownload, let progress = activeDownloadProgress { + let isActive = progress < 1.0 - .ulpOfOne + let progressString: String + if isActive { + if let fileSize = activeDownload.fileSize { + let downloadedSize = Int64(Double(fileSize) * progress) + progressString = "\(dataSizeString(downloadedSize, formatting: DataSizeStringFormatting(presentationData: presentationData))) / \(dataSizeString(fileSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))" + } else { + progressString = "\(Int32(progress))%" + } + } else { + progressString = activeDownload.isMedia ? presentationData.strings.WebApp_Download_SavedToPhotos : presentationData.strings.WebApp_Download_SavedToFiles + } + items.append(.action(ContextMenuActionItem(text: activeDownload.fileName, textLayout: .secondLineWithValue(progressString), icon: { theme in return isActive ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.primaryColor) : nil }, iconPosition: .right, action: isActive ? { [weak self, weak activeDownload] _, f in + f(.default) + + WebAppController.activeDownloads.removeAll(where: { $0 === activeDownload }) + activeDownload?.cancel() + + if let fileDownloadTooltip = self?.controllerNode.fileDownloadTooltip { + fileDownloadTooltip.dismissWithCommitAction() + } + } : nil))) + items.append(.separator) + } + let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) }) if hasSettings { items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_Settings, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor) diff --git a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift index 3230a718bf..62eb845139 100644 --- a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift +++ b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift @@ -76,7 +76,7 @@ private final class SheetContent: CombinedComponent { let closeButton = closeButton.update( component: Button( - content: AnyComponent(Text(text: "Cancel", font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)), + content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)), action: { component.dismiss() } @@ -89,7 +89,7 @@ private final class SheetContent: CombinedComponent { ) let title = title.update( - component: Text(text: "Share Message", font: Font.bold(17.0), color: theme.list.itemPrimaryTextColor), + component: Text(text: environment.strings.WebApp_ShareMessage_Title, font: Font.bold(17.0), color: theme.list.itemPrimaryTextColor), availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), transition: .immediate ) @@ -105,7 +105,7 @@ private final class SheetContent: CombinedComponent { return (TelegramTextAttributes.URL, contents) }) - let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("\(component.botName) mini app suggests you to send this message to a chat you select.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.WebApp_ShareMessage_Info(component.botName).string, attributes: amountMarkdownAttributes, textAlignment: .natural)) let amountFooter = AnyComponent(MultilineTextComponent( text: .plain(amountInfoString), maximumNumberOfLines: 0, @@ -192,7 +192,7 @@ private final class SheetContent: CombinedComponent { theme: theme, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Message Preview".uppercased(), + string: environment.strings.WebApp_ShareMessage_PreviewTitle.uppercased(), font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.freeTextColor )), @@ -230,7 +230,7 @@ private final class SheetContent: CombinedComponent { contentSize.height += amountSection.size.height contentSize.height += 32.0 - let buttonString: String = "Share With..." + let buttonString: String = environment.strings.WebApp_ShareMessage_Share let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let button = button.update(