// // 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 goToRibbon(selectedRibbon: Ribbon, destRibbon: Ribbon, scrollId: Binding, scrollOffset: Binding, refresh: Binding, showOverlay: Binding, appDatabase: AppDatabase, loading: Bool) { Task { // print("SELECTED RIBBON", selectedRibbon) var scrollOffsetToSave = currentOffset var scrollIdToSave = currentId var updatedRibbon = selectedRibbon if selectedRibbon.id != destRibbon.id! || loading { print("switching ribbons") // withAnimation(.spring(response: 0.05)) { // showOverlay.wrappedValue = true showOverlay.wrappedValue = true // } if loading { currentId = destRibbon.scrollId // currentOffset = CGFloat(destRibbon.scrollOffset) } scrollId.wrappedValue = destRibbon.scrollId // print("setting scroll offset") scrollOffset.wrappedValue = CGFloat(destRibbon.scrollOffset) // print(scrollOffset.wrappedValue) // print("end setting scroll offset") refresh.wrappedValue.toggle() var updateSelectRibbon = SelectedRibbon(id: Int64(1), ribbonId: destRibbon.id!) // print("Saving selected ribbon") // print(updateSelectRibbon) do { _ = try await appDatabase.saveSelectedRibbon(&updateSelectRibbon) } catch { // Print("something wrong") } } if !loading { print("not loading") updatedRibbon.scrollOffset = Int(floor(scrollOffsetToSave!)) updatedRibbon.scrollId = scrollIdToSave! _ = try await appDatabase.saveRibbon(&updatedRibbon) // print("saved updatedRibbon", updatedRibbon) // print("UPDATED") // scrollOffsetToSave = userDefaults.object(forKey: "currentOffset") as? CGFloat // scrollIdToSave = userDefaults.object(forKey: "currentId") as? String } else { print("loading") } // print("scrollOffsetToSave: ", scrollOffsetToSave) // print("scrollIdToSave: ", scrollIdToSave) } } extension View { @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Transform) -> some View { if condition { transform(self) } else { self } } } struct RibbonCrown: View { var ribbon: Ribbon @Binding var scrollId: String? @Binding var scrollOffset: CGFloat? @Binding var showOverlay: Bool @Binding var refresh: Bool var draggedRibbon: Ribbon? var isDragging: Bool var height = CGFloat(45) var scale = 0.65 @Environment(\.appDatabase) private var appDatabase @Query(SelectedRibbonRequest()) private var sr: [Ribbon] @State var saveOffset = CGFloat() 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 { goToRibbon(selectedRibbon: sr[0], destRibbon: ribbon, scrollId: $scrollId, scrollOffset: $scrollOffset, refresh: $refresh, showOverlay: $showOverlay, appDatabase: appDatabase, loading: false) } } .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 } struct ContentView: View { // this is for the whole view swiping @State var viewState = CGSize.zero @State var pulledOut = CGSize.zero // scroll location of the main fenestra @State var scrollId: String? @State var scrollOffset: CGFloat? @State var setScrollOffset: CGFloat? @State var showOverlay: Bool = false @State var vertSep = CGFloat(20) @Environment(\.appDatabase) private var appDatabase @Query(SegDenormRequest(book: "bible.mark")) private var segs: [SegDenorm] @State var endedDrag = true @State var readOffset = CGPoint() @State var dragOffset = CGFloat() @State var refresh: Bool = false @State var refresh2: Bool = false @State var draggedRibbon: Ribbon? @State var isDragging = false @State var reorder = true @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, scrollId: $scrollId, scrollOffset: $scrollOffset, showOverlay: $showOverlay, refresh: $refresh, draggedRibbon: draggedRibbon, isDragging: isDragging) .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 { ScrollViewReader { proxy in VisibilityTrackingScrollView(action: handleVisibilityChanged) { // ScrollView { LazyVStack { ForEach(segs) { seg in SegRow(seg: seg, ribbonId: selectedRibbon[0].id!) .id("\(seg.id)") .offset(x: -dragOffset) // .offset(x: pulledOut.width) .padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20)) .trackVisibility(id: "\(seg.id)") } } .background(Color(red: 0.18, green: 0.18, blue: 0.18)) } .onAppear { Print("APPEAR") // Print(selectedRibbon[0]) // scrollId = "3" // scrollOffset = 103 // refresh.toggle() goToRibbon(selectedRibbon: selectedRibbon[0], destRibbon: selectedRibbon[0], scrollId: $scrollId, scrollOffset: $scrollOffset, refresh: $refresh, showOverlay: $showOverlay, appDatabase: appDatabase, loading: true) } .onChange(of: refresh) { _ in // if let target = target { // gTracker!.visibleViews["123123"] = CGFloat(100) // Print("ON CHANGE", gTracker!.visibleViews) // Print("removing", gTracker!.visibleViews.removeAll()) Task { DispatchQueue.main.async { Print("scroll Id target: \(scrollId)") proxy.scrollTo(scrollId!, anchor: .top) // proxy.scrollTo(String(Int(scrollId!)! + 1)) // currentId = scrollId! // if (currentId != scrollId!) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { Print(" scroll id target", scrollId) Print(" current id ", currentId) Print(gTracker!.sortedViewIDs) if currentId! != scrollId! { Print("NO MATCH") } // Print(" scroll offset target", scrollOffset) Print(" current offset ", gTracker!.visibleViews[scrollId!]) var curOffset = gTracker!.visibleViews[scrollId!] Print(" stats", gTracker!.visibleViews) // // setScrollOffset = CGFloat(Int(currentOffset!) * -1 + Int(scrollOffset!)) if curOffset != nil { setScrollOffset = CGFloat(Int(scrollOffset!) - Int(curOffset!)) Print("applying scroll offset \(setScrollOffset)") // // setScrollOffset = CGFloat(Int(scrollOffset!)) // Print("setting scroll offset", setScrollOffset) refresh2.toggle() } else { var adjust = (Int(scrollId!)! - Int(currentId!)!) * 200 Print("adjusting \(adjust)") setScrollOffset = CGFloat(adjust) refresh.toggle() } // // currentId = scrollId! // DispatchQueue.main.async { // currentOffset = scrollOffset! // currentId = scrollId! // } } } } } .introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { scrollView in Print("introspect") if setScrollOffset != nil { DispatchQueue.main.async { scrollView.contentOffset.y = scrollView.contentOffset.y + setScrollOffset! setScrollOffset = nil // self.showOverlay = false withAnimation { showOverlay = false } } } Print("end introspect") } .listStyle(PlainListStyle()) } .zIndex(1) .background(Color(red: 0.2, green: 0.2, blue: 0.2)) .frame(width: geometry.size.width - 50, height: geometry.size.height / 2 - vertSep) Text("separator").foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00))) .gesture( DragGesture() .onChanged { gesture in vertSep = 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 if endedDrag { endedDrag = false scrollOffset = readOffset.y - 20 } Print(viewState.width) if abs(gesture.translation.width) > 20 { viewState.width = gesture.translation.width if gesture.translation.width < -50, pulledOut.width == CGFloat(0) { dragOffset = gesture.translation.width + 50 } } } .onEnded { _ in endedDrag = true var pulledOutWidth = CGFloat(0) if viewState.width < 0 { pulledOutWidth = CGFloat(0) } else if abs(viewState.width + pulledOut.width) > 30 { pulledOutWidth = CGFloat(200) } withAnimation(.spring(response: 0.2)) { pulledOut.width = pulledOutWidth viewState = .zero dragOffset = .zero } } ) if 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) } } } } func handleVisibilityChanged2(_: String, change _: VisibilityChange, tracker _: VisibilityTracker) {} func handleVisibilityChanged(_: String, change _: VisibilityChange, tracker: VisibilityTracker) { // var printRate: Int64 = 10 gTracker = tracker let visibleViews2 = Array(tracker.visibleViews.keys) if visibleViews2.count == 0 { return } // currentId = tracker.sortedViewIDs[tracker.sortedViewIDs.count - 1] currentId = tracker.sortedViewIDs[0] currentOffset = tracker.visibleViews[currentId!]! } } 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 { func Print(_ vars: Any...) -> some View { for v in vars { print(v) } return EmptyView() } }