Merge pull request #840 from kiwix/814-fix-export-extensions

Fix file export extension and make it more generic
This commit is contained in:
Kelson 2024-07-01 02:04:55 +02:00 committed by GitHub
commit dcc011d0d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 94 additions and 41 deletions

View File

@ -41,7 +41,7 @@ struct Kiwix: App {
.environmentObject(navigation)
.modifier(AlertHandler())
.modifier(OpenFileHandler())
.modifier(SharePDFHandler())
.modifier(FileExportHandler())
.modifier(SaveContentHandler())
.onChange(of: scenePhase) { newValue in
guard newValue == .inactive else { return }

View File

@ -137,7 +137,7 @@ private struct CompactView: View {
Spacer()
BookmarkButton()
Spacer()
ShareButton()
ExportButton()
Spacer()
TabsManagerButton()
if FeatureFlags.hasLibrary {

View File

@ -15,6 +15,7 @@
import Combine
import CoreData
import UniformTypeIdentifiers
class Bookmark: NSManagedObject, Identifiable {
var id: URL { articleURL }
@ -170,6 +171,13 @@ struct URLContentMetaData {
var isTextType: Bool {
mime.hasPrefix("text/")
}
var exportFileExtension: String? {
if isTextType {
return "pdf"
}
return UTType(mimeType: mime)?.preferredFilenameExtension
}
}
struct URLContent {

View File

@ -0,0 +1,48 @@
// This file is part of Kiwix for iOS & macOS.
//
// Kiwix is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// any later version.
//
// Kiwix is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kiwix; If not, see https://www.gnu.org/licenses/.
import Foundation
struct FileExportData {
let data: Data
let fileName: String
let fileExtension: String?
init(data: Data, fileName: String, fileExtension: String? = "pdf") {
self.data = data
self.fileName = fileName
self.fileExtension = fileExtension
}
}
enum FileExporter {
static func tempFileFrom(exportData: FileExportData) -> URL? {
let extensionToAppend: String
if let fileExtension = exportData.fileExtension {
extensionToAppend = ".\(fileExtension)"
} else {
extensionToAppend = ""
}
guard let tempFileName = exportData.fileName.split(separator: ".").first?.appending(extensionToAppend) else {
return nil
}
let tempFileURL = URL(temporaryFileWithName: tempFileName)
guard (try? exportData.data.write(to: tempFileURL)) != nil else {
return nil
}
return tempFileURL
}
}

View File

@ -71,7 +71,7 @@ extension Notification.Name {
static let alert = Notification.Name("alert")
static let openFiles = Notification.Name("openFiles")
static let openURL = Notification.Name("openURL")
static let sharePDF = Notification.Name("sharePDF")
static let exportFileData = Notification.Name("exportFileData")
static let saveContent = Notification.Name("saveContent")
static let toggleSidebar = Notification.Name("toggleSidebar")
}
@ -90,8 +90,8 @@ extension NotificationCenter {
NotificationCenter.default.post(name: .openFiles, object: nil, userInfo: ["urls": urls, "context": context])
}
static func sharePDF(_ data: Data, fileName: String) {
NotificationCenter.default.post(name: .sharePDF, object: nil, userInfo: ["data": data, "fileName": fileName])
static func exportFileData(_ data: FileExportData) {
NotificationCenter.default.post(name: .exportFileData, object: nil, userInfo: ["data": data])
}
static func saveContent(url: URL) {

View File

@ -161,11 +161,21 @@ final class BrowserViewModel: NSObject, ObservableObject,
/// Get the webpage in a binary format
/// - Returns: PDF of the current page (if text type) or binary data of the content
func pdfData() async -> Data? {
if metaData?.isTextType == true {
return try? await webView.pdf()
} else if let url = await webView.url {
return ZimFileService.shared.getURLContent(url: url)?.data
/// and the file extension, if known
func pageDataWithExtension() async -> (Data, String?)? {
if metaData?.isTextType == true,
let pdfData = try? await webView.pdf() {
return (pdfData, metaData?.exportFileExtension)
} else if let url = await webView.url,
let contentData = ZimFileService.shared.getURLContent(url: url)?.data {
let pathExtesion = url.pathExtension
let fileExtension: String?
if !pathExtesion.isEmpty {
fileExtension = pathExtesion
} else {
fileExtension = metaData?.exportFileExtension
}
return (contentData, fileExtension)
}
return nil
}

View File

@ -37,7 +37,7 @@ struct BrowserTab: View {
#endif
ToolbarItemGroup(placement: .primaryAction) {
OutlineButton()
ShareButton()
ExportButton()
#if os(macOS)
PrintButton()
#endif

View File

@ -15,31 +15,32 @@
import SwiftUI
struct ShareButton: View {
struct ExportButton: View {
@EnvironmentObject private var browser: BrowserViewModel
private func dataAndName() async -> (Data, String)? {
guard let browserURLName = browser.webView.url?.lastPathComponent else {
/// - Returns: Returns the browser data, fileName and extension
private func dataNameAndExtension() async -> FileExportData? {
guard let fileName = browser.webView.url?.lastPathComponent else {
return nil
}
guard let pdfData = await browser.pdfData() else {
guard let (pageData, fileExtension) = await browser.pageDataWithExtension() else {
return nil
}
return (pdfData, browserURLName)
return FileExportData(data: pageData, fileName: fileName, fileExtension: fileExtension)
}
private func tempFileURL() async -> URL? {
guard let (pdfData, browserURLName) = await dataAndName() else { return nil }
return PDFHandler.tempFileFrom(pdfData: pdfData, fileName: browserURLName)
guard let exportData = await dataNameAndExtension() else { return nil }
return FileExporter.tempFileFrom(exportData: exportData)
}
var body: some View {
Button {
Task {
#if os(iOS)
guard let (pdfData, browserURLName) = await dataAndName() else { return }
NotificationCenter.sharePDF(pdfData, fileName: browserURLName)
guard let exportData = await dataNameAndExtension() else { return }
NotificationCenter.exportFileData(exportData)
#else
guard let url = await tempFileURL() else { return }
NSSharingServicePicker(items: [url]).show(relativeTo: .null, of: browser.webView, preferredEdge: .minY)

View File

@ -33,7 +33,7 @@ struct PrintButton: View {
private func tempFileURL() async -> URL? {
guard let (pdfData, browserURLName) = await dataAndName() else { return nil }
return PDFHandler.tempFileFrom(pdfData: pdfData, fileName: browserURLName)
return FileExporter.tempFileFrom(exportData: .init(data: pdfData, fileName: browserURLName))
}
var body: some View {

View File

@ -15,33 +15,19 @@
import SwiftUI
enum PDFHandler {
static func tempFileFrom(pdfData: Data, fileName: String) -> URL? {
guard let tempFileName = fileName.split(separator: ".").first?.appending(".pdf") else {
return nil
}
let tempFileURL = URL(temporaryFileWithName: tempFileName)
guard (try? pdfData.write(to: tempFileURL)) != nil else {
return nil
}
return tempFileURL
}
}
#if os(iOS)
/// On receiving pdf content and a file name, it gives the ability to share it
struct SharePDFHandler: ViewModifier {
/// On receiving FileExportData, it gives the ability to share it
struct FileExportHandler: ViewModifier {
private let sharePDF = NotificationCenter.default.publisher(for: .sharePDF)
private let exportFileData = NotificationCenter.default.publisher(for: .exportFileData)
@State private var temporaryURL: URL?
func body(content: Content) -> some View {
content.onReceive(sharePDF) { notification in
content.onReceive(exportFileData) { notification in
guard let userInfo = notification.userInfo,
let pdfData = userInfo["data"] as? Data,
let fileName = userInfo["fileName"] as? String,
let tempURL = PDFHandler.tempFileFrom(pdfData: pdfData, fileName: fileName) else {
let exportData = userInfo["data"] as? FileExportData,
let tempURL = FileExporter.tempFileFrom(exportData: exportData) else {
temporaryURL = nil
return
}