// // 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? 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`(_ 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? @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 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) {} } 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() } }