From fd2463b917660182dd9ae9c7ced6e944b1114704 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 27 Sep 2020 22:26:44 -0400 Subject: [PATCH] iOS: Add preferences --- BrowserCore/NavigationManager.swift | 11 ++++ Gemini-iOS/ContentView.swift | 2 +- Gemini-iOS/Preferences.swift | 56 +++++++++++++++++ Gemini-iOS/PreferencesView.swift | 61 +++++++++++++++++++ Gemini-iOS/SceneDelegate.swift | 20 ++++++ Gemini.xcodeproj/project.pbxproj | 8 +++ .../xcschemes/xcschememanagement.plist | 4 +- 7 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 Gemini-iOS/Preferences.swift create mode 100644 Gemini-iOS/PreferencesView.swift diff --git a/BrowserCore/NavigationManager.swift b/BrowserCore/NavigationManager.swift index f130b9a..c929cae 100644 --- a/BrowserCore/NavigationManager.swift +++ b/BrowserCore/NavigationManager.swift @@ -7,8 +7,14 @@ import Foundation +public protocol NavigationManagerDelegate: class { + func loadNonGeminiURL(_ url: URL) +} + public class NavigationManager: NSObject, ObservableObject { + public weak var delegate: NavigationManagerDelegate? + @Published public var currentURL: URL @Published public var backStack = [URL]() @Published public var forwardStack = [URL]() @@ -18,6 +24,11 @@ public class NavigationManager: NSObject, ObservableObject { } public func changeURL(_ url: URL) { + guard url.scheme == "gemini" else { + delegate?.loadNonGeminiURL(url) + return + } + backStack.append(currentURL) currentURL = cannonicalizeURL(url) forwardStack = [] diff --git a/Gemini-iOS/ContentView.swift b/Gemini-iOS/ContentView.swift index 3a7181c..92fdc26 100644 --- a/Gemini-iOS/ContentView.swift +++ b/Gemini-iOS/ContentView.swift @@ -36,7 +36,7 @@ struct ContentView: View { urlFieldContents = new.absoluteString }) .sheet(isPresented: $showPreferencesSheet, content: { - Text("Test") + PreferencesView(presented: $showPreferencesSheet) }) } diff --git a/Gemini-iOS/Preferences.swift b/Gemini-iOS/Preferences.swift new file mode 100644 index 0000000..25c55c2 --- /dev/null +++ b/Gemini-iOS/Preferences.swift @@ -0,0 +1,56 @@ +// +// Preferences.swift +// Gemini-iOS +// +// Created by Shadowfacts on 9/27/20. +// + +import Foundation + +class Preferences: Codable, ObservableObject { + + static let shared: Preferences = load() + + private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! + private static var archiveURL = Preferences.documentsDirectory.appendingPathComponent("Preferences").appendingPathExtension("plist") + + static func save() { + let encoder = PropertyListEncoder() + let data = try? encoder.encode(shared) + try? data?.write(to: archiveURL, options: .noFileProtection) + } + + static func load() -> Preferences { + let decoder = PropertyListDecoder() + if let data = try? Data(contentsOf: archiveURL), + let prefs = try? decoder.decode(Preferences.self, from: data) { + return prefs + } + return Preferences() + } + + private init() {} + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari) + useReaderMode = try container.decode(Bool.self, forKey: .useReaderMode) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(useInAppSafari, forKey: .useInAppSafari) + try container.encode(useReaderMode, forKey: .useReaderMode) + } + + @Published var useInAppSafari = false + @Published var useReaderMode = false + + enum CodingKeys: String, CodingKey { + case useInAppSafari + case useReaderMode + } + +} diff --git a/Gemini-iOS/PreferencesView.swift b/Gemini-iOS/PreferencesView.swift new file mode 100644 index 0000000..9516acd --- /dev/null +++ b/Gemini-iOS/PreferencesView.swift @@ -0,0 +1,61 @@ +// +// PreferencesView.swift +// Gemini-iOS +// +// Created by Shadowfacts on 9/27/20. +// + +import SwiftUI + +struct PreferencesView: View { + @ObservedObject var preferences: Preferences = .shared + + @Binding var presented: Bool + + var body: some View { + NavigationView { + List { + safariSection + } + .navigationBarTitle("Preferences") + .insetOrGroupedListStyle() + .navigationBarItems(trailing: doneButton) + } + } + + private var doneButton: some View { + Button(action: { + presented = false + }, label: { + Text("Done") + }) + } + + private var safariSection: some View { + Section(header: Text("Safari")) { + Toggle("Use In-App Safari", isOn: $preferences.useInAppSafari) + + Toggle("Use Reader Mode", isOn: $preferences.useReaderMode) + .disabled(!preferences.useInAppSafari) + } + } +} + +fileprivate extension View { + @ViewBuilder + func insetOrGroupedListStyle() -> some View { + if #available(iOS 14.0, *) { + self.listStyle(InsetGroupedListStyle()) + } else { + self.listStyle(GroupedListStyle()) + } + } +} + +struct PreferencesView_Previews: PreviewProvider { + @State static var presented = true + + static var previews: some View { + PreferencesView(presented: $presented) + } +} diff --git a/Gemini-iOS/SceneDelegate.swift b/Gemini-iOS/SceneDelegate.swift index 0d56ac9..bf3e248 100644 --- a/Gemini-iOS/SceneDelegate.swift +++ b/Gemini-iOS/SceneDelegate.swift @@ -8,6 +8,7 @@ import UIKit import SwiftUI import BrowserCore +import SafariServices class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -20,6 +21,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + navigationManager.delegate = self + // Create the SwiftUI view that provides the window contents. let contentView = ContentView(navigator: navigationManager) @@ -63,3 +66,20 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } +extension SceneDelegate: NavigationManagerDelegate { + func loadNonGeminiURL(_ url: URL) { + UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { (success) in + if !success { + if Preferences.shared.useInAppSafari { + let config = SFSafariViewController.Configuration() + config.entersReaderIfAvailable = Preferences.shared.useReaderMode + let vc = SFSafariViewController(url: url, configuration: config) + self.window?.rootViewController?.present(vc, animated: true) + } else { + UIApplication.shared.open(url, options: [:]) + } + } + } + } +} + diff --git a/Gemini.xcodeproj/project.pbxproj b/Gemini.xcodeproj/project.pbxproj index 15aeee5..5642b16 100644 --- a/Gemini.xcodeproj/project.pbxproj +++ b/Gemini.xcodeproj/project.pbxproj @@ -40,6 +40,8 @@ D664673624BD07F700B0B741 /* RenderingBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673524BD07F700B0B741 /* RenderingBlock.swift */; }; D664673824BD086F00B0B741 /* RenderingBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673724BD086F00B0B741 /* RenderingBlockView.swift */; }; D664673A24BD0B8E00B0B741 /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673924BD0B8E00B0B741 /* Fonts.swift */; }; + D691A64E25217C6F00348C4B /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A64D25217C6F00348C4B /* Preferences.swift */; }; + D691A66725217FD800348C4B /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A66625217FD800348C4B /* PreferencesView.swift */; }; D69F00AC24BE9DD300E37622 /* GeminiDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */; }; D69F00AE24BEA29100E37622 /* GeminiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AD24BEA29100E37622 /* GeminiResponse.swift */; }; D6E1529824BFAAA400FDF9D3 /* BrowserWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */; }; @@ -272,6 +274,8 @@ D664673524BD07F700B0B741 /* RenderingBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingBlock.swift; sourceTree = ""; }; D664673724BD086F00B0B741 /* RenderingBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingBlockView.swift; sourceTree = ""; }; D664673924BD0B8E00B0B741 /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; + D691A64D25217C6F00348C4B /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + D691A66625217FD800348C4B /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiDataTask.swift; sourceTree = ""; }; D69F00AD24BEA29100E37622 /* GeminiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiResponse.swift; sourceTree = ""; }; D69F00AF24BEA84D00E37622 /* NavigationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationManager.swift; sourceTree = ""; }; @@ -523,6 +527,8 @@ D6E152A424BFFDF500FDF9D3 /* AppDelegate.swift */, D6E152A624BFFDF500FDF9D3 /* SceneDelegate.swift */, D6E152A824BFFDF500FDF9D3 /* ContentView.swift */, + D691A64D25217C6F00348C4B /* Preferences.swift */, + D691A66625217FD800348C4B /* PreferencesView.swift */, D6E152AA24BFFDF600FDF9D3 /* Assets.xcassets */, D6E152AF24BFFDF600FDF9D3 /* LaunchScreen.storyboard */, D6E152B224BFFDF600FDF9D3 /* Info.plist */, @@ -1021,8 +1027,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D691A66725217FD800348C4B /* PreferencesView.swift in Sources */, D6E152A524BFFDF500FDF9D3 /* AppDelegate.swift in Sources */, D6E152A724BFFDF500FDF9D3 /* SceneDelegate.swift in Sources */, + D691A64E25217C6F00348C4B /* Preferences.swift in Sources */, D6E152A924BFFDF500FDF9D3 /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist b/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist index a83b006..a01464b 100644 --- a/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Gemini.xcodeproj/xcuserdata/shadowfacts.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ BrowserCore.xcscheme_^#shared#^_ orderHint - 2 + 4 Gemini-iOS.xcscheme_^#shared#^_ @@ -27,7 +27,7 @@ GeminiProtocol.xcscheme_^#shared#^_ orderHint - 4 + 2 GeminiRenderer.xcscheme_^#shared#^_