Skip to content

Commit fa397a9

Browse files
authored
Version 3.2 (#523)
* view model * setup * library * changelog * macOS * purge cache
1 parent 20d4ef2 commit fa397a9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1499
-1365
lines changed

App/App_iOS.swift

+33-64
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import UserNotifications
1212
#if os(iOS)
1313
@main
1414
struct Kiwix: App {
15+
@Environment(\.scenePhase) private var scenePhase
16+
@StateObject private var library = LibraryViewModel()
17+
@StateObject private var navigation = NavigationViewModel()
1518
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
1619

1720
private let fileMonitor: DirectoryMonitor
@@ -29,7 +32,24 @@ struct Kiwix: App {
2932

3033
var body: some Scene {
3134
WindowGroup {
32-
RootView().environment(\.managedObjectContext, Database.viewContext)
35+
RootView()
36+
.ignoresSafeArea()
37+
.environment(\.managedObjectContext, Database.viewContext)
38+
.environmentObject(library)
39+
.environmentObject(navigation)
40+
.modifier(AlertHandler())
41+
.modifier(OpenFileHandler())
42+
.onChange(of: scenePhase) { newValue in
43+
guard newValue == .inactive else { return }
44+
try? Database.viewContext.save()
45+
}
46+
.onOpenURL { url in
47+
if url.isFileURL {
48+
NotificationCenter.openFiles([url], context: .file)
49+
} else if url.scheme == "kiwix" {
50+
NotificationCenter.openURL(url)
51+
}
52+
}
3353
}
3454
.commands {
3555
CommandGroup(replacing: .undoRedo) {
@@ -55,77 +75,26 @@ struct Kiwix: App {
5575
withCompletionHandler completionHandler: @escaping () -> Void) {
5676
if let zimFileID = UUID(uuidString: response.notification.request.identifier),
5777
let mainPageURL = ZimFileService.shared.getMainPageURL(zimFileID: zimFileID) {
58-
UIApplication.shared.open(mainPageURL)
78+
NotificationCenter.openURL(mainPageURL, inNewTab: true)
5979
}
6080
completionHandler()
6181
}
82+
83+
/// Purge some cached browser view models when receiving memory warning
84+
func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
85+
BrowserViewModel.purgeCache()
86+
}
6287
}
6388
}
6489

65-
struct RootView: View {
66-
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
67-
@Environment(\.scenePhase) private var scenePhase
68-
@StateObject private var library = LibraryViewModel()
69-
@StateObject private var navigation = NavigationViewModel()
90+
private struct RootView: UIViewControllerRepresentable {
91+
@EnvironmentObject private var navigation: NavigationViewModel
7092

71-
private let primaryItems: [NavigationItem] = [.bookmarks, .settings]
72-
private let libraryItems: [NavigationItem] = [.opened, .categories, .downloads, .new]
73-
private let openURL = NotificationCenter.default.publisher(for: .openURL)
93+
func makeUIViewController(context: Context) -> SplitViewController {
94+
SplitViewController(navigationViewModel: navigation)
95+
}
7496

75-
var body: some View {
76-
Group {
77-
if #available(iOS 16.0, *) {
78-
if horizontalSizeClass == .regular {
79-
RegularView()
80-
} else {
81-
ContainerView {
82-
CompactView()
83-
}
84-
.ignoresSafeArea()
85-
.onAppear() {
86-
navigation.navigateToMostRecentTab()
87-
}
88-
}
89-
} else {
90-
ContainerView {
91-
LegacyView()
92-
}.ignoresSafeArea()
93-
}
94-
}
95-
.focusedSceneValue(\.navigationItem, $navigation.currentItem)
96-
.environmentObject(library)
97-
.environmentObject(navigation)
98-
.modifier(AlertHandler())
99-
.modifier(ExternalLinkHandler())
100-
.modifier(OpenFileHandler())
101-
.onChange(of: scenePhase) { newScenePhase in
102-
guard newScenePhase == .inactive else { return }
103-
WebViewCache.shared.persistStates()
104-
}
105-
.onOpenURL { url in
106-
if url.isFileURL {
107-
NotificationCenter.openFiles([url], context: .file)
108-
} else if url.scheme == "kiwix" {
109-
NotificationCenter.openURL(url)
110-
}
111-
}
112-
.onReceive(openURL) { notification in
113-
guard let url = notification.userInfo?["url"] as? URL else { return }
114-
let inNewTab = notification.userInfo?["inNewTab"] as? Bool ?? false
115-
if #available(iOS 16.0, *) {
116-
if inNewTab {
117-
let tabID = navigation.createTab()
118-
WebViewCache.shared.getWebView(tabID: tabID).load(URLRequest(url: url))
119-
} else if case let .tab(tabID) = navigation.currentItem {
120-
WebViewCache.shared.getWebView(tabID: tabID).load(URLRequest(url: url))
121-
} else {
122-
let tabID = navigation.createTab()
123-
WebViewCache.shared.getWebView(tabID: tabID).load(URLRequest(url: url))
124-
}
125-
} else {
126-
WebViewCache.shared.webView.load(URLRequest(url: url))
127-
}
128-
}
97+
func updateUIViewController(_ controller: SplitViewController, context: Context) {
12998
}
13099
}
131100
#endif

App/App_macOS.swift

+6-5
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,13 @@ struct Kiwix: App {
7979
}
8080

8181
struct RootView: View {
82+
@Environment(\.controlActiveState) var controlActiveState
83+
@StateObject private var browser = BrowserViewModel()
8284
@StateObject private var navigation = NavigationViewModel()
8385

8486
private let primaryItems: [NavigationItem] = [.reading, .bookmarks]
8587
private let libraryItems: [NavigationItem] = [.opened, .categories, .downloads, .new]
86-
private let openURL = NotificationCenter.default.publisher(for: Notification.Name.openURL)
88+
private let openURL = NotificationCenter.default.publisher(for: .openURL)
8789

8890
var body: some View {
8991
NavigationView {
@@ -108,7 +110,7 @@ struct RootView: View {
108110
}
109111
switch navigation.currentItem {
110112
case .reading:
111-
ReadingView()
113+
BrowserTab().environmentObject(browser)
112114
case .bookmarks:
113115
Bookmarks()
114116
case .opened:
@@ -127,7 +129,6 @@ struct RootView: View {
127129
.focusedSceneValue(\.navigationItem, $navigation.currentItem)
128130
.environmentObject(navigation)
129131
.modifier(AlertHandler())
130-
.modifier(ExternalLinkHandler())
131132
.modifier(OpenFileHandler())
132133
.onOpenURL { url in
133134
if url.isFileURL {
@@ -137,8 +138,8 @@ struct RootView: View {
137138
}
138139
}
139140
.onReceive(openURL) { notification in
140-
guard let url = notification.userInfo?["url"] as? URL else { return }
141-
WebViewCache.shared.webView.load(URLRequest(url: url))
141+
guard controlActiveState == .key, let url = notification.userInfo?["url"] as? URL else { return }
142+
browser.load(url: url)
142143
navigation.currentItem = .reading
143144
}
144145
}

App/CompactView.swift

-64
This file was deleted.

App/CompactViewController.swift

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//
2+
// CompactViewController.swift
3+
// Kiwix
4+
//
5+
// Created by Chris Li on 9/4/23.
6+
// Copyright © 2023 Chris Li. All rights reserved.
7+
//
8+
9+
#if os(iOS)
10+
import Combine
11+
import SwiftUI
12+
import UIKit
13+
14+
class CompactViewController: UIHostingController<AnyView>, UISearchControllerDelegate, UISearchResultsUpdating {
15+
private let searchViewModel: SearchViewModel
16+
private let searchController: UISearchController
17+
private var searchTextObserver: AnyCancellable?
18+
private var openURLObserver: NSObjectProtocol?
19+
20+
init() {
21+
searchViewModel = SearchViewModel()
22+
let searchResult = SearchResults().environmentObject(searchViewModel)
23+
searchController = UISearchController(searchResultsController: UIHostingController(rootView: searchResult))
24+
super.init(rootView: AnyView(CompactView()))
25+
searchController.searchResultsUpdater = self
26+
}
27+
28+
@MainActor required dynamic init?(coder aDecoder: NSCoder) {
29+
fatalError("init(coder:) has not been implemented")
30+
}
31+
32+
override func viewDidLoad() {
33+
super.viewDidLoad()
34+
35+
definesPresentationContext = true
36+
navigationController?.isToolbarHidden = false
37+
navigationController?.toolbar.scrollEdgeAppearance = {
38+
let apperance = UIToolbarAppearance()
39+
apperance.configureWithDefaultBackground()
40+
return apperance
41+
}()
42+
navigationItem.scrollEdgeAppearance = {
43+
let apperance = UINavigationBarAppearance()
44+
apperance.configureWithDefaultBackground()
45+
return apperance
46+
}()
47+
navigationItem.titleView = searchController.searchBar
48+
searchController.automaticallyShowsCancelButton = false
49+
searchController.delegate = self
50+
searchController.hidesNavigationBarDuringPresentation = false
51+
searchController.showsSearchResultsController = true
52+
53+
searchTextObserver = searchViewModel.$searchText.sink { [weak self] searchText in
54+
guard self?.searchController.searchBar.text != searchText else { return }
55+
self?.searchController.searchBar.text = searchText
56+
}
57+
}
58+
59+
override func viewWillAppear(_ animated: Bool) {
60+
super.viewWillAppear(animated)
61+
openURLObserver = NotificationCenter.default.addObserver(
62+
forName: .openURL, object: nil, queue: nil
63+
) { [weak self] _ in
64+
self?.searchController.isActive = false
65+
self?.navigationItem.setRightBarButton(nil, animated: true)
66+
}
67+
}
68+
69+
override func viewDidDisappear(_ animated: Bool) {
70+
super.viewDidDisappear(animated)
71+
NotificationCenter.default.removeObserver(self)
72+
}
73+
74+
func willPresentSearchController(_ searchController: UISearchController) {
75+
navigationController?.setToolbarHidden(true, animated: true)
76+
navigationItem.setRightBarButton(
77+
UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction { [unowned self] _ in
78+
searchController.isActive = false
79+
navigationItem.setRightBarButton(nil, animated: true)
80+
}), animated: true
81+
)
82+
}
83+
84+
func willDismissSearchController(_ searchController: UISearchController) {
85+
navigationController?.setToolbarHidden(false, animated: true)
86+
searchViewModel.searchText = ""
87+
}
88+
89+
func updateSearchResults(for searchController: UISearchController) {
90+
searchViewModel.searchText = searchController.searchBar.text ?? ""
91+
}
92+
}
93+
94+
private struct CompactView: View {
95+
@EnvironmentObject private var navigation: NavigationViewModel
96+
97+
var body: some View {
98+
if case let .tab(tabID) = navigation.currentItem {
99+
Content().id(tabID).toolbar {
100+
ToolbarItemGroup(placement: .bottomBar) {
101+
HStack {
102+
NavigationButtons()
103+
Spacer()
104+
OutlineButton()
105+
Spacer()
106+
BookmarkButton()
107+
Spacer()
108+
ArticleShortcutButtons(displayMode: .randomArticle)
109+
Spacer()
110+
TabsManagerButton()
111+
}
112+
}
113+
}
114+
.environmentObject(BrowserViewModel.getCached(tabID: tabID))
115+
}
116+
}
117+
}
118+
119+
private struct Content: View {
120+
@EnvironmentObject private var browser: BrowserViewModel
121+
122+
var body: some View {
123+
Group {
124+
if browser.url == nil {
125+
Welcome()
126+
} else {
127+
WebView().ignoresSafeArea()
128+
}
129+
}
130+
.focusedSceneValue(\.browserViewModel, browser)
131+
.focusedSceneValue(\.canGoBack, browser.canGoBack)
132+
.focusedSceneValue(\.canGoForward, browser.canGoForward)
133+
.modifier(ExternalLinkHandler())
134+
.onAppear {
135+
browser.updateLastOpened()
136+
}
137+
.onDisappear {
138+
browser.persistState()
139+
}
140+
}
141+
}
142+
#endif

0 commit comments

Comments
 (0)