Compare commits
15 Commits
ac66feadcc
...
203bd1804f
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 203bd1804f | |
Shadowfacts | 364ffe9f94 | |
Shadowfacts | 4f3e1432e7 | |
Shadowfacts | 89b226e321 | |
Shadowfacts | 107c4b0d72 | |
Shadowfacts | 83dad76b82 | |
Shadowfacts | b000f1c2b3 | |
Shadowfacts | 01a3eaf17f | |
Shadowfacts | 8a895b70c8 | |
Shadowfacts | 19848ba8e4 | |
Shadowfacts | 182bb4b79b | |
Shadowfacts | 71b6352395 | |
Shadowfacts | 1449dc215b | |
Shadowfacts | 012ada4af7 | |
Shadowfacts | 57023d204d |
|
@ -39,6 +39,7 @@ public struct BrowserView: View {
|
||||||
Text("An error occurred")
|
Text("An error occurred")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text(message)
|
Text(message)
|
||||||
|
.lineLimit(nil)
|
||||||
case let .document(doc):
|
case let .document(doc):
|
||||||
DocumentView(document: doc, scrollingEnabled: scrollingEnabled, changeURL: navigator.changeURL)
|
DocumentView(document: doc, scrollingEnabled: scrollingEnabled, changeURL: navigator.changeURL)
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
|
@ -19,6 +19,14 @@ public class NavigationManager: NSObject, ObservableObject {
|
||||||
@Published public var backStack = [URL]()
|
@Published public var backStack = [URL]()
|
||||||
@Published public var forwardStack = [URL]()
|
@Published public var forwardStack = [URL]()
|
||||||
|
|
||||||
|
public var displayURL: String {
|
||||||
|
var components = URLComponents(url: currentURL, resolvingAgainstBaseURL: false)!
|
||||||
|
if components.port == 1965 {
|
||||||
|
components.port = nil
|
||||||
|
}
|
||||||
|
return components.string!
|
||||||
|
}
|
||||||
|
|
||||||
public init(url: URL) {
|
public init(url: URL) {
|
||||||
self.currentURL = url
|
self.currentURL = url
|
||||||
}
|
}
|
||||||
|
@ -30,9 +38,6 @@ public class NavigationManager: NSObject, ObservableObject {
|
||||||
delegate?.loadNonGeminiURL(url)
|
delegate?.loadNonGeminiURL(url)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if components.port == 1965 {
|
|
||||||
components.port = nil
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
components.scheme = "gemini"
|
components.scheme = "gemini"
|
||||||
}
|
}
|
||||||
|
@ -43,6 +48,11 @@ public class NavigationManager: NSObject, ObservableObject {
|
||||||
components.path = "/"
|
components.path = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some Gemini servers break on empty paths
|
||||||
|
if components.path.isEmpty {
|
||||||
|
components.path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
let url = components.url!
|
let url = components.url!
|
||||||
|
|
||||||
backStack.append(currentURL)
|
backStack.append(currentURL)
|
||||||
|
@ -61,10 +71,28 @@ public class NavigationManager: NSObject, ObservableObject {
|
||||||
currentURL = backStack.removeLast()
|
currentURL = backStack.removeLast()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func back(count: Int) {
|
||||||
|
guard count <= backStack.count else { return }
|
||||||
|
var removed = backStack.suffix(count)
|
||||||
|
backStack.removeLast(count)
|
||||||
|
forwardStack.insert(currentURL, at: 0)
|
||||||
|
currentURL = removed.removeFirst()
|
||||||
|
forwardStack.insert(contentsOf: removed, at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
@objc public func forward() {
|
@objc public func forward() {
|
||||||
guard !forwardStack.isEmpty else { return }
|
guard !forwardStack.isEmpty else { return }
|
||||||
backStack.append(currentURL)
|
backStack.append(currentURL)
|
||||||
currentURL = forwardStack.removeFirst()
|
currentURL = forwardStack.removeFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func forward(count: Int) {
|
||||||
|
guard count <= forwardStack.count else { return }
|
||||||
|
var removed = forwardStack.prefix(count)
|
||||||
|
forwardStack.removeFirst(count)
|
||||||
|
backStack.append(currentURL)
|
||||||
|
currentURL = removed.removeLast()
|
||||||
|
backStack.append(contentsOf: removed)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// ActivityView.swift
|
||||||
|
// Gemini-iOS
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/30/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ActivityView: UIViewControllerRepresentable {
|
||||||
|
typealias UIViewControllerType = UIActivityViewController
|
||||||
|
|
||||||
|
let items: [Any]
|
||||||
|
let activities: [UIActivity]?
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> UIActivityViewController {
|
||||||
|
return UIActivityViewController(activityItems: items, applicationActivities: activities)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -81,10 +81,7 @@ class BrowserViewController: UIViewController, UIScrollViewDelegate {
|
||||||
navBarHost.view.topAnchor.constraint(equalTo: view.topAnchor),
|
navBarHost.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
toolBarHost = UIHostingController(rootView: ToolBar(navigator: navigator, shareCurrentURL: {
|
toolBarHost = UIHostingController(rootView: ToolBar(navigator: navigator))
|
||||||
let vc = UIActivityViewController(activityItems: [self.navigator.currentURL], applicationActivities: nil)
|
|
||||||
self.present(vc, animated: true)
|
|
||||||
}))
|
|
||||||
toolBarHost.view.translatesAutoresizingMaskIntoConstraints = false
|
toolBarHost.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(toolBarHost.view)
|
view.addSubview(toolBarHost.view)
|
||||||
addChild(toolBarHost)
|
addChild(toolBarHost)
|
||||||
|
|
|
@ -8,13 +8,9 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import BrowserCore
|
import BrowserCore
|
||||||
|
|
||||||
// This is not currently used as SwiftUI's ScrollView has no mechanism for detecting when it stops deceleraing,
|
|
||||||
// which is necessary to preven tthe bars from being left in a partially visible state.
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@ObservedObject private var navigator: NavigationManager
|
@ObservedObject private var navigator: NavigationManager
|
||||||
@State private var urlFieldContents: String
|
@State private var urlFieldContents: String
|
||||||
@State private var showPreferencesSheet = false
|
|
||||||
private let shareCurrentURL: () -> Void
|
|
||||||
@State private var prevScrollOffset: CGFloat = 0
|
@State private var prevScrollOffset: CGFloat = 0
|
||||||
@State private var scrollOffset: CGFloat = 0 {
|
@State private var scrollOffset: CGFloat = 0 {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -24,11 +20,11 @@ struct ContentView: View {
|
||||||
@State private var barOffset: CGFloat = 0
|
@State private var barOffset: CGFloat = 0
|
||||||
@State private var navBarHeight: CGFloat = 0
|
@State private var navBarHeight: CGFloat = 0
|
||||||
@State private var toolBarHeight: CGFloat = 0
|
@State private var toolBarHeight: CGFloat = 0
|
||||||
|
@State private var showShareSheet = false
|
||||||
|
|
||||||
init(navigator: NavigationManager, shareCurrentURL: @escaping () -> Void) {
|
init(navigator: NavigationManager) {
|
||||||
self.navigator = navigator
|
self.navigator = navigator
|
||||||
self._urlFieldContents = State(initialValue: navigator.currentURL.absoluteString)
|
self._urlFieldContents = State(initialValue: navigator.currentURL.absoluteString)
|
||||||
self.shareCurrentURL = shareCurrentURL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -52,11 +48,16 @@ struct ContentView: View {
|
||||||
// It's not actually user scrolling, and this screws up our animation, so we ignore it.
|
// It's not actually user scrolling, and this screws up our animation, so we ignore it.
|
||||||
guard abs(delta) != outer.safeAreaInsets.top else { return }
|
guard abs(delta) != outer.safeAreaInsets.top else { return }
|
||||||
|
|
||||||
if delta != 0 {
|
if scrollOffset < 0 {
|
||||||
barOffset += delta
|
barOffset = 0
|
||||||
}
|
} else {
|
||||||
|
if delta != 0 {
|
||||||
|
barOffset += delta
|
||||||
|
}
|
||||||
|
|
||||||
barOffset = max(0, min(navBarHeight + outer.safeAreaInsets.top, barOffset))
|
print(barOffset)
|
||||||
|
barOffset = max(0, min(navBarHeight + outer.safeAreaInsets.top, barOffset))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ struct ContentView: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
ToolBar(navigator: navigator, shareCurrentURL: shareCurrentURL)
|
ToolBar(navigator: navigator, showShareSheet: $showShareSheet)
|
||||||
.background(GeometryReader { (geom: GeometryProxy) in
|
.background(GeometryReader { (geom: GeometryProxy) in
|
||||||
Color.clear.preference(key: ToolBarHeightPrefKey.self, value: geom.frame(in: .global).height)
|
Color.clear.preference(key: ToolBarHeightPrefKey.self, value: geom.frame(in: .global).height)
|
||||||
})
|
})
|
||||||
|
@ -88,9 +89,9 @@ struct ContentView: View {
|
||||||
.onReceive(navigator.$currentURL, perform: { (new) in
|
.onReceive(navigator.$currentURL, perform: { (new) in
|
||||||
urlFieldContents = new.absoluteString
|
urlFieldContents = new.absoluteString
|
||||||
})
|
})
|
||||||
.sheet(isPresented: $showPreferencesSheet, content: {
|
.sheet(isPresented: $showShareSheet) {
|
||||||
PreferencesView(presented: $showPreferencesSheet)
|
ActivityView(items: [navigator.currentURL], activities: nil)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func tweakAppearance() {
|
private func tweakAppearance() {
|
||||||
|
@ -133,6 +134,6 @@ fileprivate enum ScrollDirection {
|
||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ContentView(navigator: NavigationManager(url: URL(string: "gemini://localhost/overview.gmi")!), shareCurrentURL: {})
|
ContentView(navigator: NavigationManager(url: URL(string: "gemini://localhost/overview.gmi")!))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ struct NavigationBar: View {
|
||||||
|
|
||||||
init(navigator: NavigationManager) {
|
init(navigator: NavigationManager) {
|
||||||
self.navigator = navigator
|
self.navigator = navigator
|
||||||
self._urlFieldContents = State(initialValue: navigator.currentURL.absoluteString)
|
self._urlFieldContents = State(initialValue: navigator.displayURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -33,8 +33,8 @@ struct NavigationBar: View {
|
||||||
.foregroundColor(Color(white: colorScheme == .dark ? 0.25 : 0.75))
|
.foregroundColor(Color(white: colorScheme == .dark ? 0.25 : 0.75))
|
||||||
}
|
}
|
||||||
.background(Color(UIColor.systemBackground).edgesIgnoringSafeArea(.top))
|
.background(Color(UIColor.systemBackground).edgesIgnoringSafeArea(.top))
|
||||||
.onReceive(navigator.$currentURL) { (newURL) in
|
.onReceive(navigator.$currentURL) { (_) in
|
||||||
urlFieldContents = newURL.absoluteString
|
urlFieldContents = navigator.displayURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 9/27/20.
|
// Created by Shadowfacts on 9/27/20.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import UIKit
|
||||||
|
|
||||||
class Preferences: Codable, ObservableObject {
|
class Preferences: Codable, ObservableObject {
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ class Preferences: Codable, ObservableObject {
|
||||||
required init(from decoder: Decoder) throws {
|
required init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
theme = try container.decode(UIUserInterfaceStyle.self, forKey: .theme)
|
||||||
|
|
||||||
useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari)
|
useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari)
|
||||||
useReaderMode = try container.decode(Bool.self, forKey: .useReaderMode)
|
useReaderMode = try container.decode(Bool.self, forKey: .useReaderMode)
|
||||||
}
|
}
|
||||||
|
@ -41,16 +43,25 @@ class Preferences: Codable, ObservableObject {
|
||||||
func encode(to encoder: Encoder) throws {
|
func encode(to encoder: Encoder) throws {
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
try container.encode(theme, forKey: .theme)
|
||||||
|
|
||||||
try container.encode(useInAppSafari, forKey: .useInAppSafari)
|
try container.encode(useInAppSafari, forKey: .useInAppSafari)
|
||||||
try container.encode(useReaderMode, forKey: .useReaderMode)
|
try container.encode(useReaderMode, forKey: .useReaderMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Published var theme = UIUserInterfaceStyle.unspecified
|
||||||
|
|
||||||
@Published var useInAppSafari = false
|
@Published var useInAppSafari = false
|
||||||
@Published var useReaderMode = false
|
@Published var useReaderMode = false
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case theme
|
||||||
|
|
||||||
case useInAppSafari
|
case useInAppSafari
|
||||||
case useReaderMode
|
case useReaderMode
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension UIUserInterfaceStyle: Codable {}
|
||||||
|
|
|
@ -10,25 +10,41 @@ import SwiftUI
|
||||||
struct PreferencesView: View {
|
struct PreferencesView: View {
|
||||||
@ObservedObject var preferences: Preferences = .shared
|
@ObservedObject var preferences: Preferences = .shared
|
||||||
|
|
||||||
@Binding var presented: Bool
|
@Environment(\.presentationMode) @Binding var presentationMode: PresentationMode
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List {
|
List {
|
||||||
|
appearanceSection
|
||||||
|
|
||||||
safariSection
|
safariSection
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Preferences")
|
.navigationBarTitle("Preferences")
|
||||||
.insetOrGroupedListStyle()
|
.insetOrGroupedListStyle()
|
||||||
.navigationBarItems(trailing: doneButton)
|
.navigationBarItems(trailing: doneButton)
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
Preferences.save()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var doneButton: some View {
|
private var doneButton: some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
presented = false
|
presentationMode.dismiss()
|
||||||
}, label: {
|
}, label: {
|
||||||
Text("Done")
|
Text("Done")
|
||||||
})
|
})
|
||||||
|
.hoverEffect(.highlight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var appearanceSection: some View {
|
||||||
|
Section(header: Text("Appearance")) {
|
||||||
|
Picker(selection: $preferences.theme, label: Text("Theme")) {
|
||||||
|
Text("Use System Theme").tag(UIUserInterfaceStyle.unspecified)
|
||||||
|
Text("Always Light").tag(UIUserInterfaceStyle.light)
|
||||||
|
Text("Always Dark").tag(UIUserInterfaceStyle.dark)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var safariSection: some View {
|
private var safariSection: some View {
|
||||||
|
@ -56,6 +72,6 @@ struct PreferencesView_Previews: PreviewProvider {
|
||||||
@State static var presented = true
|
@State static var presented = true
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PreferencesView(presented: $presented)
|
PreferencesView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import UIKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import BrowserCore
|
import BrowserCore
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
import Combine
|
||||||
|
|
||||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
|
@ -16,6 +17,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
var navigationManager: NavigationManager!
|
var navigationManager: NavigationManager!
|
||||||
|
|
||||||
|
private var cancellables = [AnyCancellable]()
|
||||||
|
|
||||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
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`.
|
// 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.
|
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||||
|
@ -37,16 +40,23 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
navigationManager.delegate = self
|
navigationManager.delegate = self
|
||||||
|
|
||||||
// Create the SwiftUI view that provides the window contents.
|
// Create the SwiftUI view that provides the window contents.
|
||||||
// let contentView = ContentView(navigator: navigationManager, shareCurrentURL: self.shareCurrentURL)
|
let contentView = ContentView(navigator: navigationManager)
|
||||||
|
|
||||||
// Use a UIHostingController as window root view controller.
|
// Use a UIHostingController as window root view controller.
|
||||||
if let windowScene = scene as? UIWindowScene {
|
if let windowScene = scene as? UIWindowScene {
|
||||||
let window = UIWindow(windowScene: windowScene)
|
let window = UIWindow(windowScene: windowScene)
|
||||||
// window.rootViewController = UIHostingController(rootView: contentView)
|
window.overrideUserInterfaceStyle = Preferences.shared.theme
|
||||||
window.rootViewController = BrowserViewController(navigator: navigationManager)
|
window.rootViewController = UIHostingController(rootView: contentView)
|
||||||
|
// window.rootViewController = BrowserViewController(navigator: navigationManager)
|
||||||
self.window = window
|
self.window = window
|
||||||
window.makeKeyAndVisible()
|
window.makeKeyAndVisible()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Preferences.shared.$theme
|
||||||
|
.sink { (newStyle) in
|
||||||
|
self.window!.overrideUserInterfaceStyle = newStyle
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||||
|
@ -83,11 +93,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
// to restore the scene back to its current state.
|
// to restore the scene back to its current state.
|
||||||
}
|
}
|
||||||
|
|
||||||
private func shareCurrentURL() {
|
|
||||||
let vc = UIActivityViewController(activityItems: [navigationManager.currentURL], applicationActivities: nil)
|
|
||||||
window?.rootViewController?.present(vc, animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SceneDelegate: NavigationManagerDelegate {
|
extension SceneDelegate: NavigationManagerDelegate {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import BrowserCore
|
||||||
|
|
||||||
struct ToolBar: View {
|
struct ToolBar: View {
|
||||||
@ObservedObject var navigator: NavigationManager
|
@ObservedObject var navigator: NavigationManager
|
||||||
let shareCurrentURL: () -> Void
|
@Binding var showShareSheet: Bool
|
||||||
@State private var showPreferencesSheet = false
|
@State private var showPreferencesSheet = false
|
||||||
|
|
||||||
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||||
|
@ -30,6 +30,17 @@ struct ToolBar: View {
|
||||||
Image(systemName: "arrow.left")
|
Image(systemName: "arrow.left")
|
||||||
.font(.system(size: 24))
|
.font(.system(size: 24))
|
||||||
}
|
}
|
||||||
|
.accessibility(label: Text("Back"))
|
||||||
|
.hoverEffect(.highlight)
|
||||||
|
.contextMenu {
|
||||||
|
ForEach(Array(navigator.backStack.suffix(5).enumerated()), id: \.1) { (index, url) in
|
||||||
|
Button {
|
||||||
|
navigator.back(count: min(5, navigator.backStack.count) - index)
|
||||||
|
} label: {
|
||||||
|
Text(verbatim: urlForDisplay(url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.disabled(navigator.backStack.isEmpty)
|
.disabled(navigator.backStack.isEmpty)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
@ -38,6 +49,17 @@ struct ToolBar: View {
|
||||||
Image(systemName: "arrow.right")
|
Image(systemName: "arrow.right")
|
||||||
.font(.system(size: 24))
|
.font(.system(size: 24))
|
||||||
}
|
}
|
||||||
|
.accessibility(label: Text("Forward"))
|
||||||
|
.hoverEffect(.highlight)
|
||||||
|
.contextMenu {
|
||||||
|
ForEach(navigator.forwardStack.prefix(5).enumerated().reversed(), id: \.1) { (index, url) in
|
||||||
|
Button {
|
||||||
|
navigator.forward(count: index + 1)
|
||||||
|
} label: {
|
||||||
|
Text(verbatim: urlForDisplay(url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.disabled(navigator.forwardStack.isEmpty)
|
.disabled(navigator.forwardStack.isEmpty)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
@ -46,13 +68,19 @@ struct ToolBar: View {
|
||||||
Image(systemName: "arrow.clockwise")
|
Image(systemName: "arrow.clockwise")
|
||||||
.font(.system(size: 24))
|
.font(.system(size: 24))
|
||||||
}
|
}
|
||||||
|
.accessibility(label: Text("Reload"))
|
||||||
|
.hoverEffect(.highlight)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button(action: shareCurrentURL) {
|
Button {
|
||||||
|
showShareSheet = true
|
||||||
|
} label: {
|
||||||
Image(systemName: "square.and.arrow.up")
|
Image(systemName: "square.and.arrow.up")
|
||||||
.font(.system(size: 24))
|
.font(.system(size: 24))
|
||||||
}
|
}
|
||||||
|
.accessibility(label: Text("Share"))
|
||||||
|
.hoverEffect(.highlight)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
@ -62,22 +90,36 @@ struct ToolBar: View {
|
||||||
Image(systemName: "gear")
|
Image(systemName: "gear")
|
||||||
.font(.system(size: 24))
|
.font(.system(size: 24))
|
||||||
})
|
})
|
||||||
|
.accessibility(label: Text("Preferences"))
|
||||||
|
.hoverEffect(.highlight)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(minLength: 4)
|
|
||||||
}
|
}
|
||||||
|
.padding(.bottom, 4)
|
||||||
.background(Color(UIColor.systemBackground).edgesIgnoringSafeArea(.bottom))
|
.background(Color(UIColor.systemBackground).edgesIgnoringSafeArea(.bottom))
|
||||||
.sheet(isPresented: $showPreferencesSheet, content: {
|
.sheet(isPresented: $showPreferencesSheet, content: {
|
||||||
PreferencesView(presented: $showPreferencesSheet)
|
PreferencesView()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func urlForDisplay(_ url: URL) -> String {
|
||||||
|
var str = url.host!
|
||||||
|
if let port = url.port,
|
||||||
|
url.scheme != "gemini" || port != 1965 {
|
||||||
|
str += ":\(port)"
|
||||||
|
}
|
||||||
|
str += url.path
|
||||||
|
return str
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ToolBar_Previews: PreviewProvider {
|
struct ToolBar_Previews: PreviewProvider {
|
||||||
|
@State private static var showShareSheet = false
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ToolBar(navigator: NavigationManager(url: URL(string: "gemini://localhost/overview.gmi")!), shareCurrentURL: {})
|
ToolBar(navigator: NavigationManager(url: URL(string: "gemini://localhost/overview.gmi")!), showShareSheet: $showShareSheet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,16 +36,17 @@
|
||||||
D62664EE24BC0BCE00DF9B88 /* MaybeLazyVStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664ED24BC0BCE00DF9B88 /* MaybeLazyVStack.swift */; };
|
D62664EE24BC0BCE00DF9B88 /* MaybeLazyVStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664ED24BC0BCE00DF9B88 /* MaybeLazyVStack.swift */; };
|
||||||
D62664F024BC0D7700DF9B88 /* GeminiFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */; };
|
D62664F024BC0D7700DF9B88 /* GeminiFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D62664A824BBF26A00DF9B88 /* GeminiFormat.framework */; };
|
||||||
D62664FA24BC12BC00DF9B88 /* DocumentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664F924BC12BC00DF9B88 /* DocumentTests.swift */; };
|
D62664FA24BC12BC00DF9B88 /* DocumentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62664F924BC12BC00DF9B88 /* DocumentTests.swift */; };
|
||||||
|
D62BCEE2252553620031D894 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62BCEE1252553620031D894 /* ActivityView.swift */; };
|
||||||
D664673624BD07F700B0B741 /* RenderingBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673524BD07F700B0B741 /* RenderingBlock.swift */; };
|
D664673624BD07F700B0B741 /* RenderingBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673524BD07F700B0B741 /* RenderingBlock.swift */; };
|
||||||
D664673824BD086F00B0B741 /* RenderingBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673724BD086F00B0B741 /* RenderingBlockView.swift */; };
|
D664673824BD086F00B0B741 /* RenderingBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673724BD086F00B0B741 /* RenderingBlockView.swift */; };
|
||||||
D664673A24BD0B8E00B0B741 /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673924BD0B8E00B0B741 /* Fonts.swift */; };
|
D664673A24BD0B8E00B0B741 /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664673924BD0B8E00B0B741 /* Fonts.swift */; };
|
||||||
D691A64E25217C6F00348C4B /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A64D25217C6F00348C4B /* Preferences.swift */; };
|
D691A64E25217C6F00348C4B /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A64D25217C6F00348C4B /* Preferences.swift */; };
|
||||||
D691A66725217FD800348C4B /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A66625217FD800348C4B /* PreferencesView.swift */; };
|
D691A66725217FD800348C4B /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A66625217FD800348C4B /* PreferencesView.swift */; };
|
||||||
D691A6772522382E00348C4B /* BrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A6762522382E00348C4B /* BrowserViewController.swift */; };
|
|
||||||
D691A68725223A4700348C4B /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A68625223A4600348C4B /* NavigationBar.swift */; };
|
D691A68725223A4700348C4B /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A68625223A4600348C4B /* NavigationBar.swift */; };
|
||||||
D691A6A0252242FC00348C4B /* ToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A69F252242FC00348C4B /* ToolBar.swift */; };
|
D691A6A0252242FC00348C4B /* ToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D691A69F252242FC00348C4B /* ToolBar.swift */; };
|
||||||
D69F00AC24BE9DD300E37622 /* GeminiDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */; };
|
D69F00AC24BE9DD300E37622 /* GeminiDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */; };
|
||||||
D69F00AE24BEA29100E37622 /* GeminiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AD24BEA29100E37622 /* GeminiResponse.swift */; };
|
D69F00AE24BEA29100E37622 /* GeminiResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69F00AD24BEA29100E37622 /* GeminiResponse.swift */; };
|
||||||
|
D6DA5783252396030048B65A /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DA5782252396030048B65A /* View+Extensions.swift */; };
|
||||||
D6E1529824BFAAA400FDF9D3 /* BrowserWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */; };
|
D6E1529824BFAAA400FDF9D3 /* BrowserWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */; };
|
||||||
D6E1529924BFAAA400FDF9D3 /* BrowserWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E1529724BFAAA400FDF9D3 /* BrowserWindowController.xib */; };
|
D6E1529924BFAAA400FDF9D3 /* BrowserWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E1529724BFAAA400FDF9D3 /* BrowserWindowController.xib */; };
|
||||||
D6E1529B24BFAEC700FDF9D3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E1529A24BFAEC700FDF9D3 /* MainMenu.xib */; };
|
D6E1529B24BFAEC700FDF9D3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6E1529A24BFAEC700FDF9D3 /* MainMenu.xib */; };
|
||||||
|
@ -293,6 +294,7 @@
|
||||||
D62664EB24BC0B4D00DF9B88 /* DocumentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentView.swift; sourceTree = "<group>"; };
|
D62664EB24BC0B4D00DF9B88 /* DocumentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentView.swift; sourceTree = "<group>"; };
|
||||||
D62664ED24BC0BCE00DF9B88 /* MaybeLazyVStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeLazyVStack.swift; sourceTree = "<group>"; };
|
D62664ED24BC0BCE00DF9B88 /* MaybeLazyVStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeLazyVStack.swift; sourceTree = "<group>"; };
|
||||||
D62664F924BC12BC00DF9B88 /* DocumentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentTests.swift; sourceTree = "<group>"; };
|
D62664F924BC12BC00DF9B88 /* DocumentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentTests.swift; sourceTree = "<group>"; };
|
||||||
|
D62BCEE1252553620031D894 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = "<group>"; };
|
||||||
D664673524BD07F700B0B741 /* RenderingBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingBlock.swift; sourceTree = "<group>"; };
|
D664673524BD07F700B0B741 /* RenderingBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingBlock.swift; sourceTree = "<group>"; };
|
||||||
D664673724BD086F00B0B741 /* RenderingBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingBlockView.swift; sourceTree = "<group>"; };
|
D664673724BD086F00B0B741 /* RenderingBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingBlockView.swift; sourceTree = "<group>"; };
|
||||||
D664673924BD0B8E00B0B741 /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = "<group>"; };
|
D664673924BD0B8E00B0B741 /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = "<group>"; };
|
||||||
|
@ -304,6 +306,7 @@
|
||||||
D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiDataTask.swift; sourceTree = "<group>"; };
|
D69F00AB24BE9DD300E37622 /* GeminiDataTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiDataTask.swift; sourceTree = "<group>"; };
|
||||||
D69F00AD24BEA29100E37622 /* GeminiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiResponse.swift; sourceTree = "<group>"; };
|
D69F00AD24BEA29100E37622 /* GeminiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeminiResponse.swift; sourceTree = "<group>"; };
|
||||||
D69F00AF24BEA84D00E37622 /* NavigationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationManager.swift; sourceTree = "<group>"; };
|
D69F00AF24BEA84D00E37622 /* NavigationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationManager.swift; sourceTree = "<group>"; };
|
||||||
|
D6DA5782252396030048B65A /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserWindowController.swift; sourceTree = "<group>"; };
|
D6E1529624BFAAA400FDF9D3 /* BrowserWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserWindowController.swift; sourceTree = "<group>"; };
|
||||||
D6E1529724BFAAA400FDF9D3 /* BrowserWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BrowserWindowController.xib; sourceTree = "<group>"; };
|
D6E1529724BFAAA400FDF9D3 /* BrowserWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BrowserWindowController.xib; sourceTree = "<group>"; };
|
||||||
D6E1529A24BFAEC700FDF9D3 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
D6E1529A24BFAEC700FDF9D3 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||||
|
@ -526,6 +529,7 @@
|
||||||
D62664ED24BC0BCE00DF9B88 /* MaybeLazyVStack.swift */,
|
D62664ED24BC0BCE00DF9B88 /* MaybeLazyVStack.swift */,
|
||||||
D62664EB24BC0B4D00DF9B88 /* DocumentView.swift */,
|
D62664EB24BC0B4D00DF9B88 /* DocumentView.swift */,
|
||||||
D664673724BD086F00B0B741 /* RenderingBlockView.swift */,
|
D664673724BD086F00B0B741 /* RenderingBlockView.swift */,
|
||||||
|
D6DA5782252396030048B65A /* View+Extensions.swift */,
|
||||||
);
|
);
|
||||||
path = GeminiRenderer;
|
path = GeminiRenderer;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -557,6 +561,7 @@
|
||||||
D691A69F252242FC00348C4B /* ToolBar.swift */,
|
D691A69F252242FC00348C4B /* ToolBar.swift */,
|
||||||
D691A64D25217C6F00348C4B /* Preferences.swift */,
|
D691A64D25217C6F00348C4B /* Preferences.swift */,
|
||||||
D691A66625217FD800348C4B /* PreferencesView.swift */,
|
D691A66625217FD800348C4B /* PreferencesView.swift */,
|
||||||
|
D62BCEE1252553620031D894 /* ActivityView.swift */,
|
||||||
D6E152AA24BFFDF600FDF9D3 /* Assets.xcassets */,
|
D6E152AA24BFFDF600FDF9D3 /* Assets.xcassets */,
|
||||||
D6E152AF24BFFDF600FDF9D3 /* LaunchScreen.storyboard */,
|
D6E152AF24BFFDF600FDF9D3 /* LaunchScreen.storyboard */,
|
||||||
D6E152B224BFFDF600FDF9D3 /* Info.plist */,
|
D6E152B224BFFDF600FDF9D3 /* Info.plist */,
|
||||||
|
@ -1042,6 +1047,7 @@
|
||||||
D664673A24BD0B8E00B0B741 /* Fonts.swift in Sources */,
|
D664673A24BD0B8E00B0B741 /* Fonts.swift in Sources */,
|
||||||
D62664EE24BC0BCE00DF9B88 /* MaybeLazyVStack.swift in Sources */,
|
D62664EE24BC0BCE00DF9B88 /* MaybeLazyVStack.swift in Sources */,
|
||||||
D62664EC24BC0B4D00DF9B88 /* DocumentView.swift in Sources */,
|
D62664EC24BC0B4D00DF9B88 /* DocumentView.swift in Sources */,
|
||||||
|
D6DA5783252396030048B65A /* View+Extensions.swift in Sources */,
|
||||||
D664673824BD086F00B0B741 /* RenderingBlockView.swift in Sources */,
|
D664673824BD086F00B0B741 /* RenderingBlockView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -1059,10 +1065,10 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D691A66725217FD800348C4B /* PreferencesView.swift in Sources */,
|
D691A66725217FD800348C4B /* PreferencesView.swift in Sources */,
|
||||||
D691A6772522382E00348C4B /* BrowserViewController.swift in Sources */,
|
|
||||||
D6E152A524BFFDF500FDF9D3 /* AppDelegate.swift in Sources */,
|
D6E152A524BFFDF500FDF9D3 /* AppDelegate.swift in Sources */,
|
||||||
D691A6A0252242FC00348C4B /* ToolBar.swift in Sources */,
|
D691A6A0252242FC00348C4B /* ToolBar.swift in Sources */,
|
||||||
D6E152A724BFFDF500FDF9D3 /* SceneDelegate.swift in Sources */,
|
D6E152A724BFFDF500FDF9D3 /* SceneDelegate.swift in Sources */,
|
||||||
|
D62BCEE2252553620031D894 /* ActivityView.swift in Sources */,
|
||||||
D691A68725223A4700348C4B /* NavigationBar.swift in Sources */,
|
D691A68725223A4700348C4B /* NavigationBar.swift in Sources */,
|
||||||
D691A64E25217C6F00348C4B /* Preferences.swift in Sources */,
|
D691A64E25217C6F00348C4B /* Preferences.swift in Sources */,
|
||||||
D6E152A924BFFDF500FDF9D3 /* ContentView.swift in Sources */,
|
D6E152A924BFFDF500FDF9D3 /* ContentView.swift in Sources */,
|
||||||
|
@ -1325,7 +1331,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = Gemini/Gemini.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Gemini/Gemini.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Gemini/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Gemini/Preview Content\"";
|
||||||
|
@ -1351,7 +1357,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = Gemini/Gemini.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Gemini/Gemini.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Gemini/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Gemini/Preview Content\"";
|
||||||
|
@ -1692,7 +1698,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CURRENT_PROJECT_VERSION = 3;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Gemini-iOS/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Gemini-iOS/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
@ -1718,7 +1724,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CURRENT_PROJECT_VERSION = 3;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Gemini-iOS/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Gemini-iOS/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
<EnvironmentVariable
|
<EnvironmentVariable
|
||||||
key = "DEFAULT_URL"
|
key = "DEFAULT_URL"
|
||||||
value = "gemini://drewdevault.com"
|
value = "gemini://drewdevault.com"
|
||||||
isEnabled = "YES">
|
isEnabled = "NO">
|
||||||
</EnvironmentVariable>
|
</EnvironmentVariable>
|
||||||
</EnvironmentVariables>
|
</EnvironmentVariables>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
|
|
|
@ -27,12 +27,12 @@
|
||||||
<key>GeminiProtocol.xcscheme_^#shared#^_</key>
|
<key>GeminiProtocol.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>3</integer>
|
<integer>4</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>GeminiRenderer.xcscheme_^#shared#^_</key>
|
<key>GeminiRenderer.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>4</integer>
|
<integer>3</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -108,8 +108,8 @@ extension BrowserWindowController: NSToolbarDelegate {
|
||||||
item.label = "Go Back"
|
item.label = "Go Back"
|
||||||
item.paletteLabel = "Go Back"
|
item.paletteLabel = "Go Back"
|
||||||
item.toolTip = "Go to the previous page"
|
item.toolTip = "Go to the previous page"
|
||||||
item.target = navigator
|
item.target = self
|
||||||
item.action = #selector(NavigationManager.back)
|
item.action = #selector(back)
|
||||||
item.isBordered = true
|
item.isBordered = true
|
||||||
if #available(macOS 10.16, *) {
|
if #available(macOS 10.16, *) {
|
||||||
item.isNavigational = true
|
item.isNavigational = true
|
||||||
|
@ -127,8 +127,8 @@ extension BrowserWindowController: NSToolbarDelegate {
|
||||||
item.label = "Go Forward"
|
item.label = "Go Forward"
|
||||||
item.paletteLabel = "Go Forward"
|
item.paletteLabel = "Go Forward"
|
||||||
item.toolTip = "Go to the next page"
|
item.toolTip = "Go to the next page"
|
||||||
item.target = navigator
|
item.target = self
|
||||||
item.action = #selector(NavigationManager.forward)
|
item.action = #selector(forward)
|
||||||
item.isBordered = true
|
item.isBordered = true
|
||||||
if #available(macOS 10.16, *) {
|
if #available(macOS 10.16, *) {
|
||||||
item.isNavigational = true
|
item.isNavigational = true
|
||||||
|
@ -136,14 +136,22 @@ extension BrowserWindowController: NSToolbarDelegate {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func back() {
|
||||||
|
navigator.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func forward() {
|
||||||
|
navigator.forward()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NavigationManager: NSToolbarItemValidation {
|
extension BrowserWindowController: NSToolbarItemValidation {
|
||||||
public func validateToolbarItem(_ item: NSToolbarItem) -> Bool {
|
public func validateToolbarItem(_ item: NSToolbarItem) -> Bool {
|
||||||
if item.itemIdentifier == .goBack {
|
if item.itemIdentifier == .goBack {
|
||||||
return !backStack.isEmpty
|
return !navigator.backStack.isEmpty
|
||||||
} else if item.itemIdentifier == .goForward {
|
} else if item.itemIdentifier == .goForward {
|
||||||
return !forwardStack.isEmpty
|
return !navigator.forwardStack.isEmpty
|
||||||
} else {
|
} else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ public struct DocumentView: View {
|
||||||
private var scrollBody: some View {
|
private var scrollBody: some View {
|
||||||
MaybeLazyVStack(alignment: .leading) {
|
MaybeLazyVStack(alignment: .leading) {
|
||||||
ForEach(blocks.indices) { (index) in
|
ForEach(blocks.indices) { (index) in
|
||||||
RenderingBlockView(block: blocks[index], changeURL: changeURL)
|
RenderingBlockView(document: document, block: blocks[index], changeURL: changeURL)
|
||||||
}
|
}
|
||||||
}.padding([.leading, .trailing, .bottom])
|
}.padding([.leading, .trailing, .bottom])
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,15 @@ import SwiftUI
|
||||||
import GeminiFormat
|
import GeminiFormat
|
||||||
|
|
||||||
struct RenderingBlockView: View {
|
struct RenderingBlockView: View {
|
||||||
|
let document: Document
|
||||||
let block: RenderingBlock
|
let block: RenderingBlock
|
||||||
let changeURL: ((URL) -> Void)?
|
let changeURL: ((URL) -> Void)?
|
||||||
@State var hovering = false
|
@State var hovering = false
|
||||||
|
|
||||||
init(block: RenderingBlock, changeURL: ((URL) -> Void)? = nil) {
|
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||||
|
|
||||||
|
init(document: Document, block: RenderingBlock, changeURL: ((URL) -> Void)? = nil) {
|
||||||
|
self.document = document
|
||||||
self.block = block
|
self.block = block
|
||||||
self.changeURL = changeURL
|
self.changeURL = changeURL
|
||||||
}
|
}
|
||||||
|
@ -37,7 +41,7 @@ struct RenderingBlockView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func text(_ text: String) -> some View {
|
private func text(_ text: String) -> some View {
|
||||||
Text(text)
|
Text(verbatim: text)
|
||||||
.font(.documentBody)
|
.font(.documentBody)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
@ -51,24 +55,50 @@ struct RenderingBlockView: View {
|
||||||
let buttonStyle = PlainButtonStyle()
|
let buttonStyle = PlainButtonStyle()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
let imageName: String
|
||||||
|
if url.scheme == "gemini" {
|
||||||
|
if url.host == document.url.host {
|
||||||
|
imageName = "arrow.right"
|
||||||
|
} else {
|
||||||
|
imageName = "link"
|
||||||
|
}
|
||||||
|
} else if url.scheme == "http" || url.scheme == "https" {
|
||||||
|
imageName = "safari"
|
||||||
|
} else if url.scheme == "mailto" {
|
||||||
|
imageName = "envelope"
|
||||||
|
} else {
|
||||||
|
imageName = "arrow.up.left.square"
|
||||||
|
}
|
||||||
|
|
||||||
let button: some View = Button {
|
let button: some View = Button {
|
||||||
self.changeURL?(url)
|
self.changeURL?(url)
|
||||||
} label: {
|
} label: {
|
||||||
Text(verbatim: text)
|
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||||
.font(.documentBody)
|
maybeLinkImage(name: imageName)
|
||||||
.foregroundColor(hovering ? .blue : Color.blue.opacity(0.8))
|
|
||||||
.underline()
|
Text(verbatim: text)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.font(.documentBody)
|
||||||
|
.foregroundColor(colorScheme == .dark ?
|
||||||
|
hovering ? Color.blue.opacity(0.8) : .blue :
|
||||||
|
hovering ? .blue : Color.blue.opacity(0.8))
|
||||||
|
.underline()
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(buttonStyle)
|
.buttonStyle(buttonStyle)
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
self.hovering = hovering
|
self.hovering = hovering
|
||||||
}
|
}
|
||||||
|
|
||||||
if #available(macOS 10.16, iOS 14.0, *) {
|
return button.maybeHelp(url.absoluteString)
|
||||||
return AnyView(button.help(url.absoluteString))
|
}
|
||||||
|
|
||||||
|
private func maybeLinkImage(name: String) -> AnyView {
|
||||||
|
// can't use availability check inside view body, since buildLimitedAvailability was introduced in iOS 14 :/
|
||||||
|
if #available(iOS 13.0, macOS 11.0, *) {
|
||||||
|
return AnyView(Image(systemName: name).frame(minWidth: 23, alignment: .leading))
|
||||||
} else {
|
} else {
|
||||||
return AnyView(button)
|
return AnyView(EmptyView())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,10 +139,12 @@ struct RenderingBlockView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RenderingBlockView_Previews: PreviewProvider {
|
struct RenderingBlockView_Previews: PreviewProvider {
|
||||||
|
static let doc = Document(url: URL(string: "gemini://localhost/test.gmi")!, lines: [.text("Some Text"), .quote("A Quote")])
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
Group {
|
Group {
|
||||||
RenderingBlockView(block: .text("Some Text"))
|
RenderingBlockView(document: doc, block: .text("Some Text"))
|
||||||
RenderingBlockView(block: .quote("A Quote"))
|
RenderingBlockView(document: doc, block: .quote("A Quote"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// View+Extensions.swift
|
||||||
|
// GeminiRenderer
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/29/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func maybeHelp(_ help: String) -> some View {
|
||||||
|
if #available(iOS 14.0, macOS 11.0, *) {
|
||||||
|
self.help(help)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue