gloss-ios/gloss/ContentView.swift

554 lines
19 KiB
Swift

//
// ContentView.swift
// gloss
//
// Created by Saint on 10/23/22.
//
import GRDB
import GRDBQuery
import os
import SwiftUI
import SwiftUIIntrospect
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network")
var currentId: String?
var currentOffset: CGFloat?
//var gTracker: VisibilityTracker<String>?
var printCount: Int64 = 0
var disableDrop = false
public extension UserDefaults {
func optionalInt(forKey defaultName: String) -> Int? {
let defaults = self
if let value = defaults.value(forKey: defaultName) {
return value as? Int
}
return nil
}
func optionalBool(forKey defaultName: String) -> Bool? {
let defaults = self
if let value = defaults.value(forKey: defaultName) {
return value as? Bool
}
return nil
}
}
func createUndoState(selectedRibbon: Ribbon,
appDatabase : AppDatabase,
paneConnector : PaneConnector) async throws -> [Ribbon]
{
let updateThreshold = 30
print("meow updating ribbon")
var updatedRibbon = selectedRibbon
var scrollOffsetToSave = Int(floor(paneConnector.currentOffset))
var scrollIdToSave = paneConnector.currentId
print("meow scrolloffsets \(scrollIdToSave) \(scrollOffsetToSave)")
var offsetDiff = abs(scrollOffsetToSave - updatedRibbon.scrollOffset) > 30
var idDiff = Int(updatedRibbon.scrollId) != Int(scrollIdToSave)
if idDiff || offsetDiff {
updatedRibbon.scrollId = scrollIdToSave
updatedRibbon.scrollOffset = scrollOffsetToSave
print("meow bumping")
let ret = try await appDatabase.bumpRibbon(&updatedRibbon)
print("meow finished bumping")
return ret
}
print("meow no bump")
return []
}
func goToRibbon(selectedRibbon: Ribbon,
destRibbon: Ribbon,
appDatabase: AppDatabase,
paneConnector: PaneConnector,
loading: Bool,
bump: Bool)
{
print("meow goto ribbon - selected ribbon: \(selectedRibbon), dest ribbon: \(destRibbon) ")
DispatchQueue.main.asyncAfter(deadline: .now()) {
Task {
var scrollOffsetToSave = paneConnector.currentOffset
var scrollIdToSave = paneConnector.currentId
print("go to ribbon")
print("\(selectedRibbon.id) \(destRibbon.id!)")
// if selectedRibbon.id != destRibbon.id! || loading {
if true {
print("switching ribbons")
paneConnector.showOverlay = true
if loading {
paneConnector.currentId = destRibbon.scrollId
}
paneConnector.scrollId = destRibbon.scrollId
paneConnector.scrollOffset = CGFloat(destRibbon.scrollOffset)
paneConnector.refresh.toggle()
print("toggling")
print("paneconnector: \(paneConnector.refresh)")
var updateSelectRibbon = SelectedRibbon(id: Int64(1), ribbonGroupId: Int64(destRibbon.groupId))
// print("Saving selected ribbon")
// print(updateSelectRibbon)
do {
_ = try await appDatabase.saveSelectedRibbon(&updateSelectRibbon)
} catch {
// Print("something wrong")
}
}
// // this gets run regardless of if we switch ribbons or not
// // so if you click the same ribbon or a different ribbon
// if !loading || bump {
// print("not loading")
// print("saving ribbon")
// // updating the ribbon location
// updatedRibbon.scrollOffset = Int(floor(scrollOffsetToSave))
// updatedRibbon.scrollId = scrollIdToSave
// // updatedRibbon.undoLevel = updatedRibbon.undoLevel + 1
// // updatedRibbon.currentLevel = updatedRibbon.currentLevel + 1
// if bump {
// _ = try await appDatabase.bumpRibbon(&updatedRibbon)
// }
// } else {
// print("loading")
// }
}
}
}
extension View {
@ViewBuilder
func `if`<Transform: View>(_ condition: Bool, transform: (Self) -> Transform) -> some View {
if condition { transform(self) }
else { self }
}
}
struct RibbonCrown: View {
var ribbon: Ribbon
@ObservedObject var paneConnector: PaneConnector
var draggedRibbon: Ribbon?
var isDragging: Bool
var height = CGFloat(45)
var scale = 0.65
@Environment(\.appDatabase) private var appDatabase
@Query(SelectedRibbonRequest()) private var selectedRibbon: [Ribbon]
@State var saveOffset = CGFloat()
@Binding var refresh: Bool
var body: some View {
ZStack {
MyIcon().frame(
width: CGFloat(100 * 1.66 * scale),
height: CGFloat(100 * scale),
alignment: .center
).foregroundColor(Color(UIColor(red: 0.30, green: 0.30, blue: 0.30, alpha: 0.4)))
.contentShape(.dragPreview, RoundedRectangle(cornerRadius: 32))
.if(draggedRibbon != nil && draggedRibbon!.id == ribbon.id && isDragging) { $0.overlay(Color(red: 0.1, green: 0.1, blue: 0.1)) }
Text(ribbon.title)
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.frame(minWidth: CGFloat(70),
maxWidth: CGFloat(70),
minHeight: height,
maxHeight: height,
alignment: .center)
.if(draggedRibbon != nil && draggedRibbon!.id == ribbon.id && isDragging) { $0.overlay(Color(red: 0.1, green: 0.1, blue: 0.1)) }
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
.multilineTextAlignment(.center)
// .minimumScaleFactor(0.5)
// .padding([.top, .bottom], 10)
.font(Font.custom("AveriaSerifLibre-Regular", size: CGFloat(10)))
}
.onTapGesture {
Task {
let sr = selectedRibbon[0]
let updatedRibbon = try await createUndoState(selectedRibbon: sr,
appDatabase: appDatabase,
paneConnector: paneConnector)
if sr.id == ribbon.id {
paneConnector.scrollId = paneConnector.currentId
paneConnector.scrollOffset = paneConnector.currentOffset
paneConnector.hasMoved = false
} else {
goToRibbon(selectedRibbon: sr,
destRibbon: ribbon,
appDatabase: appDatabase,
paneConnector: paneConnector,
loading: false,
bump: true)
}
}
}
.frame(width: CGFloat(100 * 1.66 * scale + 10), height: CGFloat(100 * scale + 5))
}
}
class Verse: NSObject, Codable {
var body: String
var verse: Int
}
func makeVerseView(seg: SegDenorm) -> some View {
var retView = Text("")
var segSplit = seg.body.components(separatedBy: ";;")
let decoder = JSONDecoder()
for (index, item) in segSplit.enumerated() {
let verse = try! decoder.decode(Verse.self, from: item.data(using: .utf8)!)
retView = retView + Text(String(verse.verse))
.font(Font.custom("AveriaSerifLibre-Regular", size: 6))
.baselineOffset(6.0)
.foregroundColor(Color.white)
retView = retView + Text(verse.body)
.foregroundColor(Color.white)
.font(Font.custom("AveriaSerifLibre-Regular", size: 15))
}
return retView
}
class PaneConnector: NSObject, ObservableObject {
// var scrollId: String
// var scrollOffset: CGFloat
// var setScrollOffset: CGFloat
var showOverlay: Bool = false
@Published var refresh: Bool = false
@Published var vertSep = CGFloat(20)
var currentId = ""
var currentOffset = CGFloat()
var visibilityTracker: VisibilityTracker<String>?
@Published var scrollId = ""
@Published var scrollOffset = CGFloat()
// @Published var hasMoved = false
@Published var hasMoved = false
var setScrollOffset: CGFloat?
}
struct ContentView: View {
// this is for the whole view swiping
@State var viewState = CGSize.zero
@State var pulledOut = CGSize.zero
@State var selection = 0
@StateObject var paneConnector = PaneConnector()
@State var refresh: Bool = false
@State var readOffset = CGPoint()
@Query(SegDenormRequest(book: "bible.john")) private var segs: [SegDenorm]
@State var draggedRibbon: Ribbon?
@State var isDragging = false
@Environment(\.appDatabase) private var appDatabase
@Query(RibbonRequest()) private var ribbons: [Ribbon]
@Query<SelectedRibbonRequest> var selectedRibbon: [Ribbon]
init() {
UITableView.appearance().backgroundColor = UIColor(Color(red: 0.2, green: 0.2, blue: 0.2))
_selectedRibbon = Query(SelectedRibbonRequest())
}
var body: some View {
var fontSize = CGFloat(15)
var scale = 0.65
var height = CGFloat(50)
var width = CGFloat(100 * 1.66 * scale)
GeometryReader { geometry in
ZStack(alignment: .top) {
VStack(alignment: .leading) {
VStack {
ForEach(ribbons) { ribbon in
RibbonCrown(ribbon: ribbon,
paneConnector: paneConnector,
draggedRibbon: draggedRibbon,
isDragging: isDragging,
refresh: $refresh)
.onDrag {
self.draggedRibbon = ribbon
return NSItemProvider()
}
.onDrop(of: [.item],
delegate: DropViewDelegate(destinationItem: ribbon,
draggedItem: $draggedRibbon,
isDragging: $isDragging,
appDatabase: appDatabase))
.offset(x: 6, y: 6)
}
}
.frame(width: geometry.size.width, height: geometry.size.height - 100, alignment: .topLeading)
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
.zIndex(0)
.animation(.default, value: ribbons)
.onDrop(of: [.item],
delegate: DropViewDelegate2(isDragging: $isDragging))
}
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
.frame(alignment: .topLeading)
VStack {
NaviBar(paneConnector: paneConnector)
StatsPanel(paneConnector: paneConnector)
}
.frame(maxWidth: 250)
.offset(x: geometry.size.width - 330)
VStack {
// Top pane
Pane(paneConnector: paneConnector,
selectedRibbon: selectedRibbon,
width: geometry.size.width - 50,
height: geometry.size.height / 2)
//////
Text("separator").foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.gesture(
DragGesture()
.onChanged { gesture in
paneConnector.vertSep = paneConnector.vertSep - gesture.translation.height
Print(gesture.translation.width)
Print(gesture.translation.height)
Print("drag")
}
)
ScrollViewReader { _ in
VisibilityTrackingScrollView(action: handleVisibilityChanged2) {
// ScrollView {
LazyVStack {
ForEach(segs) { seg in
SegRow(seg: seg,
ribbonId: selectedRibbon[0].id!)
.id("\(seg.id)")
.padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20))
.trackVisibility(id: "\(seg.id)")
// .onChange(of: geometry.frame(in: .named("scrollView"))) { imageRect in
// Print(imageRect)
// Print(outerProxy)
// if isInView(innerRect: imageRect, isIn: outerProxy) {
// visibleIndex.insert(item)
// } else {
// visibleIndex.remove(item)
// }
// }
}
}
.background(Color(red: 0.18, green: 0.18, blue: 0.18))
}
.onAppear {
Print("APPEAR")
}
.listStyle(PlainListStyle())
}
.zIndex(1)
.background(Color(red: 0.2, green: 0.2, blue: 0.2))
.frame(width: geometry.size.width - 50)
}
.offset(x: 30, y: 0)
.offset(x: pulledOut.width)
.offset(x: viewState.width, y: viewState.height)
.gesture(
DragGesture()
.onChanged { gesture in
print(viewState.width)
print(pulledOut.width)
// threshold of how much to swipe before the view drags
if abs(gesture.translation.width) > 10 {
viewState.width = gesture.translation.width
// if gesture.translation.width < -50, pulledOut.width == CGFloat(0) {
// }
}
}
.onEnded { _ in
var setPulledOutWith = CGFloat(0)
if viewState.width < 0 && pulledOut.width > 0 {
setPulledOutWith = CGFloat(0)
} else if viewState.width > 0 && pulledOut.width < 0 {
setPulledOutWith = CGFloat(0)
} else if viewState.width < 0 && pulledOut.width == 0 {
setPulledOutWith = CGFloat(-300)
} else if abs(viewState.width + pulledOut.width) > 30 {
setPulledOutWith = CGFloat(200)
}
withAnimation(.spring(response: 0.2)) {
pulledOut.width = setPulledOutWith
viewState = .zero
}
}
)
// if self.paneConnector.showOverlay {
// Rectangle()
// .frame(width: geometry.size.width - 50, height: geometry.size.height + 200)
// .background(.ultraThinMaterial)
// // .blur(radius: 0.8)
// .offset(x: 30, y: -100)
// .opacity(0.98)
// .transition(.opacity)
// // .frame(width: geometry.size.width - 50)
// .offset(x: pulledOut.width)
// .offset(x: viewState.width, y: viewState.height)
// .zIndex(2)
// }
}
.onChange(of: paneConnector.refresh) { _ in
Print("we changing changing")
}
}
}
func handleVisibilityChanged2(_: String, change _: VisibilityChange, tracker _: VisibilityTracker<String>) {}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
struct DropViewDelegate2: DropDelegate {
@Binding var isDragging: Bool
func dropUpdated(info _: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}
func performDrop(info _: DropInfo) -> Bool {
isDragging = false
return true
}
func dropEntered(info _: DropInfo) {
isDragging = true
print("SECOND DROPPPOOO")
}
}
struct DropViewDelegate: DropDelegate {
let destinationItem: Ribbon
// @Binding var colors: [Color]
@Binding var draggedItem: Ribbon?
@Binding var isDragging: Bool
let appDatabase: AppDatabase
func dropUpdated(info _: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}
func dropExited(info _: DropInfo) {
print("EXITED")
isDragging = false
}
func dropEntered(info _: DropInfo) {
Task {
isDragging = true
if draggedItem == nil {
return
}
if disableDrop {
return
}
var newRibbon = draggedItem!
var newDest = destinationItem
var oldPos = draggedItem!.pos
var newPos = destinationItem.pos
print("dragged item")
print(draggedItem)
print("dest item")
print(destinationItem)
if draggedItem!.id! == destinationItem.id! {
return
}
newRibbon.pos = destinationItem.pos
_ = try await appDatabase.updateRibbonPosition(&newRibbon, oldPos, newPos)
disableDrop = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
disableDrop = false
}
draggedItem!.pos = newPos
// draggedItem = nil
}
}
func performDrop(info _: DropInfo) -> Bool {
print("PERFORMED DROPPPP")
draggedItem = nil
isDragging = false
return true
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
ContentView().environment(\.appDatabase, .random())
}
}
extension View {
@discardableResult
func Print(_ vars: Any...) -> some View {
for v in vars {
print(v)
}
return EmptyView()
}
}