diff --git a/BrowserCore/BrowserCore.h b/BrowserCore/BrowserCore.h new file mode 100644 index 0000000..76fecaa --- /dev/null +++ b/BrowserCore/BrowserCore.h @@ -0,0 +1,18 @@ +// +// BrowserCore.h +// BrowserCore +// +// Created by Shadowfacts on 7/15/20. +// + +#import + +//! Project version number for BrowserCore. +FOUNDATION_EXPORT double BrowserCoreVersionNumber; + +//! Project version string for BrowserCore. +FOUNDATION_EXPORT const unsigned char BrowserCoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/BrowserCore/BrowserView.swift b/BrowserCore/BrowserView.swift new file mode 100644 index 0000000..30b182f --- /dev/null +++ b/BrowserCore/BrowserView.swift @@ -0,0 +1,96 @@ +// +// ContentView.swift +// Gemini +// +// Created by Shadowfacts on 7/12/20. +// + +import SwiftUI +import GeminiFormat +import GeminiRenderer +import GeminiProtocol + +public struct BrowserView: View { + let navigator: NavigationManager + @State var task: GeminiDataTask? + @State var state: ViewState = .loading + + public init(navigator: NavigationManager) { + self.navigator = navigator + } + + public var body: some View { + mainView + .onReceive(navigator.$currentURL, perform: self.urlChanged) + } + + @ViewBuilder + private var mainView: some View { + switch state { + case .loading: + loadingView + .onAppear(perform: self.loadDocument) + case let .error(message): + VStack { + Text("An error occurred") + .font(.headline) + Text(message) + } + case let .document(doc): + DocumentView(document: doc, changeURL: navigator.changeURL) + } + } + + @ViewBuilder + private var loadingView: some View { + if #available(macOS 10.16, iOS 14.0, *) { + ProgressView("Loading...") + } else { + Text("Loading...") + } + } + + private func loadDocument() { + let url = navigator.currentURL + task = try! GeminiDataTask(url: url, completion: { (response) in + self.task = nil + switch response { + case let .failure(error): + state = .error(error.localizedDescription) + case let .success(response): + if response.status.isRedirect { + if let redirect = URL(string: response.meta) { + navigator.changeURL(redirect) + loadDocument() + } else { + state = .error("Invalid redirect URL: '\(response.meta)'") + } + } else if response.status.isSuccess, + let text = response.bodyText { + state = .document(GeminiParser.parse(text: text, baseURL: url)) + } else { + state = .error("Unknown error: \(response.header)") + } + } + }) + task!.resume() + } + + private func urlChanged(_ newValue: URL) { + state = .loading + } +} + +extension BrowserView { + enum ViewState { + case loading + case document(Document) + case error(String) + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + BrowserView(navigator: NavigationManager(url: URL(string: "gemini://localhost/overview.gmi")!)) + } +} diff --git a/BrowserCore/Info.plist b/BrowserCore/Info.plist new file mode 100644 index 0000000..9bcb244 --- /dev/null +++ b/BrowserCore/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/Gemini/NavigationManager.swift b/BrowserCore/NavigationManager.swift similarity index 72% rename from Gemini/NavigationManager.swift rename to BrowserCore/NavigationManager.swift index 27d3bc0..fd1cc0f 100644 --- a/Gemini/NavigationManager.swift +++ b/BrowserCore/NavigationManager.swift @@ -7,17 +7,17 @@ import Foundation -class NavigationManager: NSObject, ObservableObject { +public class NavigationManager: NSObject, ObservableObject { - @Published var currentURL: URL - @Published var backStack = [URL]() - @Published var forwardStack = [URL]() + @Published public var currentURL: URL + @Published public var backStack = [URL]() + @Published public var forwardStack = [URL]() - init(url: URL) { + public init(url: URL) { self.currentURL = url } - func changeURL(_ url: URL) { + public func changeURL(_ url: URL) { backStack.append(currentURL) currentURL = cannonicalizeURL(url) forwardStack = [] @@ -31,13 +31,13 @@ class NavigationManager: NSObject, ObservableObject { return components.url! } - @objc func back() { + @objc public func back() { guard !backStack.isEmpty else { return } forwardStack.insert(currentURL, at: 0) currentURL = backStack.removeLast() } - @objc func forward() { + @objc public func forward() { guard !forwardStack.isEmpty else { return } backStack.append(currentURL) currentURL = forwardStack.removeFirst() diff --git a/BrowserCoreTests/BrowserCoreTests.swift b/BrowserCoreTests/BrowserCoreTests.swift new file mode 100644 index 0000000..5bda1c3 --- /dev/null +++ b/BrowserCoreTests/BrowserCoreTests.swift @@ -0,0 +1,33 @@ +// +// BrowserCoreTests.swift +// BrowserCoreTests +// +// Created by Shadowfacts on 7/15/20. +// + +import XCTest +@testable import BrowserCore + +class BrowserCoreTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/BrowserCoreTests/Info.plist b/BrowserCoreTests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/BrowserCoreTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Gemini-iOS/AppDelegate.swift b/Gemini-iOS/AppDelegate.swift new file mode 100644 index 0000000..1ec0c8c --- /dev/null +++ b/Gemini-iOS/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// Gemini-iOS +// +// Created by Shadowfacts on 7/15/20. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/Gemini-iOS/Assets.xcassets/AccentColor.colorset/Contents.json b/Gemini-iOS/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Gemini-iOS/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Gemini-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Gemini-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/Gemini-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Gemini-iOS/Assets.xcassets/Contents.json b/Gemini-iOS/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Gemini-iOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Gemini-iOS/Base.lproj/LaunchScreen.storyboard b/Gemini-iOS/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Gemini-iOS/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gemini-iOS/ContentView.swift b/Gemini-iOS/ContentView.swift new file mode 100644 index 0000000..ded0338 --- /dev/null +++ b/Gemini-iOS/ContentView.swift @@ -0,0 +1,23 @@ +// +// ContentView.swift +// Gemini-iOS +// +// Created by Shadowfacts on 7/15/20. +// + +import SwiftUI +import BrowserCore + +struct ContentView: View { + let navigator: NavigationManager + + var body: some View { + BrowserView(navigator: navigator) + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView(navigator: NavigationManager(url: URL(string: "gemini://localhost/overview.gmi")!)) + } +} diff --git a/Gemini-iOS/Info.plist b/Gemini-iOS/Info.plist new file mode 100644 index 0000000..2688b32 --- /dev/null +++ b/Gemini-iOS/Info.plist @@ -0,0 +1,62 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Gemini-iOS/Preview Content/Preview Assets.xcassets/Contents.json b/Gemini-iOS/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Gemini-iOS/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Gemini-iOS/SceneDelegate.swift b/Gemini-iOS/SceneDelegate.swift new file mode 100644 index 0000000..0d56ac9 --- /dev/null +++ b/Gemini-iOS/SceneDelegate.swift @@ -0,0 +1,65 @@ +// +// SceneDelegate.swift +// Gemini-iOS +// +// Created by Shadowfacts on 7/15/20. +// + +import UIKit +import SwiftUI +import BrowserCore + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + let navigationManager = NavigationManager(url: URL(string: "gemini://gemini.circumlunar.space/")!) + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // 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). + + // Create the SwiftUI view that provides the window contents. + let contentView = ContentView(navigator: navigationManager) + + // Use a UIHostingController as window root view controller. + if let windowScene = scene as? UIWindowScene { + let window = UIWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: contentView) + self.window = window + window.makeKeyAndVisible() + } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Gemini.xcodeproj/project.pbxproj b/Gemini.xcodeproj/project.pbxproj index 817ae0e..5b81506 100644 --- a/Gemini.xcodeproj/project.pbxproj +++ b/Gemini.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ D626645F24BBF1C200DF9B88 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626645E24BBF1C200DF9B88 /* AppDelegate.swift */; }; - D626646124BBF1C200DF9B88 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626646024BBF1C200DF9B88 /* ContentView.swift */; }; D626646324BBF1C300DF9B88 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D626646224BBF1C300DF9B88 /* Assets.xcassets */; }; D626646624BBF1C300DF9B88 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D626646524BBF1C300DF9B88 /* Preview Assets.xcassets */; }; D626648024BBF22E00DF9B88 /* GeminiProtocol.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */; }; @@ -44,10 +43,31 @@ D664673A24BD0B8E00B0B741 /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673924BD0B8E00B0B741 /* Fonts.swift */; }; D69F00AC24BE9DD300E37622 /* GeminiDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */; }; D69F00AE24BEA29100E37622 /* GeminiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AD24BEA29100E37622 /* GeminiResponse.swift */; }; - D69F00B024BEA84D00E37622 /* NavigationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AF24BEA84D00E37622 /* NavigationManager.swift */; }; D6E1529824BFAAA400FDF9D3 /* BrowserWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */; }; D6E1529924BFAAA400FDF9D3 /* BrowserWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E1529724BFAAA400FDF9D3 /* BrowserWindowController.xib */; }; D6E1529B24BFAEC700FDF9D3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E1529A24BFAEC700FDF9D3 /* MainMenu.xib */; }; + D6E152A524BFFDF500FDF9D3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E152A424BFFDF500FDF9D3 /* AppDelegate.swift */; }; + D6E152A724BFFDF500FDF9D3 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E152A624BFFDF500FDF9D3 /* SceneDelegate.swift */; }; + D6E152A924BFFDF500FDF9D3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E152A824BFFDF500FDF9D3 /* ContentView.swift */; }; + D6E152AB24BFFDF600FDF9D3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E152AA24BFFDF600FDF9D3 /* Assets.xcassets */; }; + D6E152AE24BFFDF600FDF9D3 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E152AD24BFFDF600FDF9D3 /* Preview Assets.xcassets */; }; + D6E152B124BFFDF600FDF9D3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E152AF24BFFDF600FDF9D3 /* LaunchScreen.storyboard */; }; + D6E152B624BFFE2400FDF9D3 /* GeminiFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */; }; + D6E152B724BFFE2400FDF9D3 /* GeminiFormat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D6E152C324C0004C00FDF9D3 /* GeminiProtocol.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */; }; + D6E152C424C0004C00FDF9D3 /* GeminiProtocol.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D626647724BBF22E00DF9B88 /* GeminiProtocol.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D6E152C724C0004C00FDF9D3 /* GeminiRenderer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D62664CE24BC081B00DF9B88 /* GeminiRenderer.framework */; }; + D6E152C824C0004C00FDF9D3 /* GeminiRenderer.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D62664CE24BC081B00DF9B88 /* GeminiRenderer.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D6E152D924C0007200FDF9D3 /* BrowserCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6E152D024C0007200FDF9D3 /* BrowserCore.framework */; }; + D6E152E024C0007200FDF9D3 /* BrowserCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E152DF24C0007200FDF9D3 /* BrowserCoreTests.swift */; }; + D6E152E224C0007200FDF9D3 /* BrowserCore.h in Headers */ = {isa = PBXBuildFile; fileRef = D6E152D224C0007200FDF9D3 /* BrowserCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D6E152E524C0007200FDF9D3 /* BrowserCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6E152D024C0007200FDF9D3 /* BrowserCore.framework */; }; + D6E152E624C0007200FDF9D3 /* BrowserCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6E152D024C0007200FDF9D3 /* BrowserCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D6E152ED24C0009300FDF9D3 /* BrowserCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6E152D024C0007200FDF9D3 /* BrowserCore.framework */; }; + D6E152EE24C0009300FDF9D3 /* BrowserCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6E152D024C0007200FDF9D3 /* BrowserCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D6E152F124C000A000FDF9D3 /* NavigationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AF24BEA84D00E37622 /* NavigationManager.swift */; }; + D6E152F224C000CD00FDF9D3 /* BrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626646024BBF1C200DF9B88 /* BrowserView.swift */; }; + D6E152F424C000DA00FDF9D3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E152F324C000DA00FDF9D3 /* ContentView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -121,6 +141,55 @@ remoteGlobalIDString = D62664A724BBF26A00DF9B88; remoteInfo = GeminiFormat; }; + D6E152B824BFFE2400FDF9D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D62664A724BBF26A00DF9B88; + remoteInfo = GeminiFormat; + }; + D6E152C524C0004C00FDF9D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D626647624BBF22E00DF9B88; + remoteInfo = GeminiProtocol; + }; + D6E152C924C0004C00FDF9D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D62664CD24BC081B00DF9B88; + remoteInfo = GeminiRenderer; + }; + D6E152DA24C0007200FDF9D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D6E152CF24C0007200FDF9D3; + remoteInfo = BrowserCore; + }; + D6E152DC24C0007200FDF9D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D626645A24BBF1C200DF9B88; + remoteInfo = Gemini; + }; + D6E152E324C0007200FDF9D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D6E152CF24C0007200FDF9D3; + remoteInfo = BrowserCore; + }; + D6E152EF24C0009300FDF9D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D626645324BBF1C200DF9B88 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D6E152CF24C0007200FDF9D3; + remoteInfo = BrowserCore; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -132,6 +201,7 @@ files = ( D62664E424BC081B00DF9B88 /* GeminiRenderer.framework in Embed Frameworks */, D62664BE24BBF26A00DF9B88 /* GeminiFormat.framework in Embed Frameworks */, + D6E152E624C0007200FDF9D3 /* BrowserCore.framework in Embed Frameworks */, D626648D24BBF22E00DF9B88 /* GeminiProtocol.framework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -148,12 +218,26 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + D6E152C224BFFE2500FDF9D3 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D6E152C824C0004C00FDF9D3 /* GeminiRenderer.framework in Embed Frameworks */, + D6E152B724BFFE2400FDF9D3 /* GeminiFormat.framework in Embed Frameworks */, + D6E152EE24C0009300FDF9D3 /* BrowserCore.framework in Embed Frameworks */, + D6E152C424C0004C00FDF9D3 /* GeminiProtocol.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ D626645B24BBF1C200DF9B88 /* Gemini.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gemini.app; sourceTree = BUILT_PRODUCTS_DIR; }; D626645E24BBF1C200DF9B88 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - D626646024BBF1C200DF9B88 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + D626646024BBF1C200DF9B88 /* BrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserView.swift; sourceTree = ""; }; D626646224BBF1C300DF9B88 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; D626646524BBF1C300DF9B88 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; D626646A24BBF1C300DF9B88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -196,6 +280,21 @@ D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserWindowController.swift; sourceTree = ""; }; D6E1529724BFAAA400FDF9D3 /* BrowserWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BrowserWindowController.xib; sourceTree = ""; }; D6E1529A24BFAEC700FDF9D3 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; + D6E152A224BFFDF500FDF9D3 /* Gemini-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Gemini-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + D6E152A424BFFDF500FDF9D3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + D6E152A624BFFDF500FDF9D3 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + D6E152A824BFFDF500FDF9D3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + D6E152AA24BFFDF600FDF9D3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D6E152AD24BFFDF600FDF9D3 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + D6E152B024BFFDF600FDF9D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + D6E152B224BFFDF600FDF9D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D6E152D024C0007200FDF9D3 /* BrowserCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BrowserCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D6E152D224C0007200FDF9D3 /* BrowserCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BrowserCore.h; sourceTree = ""; }; + D6E152D324C0007200FDF9D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D6E152D824C0007200FDF9D3 /* BrowserCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BrowserCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D6E152DF24C0007200FDF9D3 /* BrowserCoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserCoreTests.swift; sourceTree = ""; }; + D6E152E124C0007200FDF9D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D6E152F324C000DA00FDF9D3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -205,6 +304,7 @@ files = ( D62664E324BC081B00DF9B88 /* GeminiRenderer.framework in Frameworks */, D62664BD24BBF26A00DF9B88 /* GeminiFormat.framework in Frameworks */, + D6E152E524C0007200FDF9D3 /* BrowserCore.framework in Frameworks */, D626648C24BBF22E00DF9B88 /* GeminiProtocol.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -255,6 +355,32 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D6E1529F24BFFDF500FDF9D3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D6E152C724C0004C00FDF9D3 /* GeminiRenderer.framework in Frameworks */, + D6E152B624BFFE2400FDF9D3 /* GeminiFormat.framework in Frameworks */, + D6E152ED24C0009300FDF9D3 /* BrowserCore.framework in Frameworks */, + D6E152C324C0004C00FDF9D3 /* GeminiProtocol.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D6E152CD24C0007200FDF9D3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D6E152D524C0007200FDF9D3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D6E152D924C0007200FDF9D3 /* BrowserCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -262,6 +388,9 @@ isa = PBXGroup; children = ( D626645D24BBF1C200DF9B88 /* Gemini */, + D6E152A324BFFDF500FDF9D3 /* Gemini-iOS */, + D6E152D124C0007200FDF9D3 /* BrowserCore */, + D6E152DE24C0007200FDF9D3 /* BrowserCoreTests */, D626647824BBF22E00DF9B88 /* GeminiProtocol */, D626648524BBF22E00DF9B88 /* GeminiProtocolTests */, D62664A924BBF26A00DF9B88 /* GeminiFormat */, @@ -283,6 +412,9 @@ D62664B024BBF26A00DF9B88 /* GeminiFormatTests.xctest */, D62664CE24BC081B00DF9B88 /* GeminiRenderer.framework */, D62664D624BC081B00DF9B88 /* GeminiRendererTests.xctest */, + D6E152A224BFFDF500FDF9D3 /* Gemini-iOS.app */, + D6E152D024C0007200FDF9D3 /* BrowserCore.framework */, + D6E152D824C0007200FDF9D3 /* BrowserCoreTests.xctest */, ); name = Products; sourceTree = ""; @@ -291,9 +423,8 @@ isa = PBXGroup; children = ( D626645E24BBF1C200DF9B88 /* AppDelegate.swift */, - D626646024BBF1C200DF9B88 /* ContentView.swift */, - D69F00AF24BEA84D00E37622 /* NavigationManager.swift */, D6E1529A24BFAEC700FDF9D3 /* MainMenu.xib */, + D6E152F324C000DA00FDF9D3 /* ContentView.swift */, D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */, D6E1529724BFAAA400FDF9D3 /* BrowserWindowController.xib */, D626646224BBF1C300DF9B88 /* Assets.xcassets */, @@ -389,6 +520,48 @@ name = Frameworks; sourceTree = ""; }; + D6E152A324BFFDF500FDF9D3 /* Gemini-iOS */ = { + isa = PBXGroup; + children = ( + D6E152A424BFFDF500FDF9D3 /* AppDelegate.swift */, + D6E152A624BFFDF500FDF9D3 /* SceneDelegate.swift */, + D6E152A824BFFDF500FDF9D3 /* ContentView.swift */, + D6E152AA24BFFDF600FDF9D3 /* Assets.xcassets */, + D6E152AF24BFFDF600FDF9D3 /* LaunchScreen.storyboard */, + D6E152B224BFFDF600FDF9D3 /* Info.plist */, + D6E152AC24BFFDF600FDF9D3 /* Preview Content */, + ); + path = "Gemini-iOS"; + sourceTree = ""; + }; + D6E152AC24BFFDF600FDF9D3 /* Preview Content */ = { + isa = PBXGroup; + children = ( + D6E152AD24BFFDF600FDF9D3 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + D6E152D124C0007200FDF9D3 /* BrowserCore */ = { + isa = PBXGroup; + children = ( + D6E152D224C0007200FDF9D3 /* BrowserCore.h */, + D6E152D324C0007200FDF9D3 /* Info.plist */, + D69F00AF24BEA84D00E37622 /* NavigationManager.swift */, + D626646024BBF1C200DF9B88 /* BrowserView.swift */, + ); + path = BrowserCore; + sourceTree = ""; + }; + D6E152DE24C0007200FDF9D3 /* BrowserCoreTests */ = { + isa = PBXGroup; + children = ( + D6E152DF24C0007200FDF9D3 /* BrowserCoreTests.swift */, + D6E152E124C0007200FDF9D3 /* Info.plist */, + ); + path = BrowserCoreTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -416,6 +589,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D6E152CB24C0007200FDF9D3 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D6E152E224C0007200FDF9D3 /* BrowserCore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -434,6 +615,7 @@ D626648B24BBF22E00DF9B88 /* PBXTargetDependency */, D62664BC24BBF26A00DF9B88 /* PBXTargetDependency */, D62664E224BC081B00DF9B88 /* PBXTargetDependency */, + D6E152E424C0007200FDF9D3 /* PBXTargetDependency */, ); name = Gemini; productName = Gemini; @@ -553,6 +735,65 @@ productReference = D62664D624BC081B00DF9B88 /* GeminiRendererTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + D6E152A124BFFDF500FDF9D3 /* Gemini-iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = D6E152B324BFFDF600FDF9D3 /* Build configuration list for PBXNativeTarget "Gemini-iOS" */; + buildPhases = ( + D6E1529E24BFFDF500FDF9D3 /* Sources */, + D6E1529F24BFFDF500FDF9D3 /* Frameworks */, + D6E152A024BFFDF500FDF9D3 /* Resources */, + D6E152C224BFFE2500FDF9D3 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D6E152B924BFFE2400FDF9D3 /* PBXTargetDependency */, + D6E152C624C0004C00FDF9D3 /* PBXTargetDependency */, + D6E152CA24C0004C00FDF9D3 /* PBXTargetDependency */, + D6E152F024C0009300FDF9D3 /* PBXTargetDependency */, + ); + name = "Gemini-iOS"; + productName = "Gemini-iOS"; + productReference = D6E152A224BFFDF500FDF9D3 /* Gemini-iOS.app */; + productType = "com.apple.product-type.application"; + }; + D6E152CF24C0007200FDF9D3 /* BrowserCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = D6E152E724C0007200FDF9D3 /* Build configuration list for PBXNativeTarget "BrowserCore" */; + buildPhases = ( + D6E152CB24C0007200FDF9D3 /* Headers */, + D6E152CC24C0007200FDF9D3 /* Sources */, + D6E152CD24C0007200FDF9D3 /* Frameworks */, + D6E152CE24C0007200FDF9D3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BrowserCore; + productName = BrowserCore; + productReference = D6E152D024C0007200FDF9D3 /* BrowserCore.framework */; + productType = "com.apple.product-type.framework"; + }; + D6E152D724C0007200FDF9D3 /* BrowserCoreTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D6E152EA24C0007200FDF9D3 /* Build configuration list for PBXNativeTarget "BrowserCoreTests" */; + buildPhases = ( + D6E152D424C0007200FDF9D3 /* Sources */, + D6E152D524C0007200FDF9D3 /* Frameworks */, + D6E152D624C0007200FDF9D3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D6E152DB24C0007200FDF9D3 /* PBXTargetDependency */, + D6E152DD24C0007200FDF9D3 /* PBXTargetDependency */, + ); + name = BrowserCoreTests; + productName = BrowserCoreTests; + productReference = D6E152D824C0007200FDF9D3 /* BrowserCoreTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -589,6 +830,16 @@ CreatedOnToolsVersion = 12.0; TestTargetID = D626645A24BBF1C200DF9B88; }; + D6E152A124BFFDF500FDF9D3 = { + CreatedOnToolsVersion = 12.0; + }; + D6E152CF24C0007200FDF9D3 = { + CreatedOnToolsVersion = 12.0; + }; + D6E152D724C0007200FDF9D3 = { + CreatedOnToolsVersion = 12.0; + TestTargetID = D626645A24BBF1C200DF9B88; + }; }; }; buildConfigurationList = D626645624BBF1C200DF9B88 /* Build configuration list for PBXProject "Gemini" */; @@ -605,12 +856,15 @@ projectRoot = ""; targets = ( D626645A24BBF1C200DF9B88 /* Gemini */, + D6E152A124BFFDF500FDF9D3 /* Gemini-iOS */, D626647624BBF22E00DF9B88 /* GeminiProtocol */, D626647E24BBF22E00DF9B88 /* GeminiProtocolTests */, D62664A724BBF26A00DF9B88 /* GeminiFormat */, D62664AF24BBF26A00DF9B88 /* GeminiFormatTests */, D62664CD24BC081B00DF9B88 /* GeminiRenderer */, D62664D524BC081B00DF9B88 /* GeminiRendererTests */, + D6E152CF24C0007200FDF9D3 /* BrowserCore */, + D6E152D724C0007200FDF9D3 /* BrowserCoreTests */, ); }; /* End PBXProject section */ @@ -669,6 +923,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D6E152A024BFFDF500FDF9D3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D6E152B124BFFDF600FDF9D3 /* LaunchScreen.storyboard in Resources */, + D6E152AE24BFFDF600FDF9D3 /* Preview Assets.xcassets in Resources */, + D6E152AB24BFFDF600FDF9D3 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D6E152CE24C0007200FDF9D3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D6E152D624C0007200FDF9D3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -677,9 +955,8 @@ buildActionMask = 2147483647; files = ( D6E1529824BFAAA400FDF9D3 /* BrowserWindowController.swift in Sources */, - D626646124BBF1C200DF9B88 /* ContentView.swift in Sources */, D626645F24BBF1C200DF9B88 /* AppDelegate.swift in Sources */, - D69F00B024BEA84D00E37622 /* NavigationManager.swift in Sources */, + D6E152F424C000DA00FDF9D3 /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -744,6 +1021,33 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D6E1529E24BFFDF500FDF9D3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D6E152A524BFFDF500FDF9D3 /* AppDelegate.swift in Sources */, + D6E152A724BFFDF500FDF9D3 /* SceneDelegate.swift in Sources */, + D6E152A924BFFDF500FDF9D3 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D6E152CC24C0007200FDF9D3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D6E152F124C000A000FDF9D3 /* NavigationManager.swift in Sources */, + D6E152F224C000CD00FDF9D3 /* BrowserView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D6E152D424C0007200FDF9D3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D6E152E024C0007200FDF9D3 /* BrowserCoreTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -797,8 +1101,54 @@ target = D62664A724BBF26A00DF9B88 /* GeminiFormat */; targetProxy = D62664F224BC0D7700DF9B88 /* PBXContainerItemProxy */; }; + D6E152B924BFFE2400FDF9D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D62664A724BBF26A00DF9B88 /* GeminiFormat */; + targetProxy = D6E152B824BFFE2400FDF9D3 /* PBXContainerItemProxy */; + }; + D6E152C624C0004C00FDF9D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D626647624BBF22E00DF9B88 /* GeminiProtocol */; + targetProxy = D6E152C524C0004C00FDF9D3 /* PBXContainerItemProxy */; + }; + D6E152CA24C0004C00FDF9D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D62664CD24BC081B00DF9B88 /* GeminiRenderer */; + targetProxy = D6E152C924C0004C00FDF9D3 /* PBXContainerItemProxy */; + }; + D6E152DB24C0007200FDF9D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D6E152CF24C0007200FDF9D3 /* BrowserCore */; + targetProxy = D6E152DA24C0007200FDF9D3 /* PBXContainerItemProxy */; + }; + D6E152DD24C0007200FDF9D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D626645A24BBF1C200DF9B88 /* Gemini */; + targetProxy = D6E152DC24C0007200FDF9D3 /* PBXContainerItemProxy */; + }; + D6E152E424C0007200FDF9D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D6E152CF24C0007200FDF9D3 /* BrowserCore */; + targetProxy = D6E152E324C0007200FDF9D3 /* PBXContainerItemProxy */; + }; + D6E152F024C0009300FDF9D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D6E152CF24C0007200FDF9D3 /* BrowserCore */; + targetProxy = D6E152EF24C0009300FDF9D3 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ +/* Begin PBXVariantGroup section */ + D6E152AF24BFFDF600FDF9D3 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D6E152B024BFFDF600FDF9D3 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ D626646C24BBF1C300DF9B88 /* Debug */ = { isa = XCBuildConfiguration; @@ -979,6 +1329,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = GeminiProtocol/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -988,8 +1339,10 @@ PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiProtocol; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + VALID_ARCHS = "x86_64 arm64 armv7s armv7 arm64e"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1009,6 +1362,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = GeminiProtocol/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -1018,7 +1372,9 @@ PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiProtocol; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; SWIFT_VERSION = 5.0; + VALID_ARCHS = "x86_64 arm64 armv7s armv7 arm64e"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1078,6 +1434,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = GeminiFormat/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -1087,8 +1444,10 @@ PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiFormat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + VALID_ARCHS = "x86_64 arm64 armv7s armv7 arm64e"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1108,6 +1467,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = GeminiFormat/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -1117,7 +1477,9 @@ PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiFormat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; SWIFT_VERSION = 5.0; + VALID_ARCHS = "x86_64 arm64 armv7s armv7 arm64e"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1177,6 +1539,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = GeminiRenderer/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -1186,8 +1549,10 @@ PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiRenderer; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + VALID_ARCHS = "x86_64 arm64 armv7s armv7 arm64e"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1207,6 +1572,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = GeminiRenderer/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -1216,7 +1582,9 @@ PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.GeminiRenderer; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; SWIFT_VERSION = 5.0; + VALID_ARCHS = "x86_64 arm64 armv7s armv7 arm64e"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1262,6 +1630,157 @@ }; name = Release; }; + D6E152B424BFFDF600FDF9D3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"Gemini-iOS/Preview Content\""; + DEVELOPMENT_TEAM = HGYVAQA9FW; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "Gemini-iOS/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "net.shadowfacts.Gemini-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D6E152B524BFFDF600FDF9D3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"Gemini-iOS/Preview Content\""; + DEVELOPMENT_TEAM = HGYVAQA9FW; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = "Gemini-iOS/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "net.shadowfacts.Gemini-iOS"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D6E152E824C0007200FDF9D3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = HGYVAQA9FW; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = BrowserCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.BrowserCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; + SWIFT_VERSION = 5.0; + VALID_ARCHS = "x86_64 arm64 armv7s armv7 arm64e"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + D6E152E924C0007200FDF9D3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = HGYVAQA9FW; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = BrowserCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.BrowserCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos"; + SWIFT_VERSION = 5.0; + VALID_ARCHS = "x86_64 arm64 armv7s armv7 arm64e"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + D6E152EB24C0007200FDF9D3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HGYVAQA9FW; + INFOPLIST_FILE = BrowserCoreTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.BrowserCoreTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Gemini.app/Contents/MacOS/Gemini"; + }; + name = Debug; + }; + D6E152EC24C0007200FDF9D3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = HGYVAQA9FW; + INFOPLIST_FILE = BrowserCoreTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.BrowserCoreTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Gemini.app/Contents/MacOS/Gemini"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1337,6 +1856,33 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D6E152B324BFFDF600FDF9D3 /* Build configuration list for PBXNativeTarget "Gemini-iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D6E152B424BFFDF600FDF9D3 /* Debug */, + D6E152B524BFFDF600FDF9D3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D6E152E724C0007200FDF9D3 /* Build configuration list for PBXNativeTarget "BrowserCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D6E152E824C0007200FDF9D3 /* Debug */, + D6E152E924C0007200FDF9D3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D6E152EA24C0007200FDF9D3 /* Build configuration list for PBXNativeTarget "BrowserCoreTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D6E152EB24C0007200FDF9D3 /* Debug */, + D6E152EC24C0007200FDF9D3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = D626645324BBF1C200DF9B88 /* Project object */; diff --git a/Gemini.xcodeproj/xcshareddata/xcschemes/Gemini.xcscheme b/Gemini.xcodeproj/xcshareddata/xcschemes/Gemini.xcscheme index 8484cc1..acaaab4 100644 --- a/Gemini.xcodeproj/xcshareddata/xcschemes/Gemini.xcscheme +++ b/Gemini.xcodeproj/xcshareddata/xcschemes/Gemini.xcscheme @@ -58,6 +58,16 @@ ReferencedContainer = "container:Gemini.xcodeproj"> + + + + SchemeUserState + BrowserCore.xcscheme_^#shared#^_ + + orderHint + 5 + + Gemini-iOS.xcscheme_^#shared#^_ + + orderHint + 2 + Gemini.xcscheme_^#shared#^_ orderHint @@ -17,12 +27,12 @@ GeminiProtocol.xcscheme_^#shared#^_ orderHint - 2 + 3 GeminiRenderer.xcscheme_^#shared#^_ orderHint - 3 + 4 SuppressBuildableAutocreation diff --git a/Gemini/BrowserWindowController.swift b/Gemini/BrowserWindowController.swift index ad971f2..c9cc140 100644 --- a/Gemini/BrowserWindowController.swift +++ b/Gemini/BrowserWindowController.swift @@ -8,6 +8,7 @@ import Cocoa import SwiftUI import Combine +import BrowserCore class BrowserWindowController: NSWindowController { @@ -26,7 +27,7 @@ class BrowserWindowController: NSWindowController { override func windowDidLoad() { super.windowDidLoad() - contentViewController = NSHostingController(rootView: ContentView(navigator: navigator)) + contentViewController = NSHostingController(rootView: BrowserView(navigator: navigator)) urlUpdater = navigator.$currentURL.sink(receiveValue: self.currentURLChanged(_:)) } @@ -138,7 +139,7 @@ extension BrowserWindowController: NSToolbarDelegate { } extension NavigationManager: NSToolbarItemValidation { - func validateToolbarItem(_ item: NSToolbarItem) -> Bool { + public func validateToolbarItem(_ item: NSToolbarItem) -> Bool { if item.itemIdentifier == .goBack { return !backStack.isEmpty } else if item.itemIdentifier == .goForward { diff --git a/Gemini/ContentView.swift b/Gemini/ContentView.swift index 3138c22..9ebdb18 100644 --- a/Gemini/ContentView.swift +++ b/Gemini/ContentView.swift @@ -6,83 +6,14 @@ // import SwiftUI -import GeminiFormat -import GeminiRenderer -import GeminiProtocol +import BrowserCore struct ContentView: View { let navigator: NavigationManager - @State var task: GeminiDataTask? - @State var state: ViewState = .loading var body: some View { - mainView + BrowserView(navigator: navigator) .frame(minWidth: 480, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) - .onReceive(navigator.$currentURL, perform: self.urlChanged) - } - - @ViewBuilder - private var mainView: some View { - switch state { - case .loading: - loadingView - .onAppear(perform: self.loadDocument) - case let .error(message): - VStack { - Text("An error occurred") - .font(.headline) - Text(message) - } - case let .document(doc): - DocumentView(document: doc, changeURL: navigator.changeURL) - } - } - - @ViewBuilder - private var loadingView: some View { - if #available(macOS 10.16, iOS 14.0, *) { - ProgressView("Loading...") - } else { - Text("Loading...") - } - } - - private func loadDocument() { - let url = navigator.currentURL - task = try! GeminiDataTask(url: url, completion: { (response) in - self.task = nil - switch response { - case let .failure(error): - state = .error(error.localizedDescription) - case let .success(response): - if response.status.isRedirect { - if let redirect = URL(string: response.meta) { - navigator.changeURL(redirect) - loadDocument() - } else { - state = .error("Invalid redirect URL: '\(response.meta)'") - } - } else if response.status.isSuccess, - let text = response.bodyText { - state = .document(GeminiParser.parse(text: text, baseURL: url)) - } else { - state = .error("Unknown error: \(response.header)") - } - } - }) - task!.resume() - } - - private func urlChanged(_ newValue: URL) { - state = .loading - } -} - -extension ContentView { - enum ViewState { - case loading - case document(Document) - case error(String) } } diff --git a/GeminiProtocol/GeminiResponse.swift b/GeminiProtocol/GeminiResponse.swift index 1257f09..902b50a 100644 --- a/GeminiProtocol/GeminiResponse.swift +++ b/GeminiProtocol/GeminiResponse.swift @@ -32,7 +32,7 @@ public struct GeminiResponse { parameters[parts[0].lowercased()] = parts[1] } } - @available(macOS 10.16, *) + @available(macOS 10.16, iOS 14.0, *) public var utiType: UTType? { guard let mimeType = mimeType else { return nil } return UTType.types(tag: mimeType, tagClass: .mimeType, conformingTo: nil).first diff --git a/GeminiRenderer/RenderingBlockView.swift b/GeminiRenderer/RenderingBlockView.swift index 571f1d0..31c3645 100644 --- a/GeminiRenderer/RenderingBlockView.swift +++ b/GeminiRenderer/RenderingBlockView.swift @@ -44,6 +44,13 @@ struct RenderingBlockView: View { private func link(_ url: URL, text: String?) -> some View { let text = text ?? url.absoluteString + + #if os(macOS) + let buttonStyle = LinkButtonStyle() + #elseif os(iOS) + let buttonStyle = PlainButtonStyle() + #endif + let button: some View = Button { self.changeURL?(url) } label: { @@ -53,7 +60,7 @@ struct RenderingBlockView: View { .underline() .frame(maxWidth: .infinity, alignment: .leading) } - .buttonStyle(LinkButtonStyle()) + .buttonStyle(buttonStyle) .onHover { hovering in self.hovering = hovering }