diff --git a/ScrollState.swift b/ScrollState.swift new file mode 100644 index 0000000..6f4324f --- /dev/null +++ b/ScrollState.swift @@ -0,0 +1,44 @@ +import GRDB +/// The Seg struct. +/// +/// Identifiable conformance supports SwiftUI list animations, and type-safe +/// GRDB primary key methods. +/// Equatable conformance supports tests. +struct ScrollState: Identifiable, Equatable { + /// Int64 is the recommended type for auto-incremented database ids. + /// Use nil for players that are not inserted yet in the database. + var id: Int64? + var scrollId: String + var scrollOffset: Int64 +} + +extension ScrollState { +} + +// MARK: - Persistence + +/// Make ScrollState a Codable Record. +/// +/// See +extension ScrollState: Codable, FetchableRecord, MutablePersistableRecord { + // Define database columns from CodingKeys + fileprivate enum Columns { + static let id = Column(CodingKeys.id) + static let scrollId = Column(CodingKeys.scrollId) + static let scrollOffset = Column(CodingKeys.scrollOffset) + } + + /// Updates a player id after it has been inserted in the database. + mutating func didInsert(_ inserted: InsertionSuccess) { + id = inserted.rowID + } +} + +// MARK: - ScrollState Database Requests + +/// Define some player requests used by the application. +/// +/// See +/// See +extension DerivableRequest { +} diff --git a/ScrollStateRequest.swift b/ScrollStateRequest.swift new file mode 100644 index 0000000..2886cc1 --- /dev/null +++ b/ScrollStateRequest.swift @@ -0,0 +1,36 @@ +import Combine +import GRDB +import GRDBQuery + +struct ScrollStateRequest: Queryable { + // enum Ordering { + // case byScore + // case byName + // } + + /// The ordering used by the player request. + // var ordering: Ordering + static var defaultValue: [Ribbon] { [] } + + func publisher(in appDatabase: AppDatabase) -> AnyPublisher<[Ribbon], Error> { + // Build the publisher from the general-purpose read-only access + // granted by `appDatabase.reader`. + // Some apps will prefer to call a dedicated method of `appDatabase`. + ValueObservation + .tracking(fetchValue(_:)) + .publisher( + in: appDatabase.reader, + // The `.immediate` scheduling feeds the view right on + // subscription, and avoids an undesired animation when the + // application starts. + scheduling: .immediate) + .eraseToAnyPublisher() + } + + // This method is not required by Queryable, but it makes it easier + func fetchValue(_ db: Database) throws -> [Ribbon] { + var ret = try Ribbon.fetchAll(db, sql: "SELECT * FROM ScrollState LIMIT 1") // [Player] + // print(ret) + return ret + } +} diff --git a/VisibilityTracker.swift b/VisibilityTracker.swift index 88a3575..5e43e41 100644 --- a/VisibilityTracker.swift +++ b/VisibilityTracker.swift @@ -44,7 +44,7 @@ public class VisibilityTracker: ObservableObject { containerBounds = bounds } - public func reportContentBounds(_ bounds: CGRect, id: ID) { + public func reportContentBounds(_ bounds: CGRect, id: ID) { let topLeft = bounds.origin let size = bounds.size let bottomRight = CGPoint(x: topLeft.x + size.width, y: topLeft.y + size.height) @@ -53,6 +53,9 @@ public class VisibilityTracker: ObservableObject { visibleViews[id] = -1 * (bounds.origin.y - containerBounds.origin.y) + if (abs(visibleViews[id]! ) > 1000) { + visibleViews.removeValue(forKey: id) + } sortViews() action(id, .shown, self) diff --git a/gloss.xcodeproj/project.pbxproj b/gloss.xcodeproj/project.pbxproj index 9bdd79f..a807857 100644 --- a/gloss.xcodeproj/project.pbxproj +++ b/gloss.xcodeproj/project.pbxproj @@ -36,6 +36,8 @@ 85942EF929B1150B00307621 /* SegDenorm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85942EF829B1150B00307621 /* SegDenorm.swift */; }; 85942EFE29B11C0B00307621 /* john_export.json in Resources */ = {isa = PBXBuildFile; fileRef = 85942EFC29B11C0A00307621 /* john_export.json */; }; 85942EFF29B11C0B00307621 /* mark_export.json in Resources */ = {isa = PBXBuildFile; fileRef = 85942EFD29B11C0B00307621 /* mark_export.json */; }; + 85E00E7C29F34D2D00FF9E78 /* ScrollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E00E7B29F34D2D00FF9E78 /* ScrollState.swift */; }; + 85E00E7E29F34D3700FF9E78 /* ScrollStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E00E7D29F34D3700FF9E78 /* ScrollStateRequest.swift */; }; 85F01DF82978787800F317B4 /* AveriaSerifLibre-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 85F01DF72978787800F317B4 /* AveriaSerifLibre-Regular.ttf */; }; 85F01DFB2978790400F317B4 /* xe-Dogma-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 85F01DFA2978790400F317B4 /* xe-Dogma-Bold.ttf */; }; /* End PBXBuildFile section */ @@ -68,6 +70,8 @@ 85942EF829B1150B00307621 /* SegDenorm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegDenorm.swift; sourceTree = ""; }; 85942EFC29B11C0A00307621 /* john_export.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = john_export.json; sourceTree = ""; }; 85942EFD29B11C0B00307621 /* mark_export.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = mark_export.json; sourceTree = ""; }; + 85E00E7B29F34D2D00FF9E78 /* ScrollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollState.swift; sourceTree = ""; }; + 85E00E7D29F34D3700FF9E78 /* ScrollStateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollStateRequest.swift; sourceTree = ""; }; 85F01DF72978787800F317B4 /* AveriaSerifLibre-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "AveriaSerifLibre-Regular.ttf"; sourceTree = ""; }; 85F01DFA2978790400F317B4 /* xe-Dogma-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "xe-Dogma-Bold.ttf"; sourceTree = ""; }; 85F01DFC29787B3500F317B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; @@ -101,6 +105,8 @@ 85431A7C2905F4F500EE0760 = { isa = PBXGroup; children = ( + 85E00E7B29F34D2D00FF9E78 /* ScrollState.swift */, + 85E00E7D29F34D3700FF9E78 /* ScrollStateRequest.swift */, 8528897429B2B86B003F2E16 /* CrownOfThorns.swift */, 85942EEC29AEA04200307621 /* SelectedRibbon.swift */, 85942EEE29AEA18300307621 /* SelectedRibbonRequest.swift */, @@ -274,8 +280,10 @@ 85942EEB29AD55A400307621 /* RibbonRequest.swift in Sources */, 85431A8B2905F4F500EE0760 /* ContentView.swift in Sources */, 85942EF529B108C600307621 /* Seg.swift in Sources */, + 85E00E7C29F34D2D00FF9E78 /* ScrollState.swift in Sources */, 8528897529B2B86B003F2E16 /* CrownOfThorns.swift in Sources */, 85431A952905F4F600EE0760 /* gloss.xcdatamodeld in Sources */, + 85E00E7E29F34D3700FF9E78 /* ScrollStateRequest.swift in Sources */, 85942EE929AD51A100307621 /* Ribbon.swift in Sources */, 8590D96729A183EE001EF84F /* AppDatabase.swift in Sources */, 8590D96929A18A6D001EF84F /* LineRequest.swift in Sources */, diff --git a/gloss.xcodeproj/project.xcworkspace/xcuserdata/saint.xcuserdatad/UserInterfaceState.xcuserstate b/gloss.xcodeproj/project.xcworkspace/xcuserdata/saint.xcuserdatad/UserInterfaceState.xcuserstate index 330c412..933dab1 100644 Binary files a/gloss.xcodeproj/project.xcworkspace/xcuserdata/saint.xcuserdatad/UserInterfaceState.xcuserstate and b/gloss.xcodeproj/project.xcworkspace/xcuserdata/saint.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/gloss/AppDatabase.swift b/gloss/AppDatabase.swift index b4d77e3..f293826 100644 --- a/gloss/AppDatabase.swift +++ b/gloss/AppDatabase.swift @@ -54,6 +54,7 @@ struct AppDatabase { t.autoIncrementedPrimaryKey("id") t.column("book", .text).notNull() t.column("scrollOffset", .integer).notNull() + t.column("scrollId", .text) } try db.create(table: "SelectedRibbon") { t in @@ -61,6 +62,12 @@ struct AppDatabase { t.column("ribbonId", .integer).notNull() } + try db.create(table: "ScrollState") { t in + t.autoIncrementedPrimaryKey("id") + t.column("scrollId", .text).notNull() + t.column("scrollOffset", .integer).notNull() + } + try db.create(table: "foo2") { t in t.autoIncrementedPrimaryKey("id") t.column("ribbonId", .integer).notNull() @@ -123,6 +130,16 @@ extension AppDatabase { } } + func saveScrollState(_ scrollState: inout ScrollState) async throws { + // if ribbon.name.isEmpty { + // throw ValidationError.missingName + // } + try await dbWriter.write { [scrollState] db in + try scrollState.update(db) + } + } + + /// Create random Lines if the database is empty. func createRandomLinesIfEmpty() throws { @@ -156,6 +173,7 @@ extension AppDatabase { _ = try Ribbon(id: 1, book: "bible.john", scrollOffset: 0).inserted(db) _ = try Ribbon(id: 2, book: "bible.john", scrollOffset: 2000).inserted(db) _ = try SelectedRibbon(id: 1, ribbonId: 1).inserted(db) + _ = try ScrollState(id: 1, scrollId: "1", scrollOffset: 1).inserted(db) } } } diff --git a/gloss/ContentView.swift b/gloss/ContentView.swift index 1bef370..4b1cf66 100644 --- a/gloss/ContentView.swift +++ b/gloss/ContentView.swift @@ -50,7 +50,7 @@ struct SwitchButton : View { // var selectedRibbon: SelectedRibbon // @Binding var book : String - @Binding var scrollDelegate : ScrollViewHandler + // @Binding var scrollDelegate : ScrollViewHandler @Binding var scrollView : UIScrollView? @Binding var scrollUpdate : Bool @@ -68,7 +68,7 @@ struct SwitchButton : View { var selectedRibbon = sr[0] Print("SELECTED RIBBON", selectedRibbon) - var saveScrollOffset = scrollDelegate.scrollOffset + // var saveScrollOffset = scrollDelegate.scrollOffset var editedRibbon = selectedRibbon @@ -79,10 +79,10 @@ struct SwitchButton : View { if (selectedRibbon.id != ribbon.id!) { Print("switching") // book = ribbon.book - Print("applying offset", CGFloat(ribbon.scrollOffset)) + // Print("applying offset", CGFloat(ribbon.scrollOffset)) // scrollOffset = CGFloat(ribbon.scrollOffset) // scrollDelegate.scrollOffset = CGFloat(ribbon.scrollOffset) - scrollDelegate.scrollOffset = CGFloat(ribbon.scrollOffset) + // scrollDelegate.scrollOffset = CGFloat(ribbon.scrollOffset) // scrollView!.contentOffset.y = CGFloat(ribbon.scrollOffset) // scrollOffset = CGFloat(1500) var updateSelectRibbon = SelectedRibbon(id: Int64(1), ribbonId: ribbon.id!) @@ -95,7 +95,7 @@ struct SwitchButton : View { } } - editedRibbon.scrollOffset = Int(saveScrollOffset) + // editedRibbon.scrollOffset = Int(saveScrollOffset) _ = try await appDatabase.saveRibbon(&editedRibbon) _ = Print("editedRibbon", editedRibbon) @@ -112,17 +112,17 @@ struct SwitchButton : View { } -class ScrollViewHandler: NSObject { - public var scrollOffset = CGFloat(10) -} +// class ScrollViewHandler: NSObject { +// public var scrollOffset = CGFloat(10) +// } -extension ScrollViewHandler: UIScrollViewDelegate { - func scrollViewDidScroll(_ scrollView: UIScrollView) { - scrollOffset = CGFloat(scrollView.contentOffset.y) - // print("delegate", scrollView.contentOffset.y) - // print("delegate prop", self.scrollOffset) - } -} +// extension ScrollViewHandler: UIScrollViewDelegate { +// func scrollViewDidScroll(_ scrollView: UIScrollView) { +// scrollOffset = CGFloat(scrollView.contentOffset.y) +// // print("delegate", scrollView.contentOffset.y) +// // print("delegate prop", self.scrollOffset) +// } +// } class Verse: NSObject, Codable { @@ -133,49 +133,19 @@ class Verse: NSObject, Codable { func makeVerseView(seg: SegDenorm) -> some View { var retView = Text("") var segSplit = seg.body.components(separatedBy: ";;") - // Text(segSplit[0].body) - // .id(seg.id) - // VStack { - // ForEach(segSplit.indices) { i in let decoder = JSONDecoder() segSplit.enumerated().forEach({ (index, item) in let verse = try! decoder.decode(Verse.self, from: item.data(using: .utf8)!) retView = retView + Text(String(verse.verse)) - // Text(seg.body) - // .contentShape(Rectangle()) .font(Font.custom("AveriaSerifLibre-Regular", size: 20)) .baselineOffset(6.0) .foregroundColor(Color.white) retView = retView + Text(verse.body) - // .frame(maxWidth: .infinity, alignment: .leading) - // .contentShape(Rectangle()) .foregroundColor(Color.white) .font(Font.custom("AveriaSerifLibre-Regular", size: 30)) - // .listRowBackground(Color(red: 0.2, green: 0.8, blue: 0.2)) - // .listRowInsets(EdgeInsets()) - // .padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20)) - // .listRowSeparator(.hidden) - // .id(String(seg.id) + "body" + String(i)) - // .id(seg.id) - //.onTapGesture { - // selectedLine = seg.id - // //Print(selectedLine) - //} - - // .listRowBackground(Color(red: 0.2, green: 0.8, blue: 0.2)) - // .listRowInsets(EdgeInsets()) - // .padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20)) - // .listRowSeparator(.hidden) - // .id(String(seg.id) + "verse" + String(i)) - // .id(seg.id) - //.onTapGesture { - // selectedLine = seg.id - // //Print(selectedLine) - //} }) - // } return retView } @@ -241,22 +211,25 @@ struct ContentView: View { @State var pulledOut = CGSize.zero @State var taskTitle : String = "FIRST DOGGG" @State var curBook : String = "Matthew" - // @State var scrollTo1 : Int64? @State var selectedLine : Int64? - @State var scrollOffset = CGFloat() @State var thisScrollView : UIScrollView? @State var scrollUpdate = false @State var initLoad = false + @State var currentId : String? + @State var currentOffset = CGFloat() + + // set this to scroll to area + @State var scrollId : String? + @State var scrollOffset: CGFloat? + + @Query(SegDenormRequest(book: "bible.mark")) private var segs: [SegDenorm] - @State var scrollDelegate: ScrollViewHandler + // @State var scrollDelegate: ScrollViewHandler // @State var scrollDelegate = ScrollViewHandler() - // @State var selectedRibbon: Ribbon!, - // @State var selectedRibbon: Ribbon! - // @State var selectedRibbonId = Int64(UserDefaults.standard.optionalInt(forKey: "selectedRibbonId") ?? 1) // ribbon @@ -276,8 +249,7 @@ struct ContentView: View { init() { UITableView.appearance().backgroundColor = UIColor(Color(red: 0.2, green: 0.2, blue: 0.2)) // _selectedRibbon = Query(RibbonRequest(id: Int64(UserDefaults.standard.optionalInt(forKey: "lastRibbonId") ?? 1))) - self._scrollDelegate = State(initialValue: ScrollViewHandler()) - Print("initalizing") + // self._scrollDelegate = State(initialValue: ScrollViewHandler()) } var body: some View { @@ -287,24 +259,6 @@ struct ContentView: View { ZStack{ VStack{ - VStack{ - - // // Star(corners: 6, smoothness: 0.85, initAngle:-CGFloat.pi / 4) - // .fill(.blue) - // .frame(width: 200, height: 200) - // .background(Color.gray.opacity(0.0)) - - // Star(corners: 6, smoothness: 0.85, initAngle:-CGFloat.pi / 2) - // .fill(.blue) - // .frame(width: 200, height: 200) - // .background(Color.gray.opacity(0.0)) - - // MyCustomShape().frame(width: 10, height: 10) - - - } - - Text("MRK") .font(Font.custom("AveriaSerifLibre-Regular", size: 20)) .foregroundColor(Color.white) @@ -320,12 +274,10 @@ struct ContentView: View { .frame(width: 120, height: 120) - - // Text("cat" + String(selectedRibbon[0].id!)) ForEach(ribbons) { ribbon in //ForEach(selectedRibbon) { sr in SwitchButton(ribbon: ribbon, - scrollDelegate: $scrollDelegate, + // scrollDelegate: $scrollDelegate, scrollView:$thisScrollView, scrollUpdate:$scrollUpdate ) @@ -337,20 +289,14 @@ struct ContentView: View { .background(Color(red: 0.1, green: 0.1, blue: 0.1)) ScrollViewReader { proxy in - VisibilityTrackingScrollView(action: handleVisibilityChanged) { // ScrollView { LazyVStack { Text(refresh ? "Selected" : "not Selected") Button("Jump to #8") { + scrollId = "20" - proxy.scrollTo("5", anchor: .top) - - DispatchQueue.main.async { - scrollOffset = 340 - refresh.toggle() - } } ForEach(segs) { seg in SegRow(seg: seg) @@ -371,27 +317,37 @@ struct ContentView: View { } } .background(Color(red: 0.2, green: 0.2, blue: 0.2)) + } + .onChange(of: scrollId) { target in + if let target = target { + proxy.scrollTo(scrollId! , anchor: .top) + //scrollId = nil + + DispatchQueue.main.async { + scrollOffset = -299 + refresh.toggle() + } + } + + + } .introspectScrollView { scrollView in - - // Print("introspect") - scrollView.delegate = scrollDelegate - Print("Scroll delegate offset", scrollDelegate.scrollOffset) + // scrollView.delegate = scrollDelegate + // Print("Scroll delegate offset", scrollDelegate.scrollOffset) - if (scrollOffset != 0) { - Print("GOT THEHREHREH") - - scrollView.contentOffset.y = scrollView.contentOffset.y + scrollOffset - - DispatchQueue.main.async { scrollOffset = 0 } + if (scrollOffset != nil) { + Print("Setting scroll offset") + scrollView.contentOffset.y = scrollView.contentOffset.y + scrollOffset! + DispatchQueue.main.async { scrollOffset = nil } } - if (thisScrollView == nil) { - Print("init scroll") - thisScrollView = scrollView - scrollView.contentOffset.y = CGFloat(selectedRibbon[0].scrollOffset) - } + // if (thisScrollView == nil) { + // Print("init scroll") + // thisScrollView = scrollView + // scrollView.contentOffset.y = CGFloat(selectedRibbon[0].scrollOffset) + // } } .listStyle(PlainListStyle()) } @@ -439,11 +395,44 @@ struct ContentView: View { } func handleVisibilityChanged(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { + + // @Environment(\.appDatabase) var appDatabase switch change { case .shown: print("\(id) shown") case .hidden: print("\(id) hidden") } print(tracker.visibleViews) + // if (currentId != nil) { + // currentOffset = tracker.visibleViews[currentId!]! + // } + + let currentId = Array(tracker.visibleViews.keys)[0] + if (currentId != nil) { + + // Access Shared Defaults Object + let userDefaults = UserDefaults.standard + + // Write/Set Value + Print(currentId) + Print(tracker.visibleViews[currentId]!) + userDefaults.set(currentId, forKey: "currentId") + userDefaults.set(tracker.visibleViews[currentId]!, forKey: "currentOffset") + // userDefaults.set(tracker.visibleViews[currentId!]!, forKey: "currentOffset") + // var updateScrollState = ScrollState(id: Int64(1), + // scrollId: currentId!, + // scrollOffset: Int64(tracker.visibleViews[currentId!]!)) + // Print("Savingg") + // Print(updateScrollState) + // Task(priority: .default) { + + // do { + // _ = try await appDatabase.saveScrollState(&updateScrollState) + + // } catch { + // Print("something wrong") + // } + // } + } }