Add DeepLinkService to track external ZIM file launch of the app

This commit is contained in:
Balazs Perlaki-Horvath 2025-04-21 20:23:45 +02:00 committed by Kelson
parent d62334be58
commit 6ae4852701
6 changed files with 67 additions and 11 deletions

View File

@ -28,6 +28,7 @@ struct Kiwix: App {
@StateObject private var navigation = NavigationViewModel() @StateObject private var navigation = NavigationViewModel()
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
private let fileMonitor: DirectoryMonitor private let fileMonitor: DirectoryMonitor
// @State var isOpeningDeeplink = true
init() { init() {
fileMonitor = DirectoryMonitor(url: URL.documentDirectory) { LibraryOperations.scanDirectory($0) } fileMonitor = DirectoryMonitor(url: URL.documentDirectory) { LibraryOperations.scanDirectory($0) }
@ -60,14 +61,15 @@ struct Kiwix: App {
} }
case .background: case .background:
break break
// reScheduleBackgroundDownloadTask()
@unknown default: @unknown default:
break break
} }
} }
.onOpenURL { url in .onOpenURL { url in
if url.isFileURL { if url.isFileURL {
NotificationCenter.openFiles([url], context: .file) let deepLinkId = UUID()
DeepLinkService.shared.startFor(uuid: deepLinkId)
NotificationCenter.openFiles([url], context: .file(deepLinkId: deepLinkId))
} else if url.isZIMURL { } else if url.isZIMURL {
NotificationCenter.openURL(url) NotificationCenter.openURL(url)
} }
@ -77,7 +79,9 @@ struct Kiwix: App {
case .kiwix: case .kiwix:
fileMonitor.start() fileMonitor.start()
await LibraryOperations.reopen() await LibraryOperations.reopen()
if !DeepLinkService.shared.isRunning() {
navigation.navigateToMostRecentTab() navigation.navigateToMostRecentTab()
}
LibraryOperations.scanDirectory(URL.documentDirectory) LibraryOperations.scanDirectory(URL.documentDirectory)
LibraryOperations.applyFileBackupSetting() LibraryOperations.applyFileBackupSetting()
DownloadService.shared.restartHeartbeatIfNeeded() DownloadService.shared.restartHeartbeatIfNeeded()

View File

@ -89,7 +89,7 @@ final class SplitViewController: UISplitViewController {
}) })
openURLObserver = NotificationCenter.default.addObserver( openURLObserver = NotificationCenter.default.addObserver(
forName: .openURL, object: nil, queue: nil forName: .openURL, object: nil, queue: .main
) { [weak self] notification in ) { [weak self] notification in
guard let url = notification.userInfo?["url"] as? URL else { return } guard let url = notification.userInfo?["url"] as? URL else { return }
let inNewTab = notification.userInfo?["inNewTab"] as? Bool ?? false let inNewTab = notification.userInfo?["inNewTab"] as? Bool ?? false
@ -99,6 +99,10 @@ final class SplitViewController: UISplitViewController {
} else if let tabID = self?.navigationViewModel.createTab() { } else if let tabID = self?.navigationViewModel.createTab() {
BrowserViewModel.getCached(tabID: tabID).load(url: url) BrowserViewModel.getCached(tabID: tabID).load(url: url)
} }
if let context = notification.userInfo?["context"] as? OpenURLContext,
case .deepLink(let deepLinkId) = context {
DeepLinkService.shared.stopFor(uuid: deepLinkId)
}
} }
} }
observeGoBackAndForward() observeGoBackAndForward()

View File

@ -0,0 +1,42 @@
// 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
/// Helper to figure out if a deeplink started ZIM file
/// handling is already running.
/// In that case we do not want to handle the default
/// navigation to the latest opened ZIM file
@MainActor
final class DeepLinkService {
static let shared = DeepLinkService()
private var ids = Set<UUID>()
private init() {}
func startFor(uuid: UUID) {
ids.insert(uuid)
}
func stopFor(uuid: UUID) {
ids.remove(uuid)
}
func isRunning() -> Bool {
!ids.isEmpty
}
}

View File

@ -138,14 +138,14 @@ enum ExternalLinkLoadingPolicy: String, CaseIterable, Identifiable, Defaults.Ser
} }
} }
enum OpenURLContext: String { enum OpenURLContext {
case deepLink case deepLink(id: UUID)
case file case file
} }
enum OpenFileContext: String { enum OpenFileContext {
case command case command
case file case file(deepLinkId: UUID? = nil)
case welcomeScreen case welcomeScreen
case library case library
} }

View File

@ -16,6 +16,7 @@
import CoreData import CoreData
import WebKit import WebKit
import Combine import Combine
import os
@MainActor @MainActor
final class NavigationViewModel: ObservableObject { final class NavigationViewModel: ObservableObject {
@ -67,6 +68,7 @@ final class NavigationViewModel: ObservableObject {
let tab = (try? context.fetch(fetchRequest).first) ?? Self.makeTab(context: context) let tab = (try? context.fetch(fetchRequest).first) ?? Self.makeTab(context: context)
Task { Task {
await MainActor.run { await MainActor.run {
os_log("open navigate to most recent tab", log: Log.LibraryOperations, type: .error)
currentItem = NavigationItem.tab(objectID: tab.objectID) currentItem = NavigationItem.tab(objectID: tab.objectID)
} }
} }

View File

@ -54,14 +54,14 @@ struct OpenFileHandler: ViewModifier {
@State private var isAlertPresented = false @State private var isAlertPresented = false
@State private var activeAlert: ActiveAlert? @State private var activeAlert: ActiveAlert?
private let importFiles = NotificationCenter.default.publisher(for: .openFiles) private let openFiles = NotificationCenter.default.publisher(for: .openFiles)
enum ActiveAlert { enum ActiveAlert {
case unableToOpen(filenames: [String]) case unableToOpen(filenames: [String])
} }
// swiftlint:disable:next cyclomatic_complexity // swiftlint:disable:next cyclomatic_complexity
func body(content: Content) -> some View { func body(content: Content) -> some View {
content.onReceive(importFiles) { notification in content.onReceive(openFiles) { notification in
guard let urls = notification.userInfo?["urls"] as? [URL], guard let urls = notification.userInfo?["urls"] as? [URL],
let context = notification.userInfo?["context"] as? OpenFileContext else { return } let context = notification.userInfo?["context"] as? OpenFileContext else { return }
@ -90,7 +90,11 @@ struct OpenFileHandler: ViewModifier {
NotificationCenter.openURL(url, inNewTab: true, context: .file) NotificationCenter.openURL(url, inNewTab: true, context: .file)
} }
#elseif os(iOS) #elseif os(iOS)
if case .file(.some(let deepLinkID)) = context {
NotificationCenter.openURL(url, inNewTab: true, context: .deepLink(id: deepLinkID))
} else {
NotificationCenter.openURL(url, inNewTab: true) NotificationCenter.openURL(url, inNewTab: true)
}
#endif #endif
} }
case .welcomeScreen: case .welcomeScreen: