Gemini/Gemini-iOS/ContentView.swift

139 lines
5.1 KiB
Swift

//
// ContentView.swift
// Gemini-iOS
//
// Created by Shadowfacts on 7/15/20.
//
import SwiftUI
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 {
@ObservedObject private var navigator: NavigationManager
@State private var urlFieldContents: String
@State private var showPreferencesSheet = false
private let shareCurrentURL: () -> Void
@State private var prevScrollOffset: CGFloat = 0
@State private var scrollOffset: CGFloat = 0 {
didSet {
prevScrollOffset = oldValue
}
}
@State private var barOffset: CGFloat = 0
@State private var navBarHeight: CGFloat = 0
@State private var toolBarHeight: CGFloat = 0
init(navigator: NavigationManager, shareCurrentURL: @escaping () -> Void) {
self.navigator = navigator
self._urlFieldContents = State(initialValue: navigator.currentURL.absoluteString)
self.shareCurrentURL = shareCurrentURL
}
var body: some View {
ZStack {
GeometryReader { (outer: GeometryProxy) in
ScrollView(.vertical) {
Color.clear.frame(height: navBarHeight)
BrowserView(navigator: navigator, scrollingEnabled: false)
.background(GeometryReader { (inner: GeometryProxy) in
Color.clear.preference(key: ScrollOffsetPrefKey.self, value: -inner.frame(in: .global).minY + outer.frame(in: .global).minY)
})
Color.clear.frame(height: toolBarHeight)
}
.onPreferenceChange(ScrollOffsetPrefKey.self) {
scrollOffset = $0
let delta = scrollOffset - prevScrollOffset
// When certain state changes happen, the scroll view seems to "scroll" by the top safe area inset.
// It's not actually user scrolling, and this screws up our animation, so we ignore it.
guard abs(delta) != outer.safeAreaInsets.top else { return }
if delta != 0 {
barOffset += delta
}
barOffset = max(0, min(navBarHeight + outer.safeAreaInsets.top, barOffset))
}
}
VStack(spacing: 0) {
NavigationBar(navigator: navigator)
.background(GeometryReader { (geom: GeometryProxy) in
Color.clear.preference(key: NavBarHeightPrefKey.self, value: geom.frame(in: .global).height)
})
.offset(y: -barOffset)
Spacer()
ToolBar(navigator: navigator, shareCurrentURL: shareCurrentURL)
.background(GeometryReader { (geom: GeometryProxy) in
Color.clear.preference(key: ToolBarHeightPrefKey.self, value: geom.frame(in: .global).height)
})
.offset(y: barOffset)
}
.onPreferenceChange(NavBarHeightPrefKey.self) {
navBarHeight = $0
print("nav bar height: \($0)")
}
.onPreferenceChange(ToolBarHeightPrefKey.self) {
toolBarHeight = $0
print("tool bar height: \($0)")
}
}
.onAppear(perform: tweakAppearance)
.onReceive(navigator.$currentURL, perform: { (new) in
urlFieldContents = new.absoluteString
})
.sheet(isPresented: $showPreferencesSheet, content: {
PreferencesView(presented: $showPreferencesSheet)
})
}
private func tweakAppearance() {
UIScrollView.appearance().keyboardDismissMode = .interactive
}
private func commitURL() {
guard let url = URL(string: urlFieldContents) else { return }
navigator.changeURL(url)
}
}
fileprivate struct ScrollOffsetPrefKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
fileprivate struct NavBarHeightPrefKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
fileprivate struct ToolBarHeightPrefKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
fileprivate enum ScrollDirection {
case up, down, none
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(navigator: NavigationManager(url: URL(string: "gemini://localhost/overview.gmi")!), shareCurrentURL: {})
}
}