Compare commits

...

37 Commits

Author SHA1 Message Date
saint 09ffa5f8e2 added acts 2024-06-12 13:05:04 -04:00
saint 495b052db0 Show title pane working with showing chapter
switched to observedobj from binding as it works better
2024-06-12 12:27:12 -04:00
saint ac45ae5b20 chap number view prelim 2024-06-12 12:19:30 -04:00
saint c04469ef19 swipe with drag offset works now 2024-06-09 10:32:59 -04:00
saint a8648b8508 swiping seems to work fine, need to fix dragoffset now 2024-06-09 10:25:08 -04:00
saint 56b32c39ab working better still needs tweaks 2024-06-08 19:41:44 -04:00
saint f23cff87ab working better 2024-06-08 18:53:23 -04:00
saint ed1377d9c5 some changes, builds but needs work oh yeah switches ribbons books properly 2024-06-07 18:50:24 -04:00
saint f202589f89 swipe works better but not done 2024-06-07 11:24:33 -04:00
saint 678400c05e small refactor 2024-06-04 07:34:15 -04:00
saint 0ac847c88c pull out offset stuff works properly, builds 2024-06-04 07:26:31 -04:00
saint 6f7eed1e81 builds and offset drag 2024-06-03 23:08:35 -04:00
saint 8c1a284151 it builds 2024-06-03 18:26:56 -04:00
saint 8774723f43 better stats formatting 2024-05-31 14:23:36 -04:00
saint 1aae36dfbc added readme 2024-05-31 13:50:01 -04:00
saint 983bba2849 grid for stats 2024-05-31 13:47:06 -04:00
saint bd1923cfb4 stats sorta working
builds
2024-05-31 13:04:43 -04:00
saint 19ca7be678 builds, still working on undo 2024-05-29 16:02:08 -04:00
saint 168a057a2a okay so working when you manually save ribbon
need to make it save position when you switch ribbon instead of manual save
but good progress
2024-05-28 16:15:07 -04:00
saint 14e1bec9e5 builds and is working, need to fix selected ribbon 2024-05-28 16:04:21 -04:00
saint 08b6116a28 okay this is better
builds and has the foundation for better undo stuff
2024-05-28 00:13:54 -04:00
saint efd05ce33b working on undo - does not build rn 2024-05-27 22:28:13 -04:00
saint 4dfc93ea90 added some back arrows and start of custom picker 2024-05-27 17:24:56 -04:00
saint 32696d7b58 can request separate books 2024-05-26 10:45:30 -04:00
saint 9eb488a87a okay works and the resize works for the overlay too yay 2024-05-24 10:36:58 -04:00
saint c069860c43 builds but idk 2024-05-24 10:19:37 -04:00
saint ae49a5cbfe building and jumps working now
using observed object instead of binding works actually
2024-05-23 17:21:58 -04:00
saint 3a7c83c2d9 builds 2024-05-23 14:41:16 -04:00
saint 5c48d1387a looks better 2024-05-23 12:19:18 -04:00
saint c934dd5c21 builds but not working really
this is with the refactor thing and yeah need to fix more stuff but it builds at least
2024-05-23 10:57:00 -04:00
saint 4615dbd433 buuld 2024-05-23 10:51:50 -04:00
saint 3746682941 builds 2024-05-23 10:46:13 -04:00
saint 1275ad4f96 builds 2024-05-22 14:27:57 -04:00
saint 4154f8f46d builds 2024-05-22 13:25:17 -04:00
saint cce075abaa More refactor, stil builds 2024-05-21 12:20:23 -04:00
saint a9b85c90d1 builds again 2024-05-20 16:40:32 -04:00
saint 82fec8363e builds 2024-05-20 16:24:00 -04:00
27 changed files with 2736 additions and 690 deletions

2
.ignore Normal file
View File

@ -0,0 +1,2 @@
*.json
*.xcodeproj/*

55
BackButton.swift Normal file
View File

@ -0,0 +1,55 @@
//
// BackButton.swift
// gloss
//
// Created by Saint on 5/27/24.
//
import Foundation
import SwiftUI
struct BackArrow: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let width = rect.size.width
let height = rect.size.height
path.move(to: CGPoint(x: 0.83333*width, y: 0.45833*height))
path.addLine(to: CGPoint(x: 0.83333*width, y: 0.54167*height))
path.addLine(to: CGPoint(x: 0.33333*width, y: 0.54167*height))
path.addLine(to: CGPoint(x: 0.33333*width, y: 0.625*height))
path.addLine(to: CGPoint(x: 0.25*width, y: 0.625*height))
path.addLine(to: CGPoint(x: 0.25*width, y: 0.54167*height))
path.addLine(to: CGPoint(x: 0.16667*width, y: 0.54167*height))
path.addLine(to: CGPoint(x: 0.16667*width, y: 0.45833*height))
path.addLine(to: CGPoint(x: 0.25*width, y: 0.45833*height))
path.addLine(to: CGPoint(x: 0.25*width, y: 0.375*height))
path.addLine(to: CGPoint(x: 0.33333*width, y: 0.375*height))
path.addLine(to: CGPoint(x: 0.33333*width, y: 0.45833*height))
path.addLine(to: CGPoint(x: 0.83333*width, y: 0.45833*height))
path.closeSubpath()
path.move(to: CGPoint(x: 0.41667*width, y: 0.29167*height))
path.addLine(to: CGPoint(x: 0.33333*width, y: 0.29167*height))
path.addLine(to: CGPoint(x: 0.33333*width, y: 0.375*height))
path.addLine(to: CGPoint(x: 0.41667*width, y: 0.375*height))
path.addLine(to: CGPoint(x: 0.41667*width, y: 0.29167*height))
path.closeSubpath()
path.move(to: CGPoint(x: 0.41667*width, y: 0.29167*height))
path.addLine(to: CGPoint(x: 0.5*width, y: 0.29167*height))
path.addLine(to: CGPoint(x: 0.5*width, y: 0.20833*height))
path.addLine(to: CGPoint(x: 0.41667*width, y: 0.20833*height))
path.addLine(to: CGPoint(x: 0.41667*width, y: 0.29167*height))
path.closeSubpath()
path.move(to: CGPoint(x: 0.41667*width, y: 0.70833*height))
path.addLine(to: CGPoint(x: 0.33333*width, y: 0.70833*height))
path.addLine(to: CGPoint(x: 0.33333*width, y: 0.625*height))
path.addLine(to: CGPoint(x: 0.41667*width, y: 0.625*height))
path.addLine(to: CGPoint(x: 0.41667*width, y: 0.70833*height))
path.closeSubpath()
path.move(to: CGPoint(x: 0.41667*width, y: 0.70833*height))
path.addLine(to: CGPoint(x: 0.5*width, y: 0.70833*height))
path.addLine(to: CGPoint(x: 0.5*width, y: 0.79167*height))
path.addLine(to: CGPoint(x: 0.41667*width, y: 0.79167*height))
path.addLine(to: CGPoint(x: 0.41667*width, y: 0.70833*height))
path.closeSubpath()
return path
}
}

52
BookmarkIcon.swift Normal file
View File

@ -0,0 +1,52 @@
//
// BookmarkIcon.swift
// gloss
//
// Created by Saint on 6/4/24.
//
import Foundation
import SwiftUI
struct BookmarkIcon: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let width = rect.size.width
let height = rect.size.height
path.move(to: CGPoint(x: 0.75*width, y: 0.08333*height))
path.addLine(to: CGPoint(x: 0.25*width, y: 0.08333*height))
path.addLine(to: CGPoint(x: 0.25*width, y: 0.16667*height))
path.addLine(to: CGPoint(x: 0.74998*width, y: 0.16667*height))
path.addLine(to: CGPoint(x: 0.74998*width, y: 0.83333*height))
path.addLine(to: CGPoint(x: 0.66666*width, y: 0.83333*height))
path.addLine(to: CGPoint(x: 0.66666*width, y: 0.75*height))
path.addLine(to: CGPoint(x: 0.58332*width, y: 0.75*height))
path.addLine(to: CGPoint(x: 0.58332*width, y: 0.66667*height))
path.addLine(to: CGPoint(x: 0.41666*width, y: 0.66667*height))
path.addLine(to: CGPoint(x: 0.41666*width, y: 0.75*height))
path.addLine(to: CGPoint(x: 0.33332*width, y: 0.75*height))
path.addLine(to: CGPoint(x: 0.33332*width, y: 0.83333*height))
path.addLine(to: CGPoint(x: 0.24999*width, y: 0.83333*height))
path.addLine(to: CGPoint(x: 0.24999*width, y: 0.08334*height))
path.addLine(to: CGPoint(x: 0.16666*width, y: 0.08334*height))
path.addLine(to: CGPoint(x: 0.16666*width, y: 0.91667*height))
path.addLine(to: CGPoint(x: 0.24999*width, y: 0.91667*height))
path.addLine(to: CGPoint(x: 0.24999*width, y: 0.91667*height))
path.addLine(to: CGPoint(x: 0.33332*width, y: 0.91667*height))
path.addLine(to: CGPoint(x: 0.33332*width, y: 0.83333*height))
path.addLine(to: CGPoint(x: 0.41666*width, y: 0.83333*height))
path.addLine(to: CGPoint(x: 0.41666*width, y: 0.75*height))
path.addLine(to: CGPoint(x: 0.58332*width, y: 0.75*height))
path.addLine(to: CGPoint(x: 0.58332*width, y: 0.83333*height))
path.addLine(to: CGPoint(x: 0.66666*width, y: 0.83333*height))
path.addLine(to: CGPoint(x: 0.66666*width, y: 0.91667*height))
path.addLine(to: CGPoint(x: 0.74998*width, y: 0.91667*height))
path.addLine(to: CGPoint(x: 0.74998*width, y: 0.91667*height))
path.addLine(to: CGPoint(x: 0.83331*width, y: 0.91667*height))
path.addLine(to: CGPoint(x: 0.83331*width, y: 0.08334*height))
path.addLine(to: CGPoint(x: 0.75*width, y: 0.08334*height))
path.addLine(to: CGPoint(x: 0.75*width, y: 0.08333*height))
path.closeSubpath()
return path
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,216 +0,0 @@
//
// Fenestra.swift
// gloss
//
// Created by Saint on 5/20/24.
//
import Foundation
import SwiftUI
import WrappingHStack
struct SegRow: View {
var seg: SegDenorm
var ribbonId: Int64
@State var highlights = Set<Int>()
var body: some View {
var segSplit = seg.body.components(separatedBy: ";;")
let decoder = JSONDecoder()
var retView = WrappingHStack(alignment: .leading, horizontalSpacing: 0) {
ForEach(0 ..< segSplit.count, id: \.self) { segIndex in
let verse = try! decoder.decode(Verse.self, from: segSplit[segIndex].data(using: .utf8)!)
let arrayOfText = verse.body.components(separatedBy: " ")
let lineHeight = CGFloat(30)
let fontSize = CGFloat(20)
let highlightColor = "470000"
ForEach(0 ..< arrayOfText.count, id: \.self) { index in
HStack(spacing: 0) {
if index == 0 {
Text("")
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
VStack(spacing: 0) {
ZStack {
Text(" ")
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
Text(String(verse.verse))
.font(Font.custom("AveriaSerifLibre-Regular", size: 10))
.padding(.horizontal, 0)
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
}
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
}
}
Text(arrayOfText[index])
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
.padding(.horizontal, 1.5) // intra word spacing
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
.foregroundColor(Color.white)
.onTapGesture {
Print(arrayOfText[index])
Print(verse.verse)
if self.highlights.contains(verse.verse) {
self.highlights.remove(verse.verse)
} else {
self.highlights.insert(verse.verse)
}
}
}
.frame(height: 16) // intra line spacing
.padding(.vertical, 0)
}
}
}
return retView
}
}
// struct Fenestra: View {
// @State var segs: [SegDenorm]
// @State var selectedRibbon: [Ribbon]
// @State var dragOffset = CGFloat()
// @State var refresh: Bool = false
// @State var refresh2: Bool = false
// // var handleVisibilityChanged: (String, VisibilityChange, VisibilityTracker<String>) -> Void
// var body: some View {
// ScrollViewReader { proxy in
// VisibilityTrackingScrollView(action: handleVisibilityChanged) {
// LazyVStack {
// ForEach(segs) { seg in
// SegRow(seg: seg,
// ribbonId: selectedRibbon[0].id!)
// .id("\(seg.id)")
// .offset(x: -dragOffset)
// .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")
// goToRibbon(selectedRibbon: selectedRibbon[0],
// destRibbon: selectedRibbon[0],
// scrollId: $scrollId,
// scrollOffset: $scrollOffset,
// refresh: $refresh,
// showOverlay: $showOverlay,
// appDatabase: appDatabase,
// loading: true)
// }
// .onChange(of: refresh) { _ in
// Task {
// DispatchQueue.main.async {
// Print("scroll Id target: \(scrollId)")
// proxy.scrollTo(scrollId!, anchor: .top)
// 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(" current offset ", gTracker!.visibleViews[scrollId!])
// var curOffset = gTracker!.visibleViews[scrollId!]
// Print(" stats", gTracker!.visibleViews)
// if curOffset != nil {
// setScrollOffset = CGFloat(Int(scrollOffset!) - Int(curOffset!))
// Print("applying scroll offset \(setScrollOffset)")
// refresh2.toggle()
// } else {
// var adjust = (Int(scrollId!)! - Int(currentId!)!) * 200
// Print("adjusting \(adjust)")
// setScrollOffset = CGFloat(adjust)
// refresh.toggle()
// }
// }
// }
// }
// }
// .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
// withAnimation {
// showOverlay = false
// }
// }
// }
// }
// .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)
// .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
// }
// }
// )
// }
// func handleVisibilityChanged(_: String, change _: VisibilityChange, tracker: VisibilityTracker<String>) {
// // 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!]!
// }
// }

56
ForwardArrow.swift Normal file
View File

@ -0,0 +1,56 @@
//
// ForwardArrow.swift
// gloss
//
// Created by Saint on 5/27/24.
//
import Foundation
import SwiftUI
struct ForwardArrow: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let width = rect.size.width
let height = rect.size.height
path.move(to: CGPoint(x: 0.16667*width, y: 0.45833*height))
path.addLine(to: CGPoint(x: 0.16667*width, y: 0.54167*height))
path.addLine(to: CGPoint(x: 0.66667*width, y: 0.54167*height))
path.addLine(to: CGPoint(x: 0.66667*width, y: 0.625*height))
path.addLine(to: CGPoint(x: 0.75*width, y: 0.625*height))
path.addLine(to: CGPoint(x: 0.75*width, y: 0.54167*height))
path.addLine(to: CGPoint(x: 0.83333*width, y: 0.54167*height))
path.addLine(to: CGPoint(x: 0.83333*width, y: 0.45833*height))
path.addLine(to: CGPoint(x: 0.75*width, y: 0.45833*height))
path.addLine(to: CGPoint(x: 0.75*width, y: 0.375*height))
path.addLine(to: CGPoint(x: 0.66667*width, y: 0.375*height))
path.addLine(to: CGPoint(x: 0.66667*width, y: 0.45833*height))
path.addLine(to: CGPoint(x: 0.16667*width, y: 0.45833*height))
path.closeSubpath()
path.move(to: CGPoint(x: 0.58333*width, y: 0.29167*height))
path.addLine(to: CGPoint(x: 0.66667*width, y: 0.29167*height))
path.addLine(to: CGPoint(x: 0.66667*width, y: 0.375*height))
path.addLine(to: CGPoint(x: 0.58333*width, y: 0.375*height))
path.addLine(to: CGPoint(x: 0.58333*width, y: 0.29167*height))
path.closeSubpath()
path.move(to: CGPoint(x: 0.58333*width, y: 0.29167*height))
path.addLine(to: CGPoint(x: 0.5*width, y: 0.29167*height))
path.addLine(to: CGPoint(x: 0.5*width, y: 0.20833*height))
path.addLine(to: CGPoint(x: 0.58333*width, y: 0.20833*height))
path.addLine(to: CGPoint(x: 0.58333*width, y: 0.29167*height))
path.closeSubpath()
path.move(to: CGPoint(x: 0.58333*width, y: 0.70833*height))
path.addLine(to: CGPoint(x: 0.66667*width, y: 0.70833*height))
path.addLine(to: CGPoint(x: 0.66667*width, y: 0.625*height))
path.addLine(to: CGPoint(x: 0.58333*width, y: 0.625*height))
path.addLine(to: CGPoint(x: 0.58333*width, y: 0.70833*height))
path.closeSubpath()
path.move(to: CGPoint(x: 0.58333*width, y: 0.70833*height))
path.addLine(to: CGPoint(x: 0.5*width, y: 0.70833*height))
path.addLine(to: CGPoint(x: 0.5*width, y: 0.79167*height))
path.addLine(to: CGPoint(x: 0.58333*width, y: 0.79167*height))
path.addLine(to: CGPoint(x: 0.58333*width, y: 0.70833*height))
path.closeSubpath()
return path
}
}

116
NaviBar.swift Normal file
View File

@ -0,0 +1,116 @@
import GRDB
import GRDBQuery
import Foundation
import SwiftUI
struct NaviBar: View {
@ObservedObject var paneConnector: PaneConnector
@Query(RibbonRequest(dir: .prev, groupId: 1)) private var backRibbon: [Ribbon]
@Query(RibbonRequest(dir: .next, groupId: 1)) private var nextRibbon: [Ribbon]
@Query(SelectedRibbonRequest()) private var selectedRibbon: [Ribbon]
@Environment(\.appDatabase) private var appDatabase
var body: some View {
var iconSize = CGFloat(25)
VStack {
HStack(spacing: 30) {
BackArrow()
.frame(width: iconSize, height: iconSize)
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
.if(paneConnector.hasMoved) { $0.foregroundColor(Color.black) }
.if(!paneConnector.hasMoved) { $0.foregroundColor(Color(UIColor(red: 0.30, green: 0.30, blue: 0.30, alpha: 0.4))) }
.onTapGesture {
Task {
var br = backRibbon
var sr = selectedRibbon
do {
if paneConnector.hasMoved {
print("has moved")
let updatedRibbon = try await createUndoState(selectedRibbon: sr[0],
appDatabase: appDatabase,
paneConnector: paneConnector)
goToRibbon(selectedRibbon: sr[0],
destRibbon: sr[0],
appDatabase: appDatabase,
paneConnector: paneConnector,
loading: false)
} else {
if backRibbon.count == 0 {
return
}
try await appDatabase.undoRibbon(&sr[0])
goToRibbon(selectedRibbon: sr[0],
destRibbon: br[0],
appDatabase: appDatabase,
paneConnector: paneConnector,
loading: false)
}
} catch {
print("Back Arrow Error info: \(error)")
}
}
}
BookmarkIcon()
.frame(width: iconSize, height: iconSize)
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
.foregroundColor(Color(UIColor(red: 0.30, green: 0.30, blue: 0.30, alpha: 0.4)))
ForwardArrow()
.frame(width: iconSize, height: iconSize)
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
.foregroundColor(Color(UIColor(red: 0.30, green: 0.30, blue: 0.30, alpha: 0.4)))
.onTapGesture {
if nextRibbon.count == 0 {
return
}
Task {
var sr = selectedRibbon[0]
var nr = nextRibbon[0]
try await appDatabase.redoRibbon(&sr)
goToRibbon(selectedRibbon: sr,
destRibbon: nr,
appDatabase: appDatabase,
paneConnector: paneConnector,
loading: false)
}
}
// Rectangle()
// .fill(Color(red: 0.1, green: 0.1, blue: 0.1))
// .frame(width: iconSize, height: iconSize)
// BackArrow()
// .frame(width: iconSize, height: iconSize)
// .background(Color(red: 0.1, green: 0.1, blue: 0.1))
// BookmarkIcon()
// .frame(width: iconSize, height: iconSize)
// .background(Color(red: 0.1, green: 0.1, blue: 0.1))
// .foregroundColor(Color(UIColor(red: 0.30, green: 0.30, blue: 0.30, alpha: 0.4)))
// ForwardArrow()
// .frame(width: iconSize, height: iconSize)
// .background(Color(red: 0.1, green: 0.1, blue: 0.1))
// .foregroundColor(Color(UIColor(red: 0.30, green: 0.30, blue: 0.30, alpha: 0.4)))
}
.cornerRadius(5)
}
}
}

287
Pane.swift Normal file
View File

@ -0,0 +1,287 @@
//
// Fenestra.swift
// gloss
//
// Created by Saint on 5/20/24.
//
import GRDB
import GRDBQuery
import Foundation
import SwiftUI
import WrappingHStack
struct SegRow: View {
var seg: SegDenorm
var ribbonId: Int64
var width: CGFloat
@State var highlights = Set<Int>()
@ObservedObject var showTitle: ShowTitle
let intraWordSpacing = CGFloat(1.6)
var body: some View {
var segSplit = seg.body.components(separatedBy: ";;")
let decoder = JSONDecoder()
var retView = VStack {
Text("\(seg.chap)")
.frame(width: width)
.padding(.vertical, 10)
.if(showTitle.chapVisible) { $0.foregroundColor(mainTextColor) }
.if(!showTitle.chapVisible) { $0.foregroundColor(secondBackgroundColor) }
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
.background(secondBackgroundColor)
// .background(mainBackgroundColor)
.onTapGesture {
showTitle.chapVisible.toggle()
}
WrappingHStack(alignment: .leading, horizontalSpacing: 0) {
ForEach(0 ..< segSplit.count, id: \.self) { segIndex in
let verse = try! decoder.decode(Verse.self, from: segSplit[segIndex].data(using: .utf8)!)
let arrayOfText = verse.body.components(separatedBy: " ")
let lineHeight = CGFloat(30)
let fontSize = CGFloat(18)
let highlightColor = "470000"
ForEach(0 ..< arrayOfText.count, id: \.self) { index in
HStack(spacing: 0) {
if index == 0 {
Text("")
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
VStack(spacing: 0) {
ZStack {
Text(" ")
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
Text(String(verse.verse))
.font(Font.custom("AveriaSerifLibre-Regular", size: 10))
.padding(.horizontal, 0)
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
}
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
}
}
Text(arrayOfText[index])
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
.padding(.horizontal, intraWordSpacing) // intra word spacing
.if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) }
.foregroundColor(Color.white)
.onTapGesture {
Print(arrayOfText[index])
Print(verse.verse)
if self.highlights.contains(verse.verse) {
self.highlights.remove(verse.verse)
} else {
self.highlights.insert(verse.verse)
}
}
}
.frame(height: 16) // intra line spacing
.padding(.vertical, 0)
}
}
}
}
return retView
}
}
class ShowTitle: NSObject, ObservableObject {
@Published var chapVisible = false
}
struct Pane: View {
@ObservedObject var paneConnector: PaneConnector
@StateObject var showTitle = ShowTitle()
// @State var chapVisible = false
@State var selectedRibbon: [Ribbon]
@State var width: CGFloat
@State var height: CGFloat
var dragOffset = CGFloat()
@State var refresh: Bool = false
@Query(SegDenormRequest(book: "bible.mark")) private var segs: [SegDenorm]
@Environment(\.appDatabase) private var appDatabase
var body: some View {
var adjustedHeight = height - paneConnector.vertSep
ZStack {
ScrollViewReader { proxy in
VisibilityTrackingScrollView(action: handleVisibilityChanged) {
LazyVStack {
ForEach(segs) { seg in
SegRow(seg: seg,
ribbonId: selectedRibbon[0].id!,
width: width,
showTitle: showTitle
)
.id("\(seg.id)")
.offset(x: -dragOffset)
.padding(EdgeInsets(top: 10, leading: 20, bottom: 20, trailing: 20))
.trackVisibility(id: "\(seg.id)")
}
}
.background(Color(red: 0.18, green: 0.18, blue: 0.18))
}
.onAppear {
goToRibbon(selectedRibbon: selectedRibbon[0],
destRibbon: selectedRibbon[0],
appDatabase: appDatabase,
paneConnector: paneConnector,
loading: true)
}
.onChange(of: paneConnector.refresh) { _ in
print("inside change")
print("on change")
Task {
DispatchQueue.main.async {
if paneConnector.visibilityTracker == nil {
return
}
let gTracker = paneConnector.visibilityTracker!
Print("scroll Id target: \(paneConnector.scrollId)")
proxy.scrollTo(paneConnector.scrollId, anchor: .top)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
Print(" scroll id target", paneConnector.scrollId)
Print(" current id ", paneConnector.currentId)
Print(gTracker.sortedViewIDs)
if paneConnector.currentId != paneConnector.scrollId {
Print("NO MATCH")
}
Print(" current offset ", gTracker.visibleViews[paneConnector.scrollId])
var curOffset = gTracker.visibleViews[paneConnector.scrollId]
Print(" stats", gTracker.visibleViews)
if curOffset != nil {
paneConnector.setScrollOffset = CGFloat(Int(paneConnector.scrollOffset) - Int(curOffset!))
// setScrollOffset = CGFloat(Int(paneConnector.scrollOffset) - Int(curOffset!))
// setScrollOffset = CGFloat(Int(paneConnector.scrollOffset) - Int(curOffset!))
Print("applying scroll offset \(paneConnector.setScrollOffset)")
// paneConnector = paneConnector.copy() as! PaneConnector
self.refresh.toggle()
} else {
var adjust = (Int(paneConnector.scrollId)! - Int(paneConnector.currentId)!) * 200
Print("adjusting \(adjust)")
paneConnector.setScrollOffset = CGFloat(adjust)
refresh.toggle()
}
}
}
}
}
.introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { scrollView in
Print("introspect")
// Weird hack for reactivity
if self.refresh {
let reactive = self.refresh
}
// if self.paneConnector != nil {
// let reactive = self.paneConnector
// }
DispatchQueue.main.async {
if paneConnector.setScrollOffset != nil {
scrollView.contentOffset.y = scrollView.contentOffset.y + paneConnector.setScrollOffset!
paneConnector.setScrollOffset = nil
withAnimation(.easeIn(duration: 0.2)) {
paneConnector.showOverlay = false
self.refresh.toggle()
}
}
}
Print("end introspect")
}
.listStyle(PlainListStyle())
}
.zIndex(1)
.background(Color(red: 0.2, green: 0.2, blue: 0.2))
.frame(width: width, height: adjustedHeight)
if self.paneConnector.showOverlay {
Rectangle()
// .frame(width: width, height: height + 200)
.background(.ultraThinMaterial)
.opacity(0.98)
.offset(y: -50)
.frame(width: width, height: adjustedHeight + 100)
// .blur(radius: 0.8)
// .opacity(1)
.transition(.opacity)
// .frame(width: geometry.size.width - 50)
// .offset(x: pulledOut.width)
// .offset(x: viewState.width, y: viewState.height)
.zIndex(2)
}
}
.frame(width: width, height: adjustedHeight)
}
func handleVisibilityChanged(_: String, change _: VisibilityChange, tracker: VisibilityTracker<String>) {
// var printRate: Int64 = 10
// gTracker = tracker
self.paneConnector.visibilityTracker = 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!]!
if self.paneConnector.currentId != tracker.sortedViewIDs[0] {
self.paneConnector.currentId = tracker.sortedViewIDs[0]
}
self.paneConnector.currentOffset = tracker.visibleViews[currentId!]!
if self.paneConnector.currentId == self.paneConnector.scrollId
&& abs(self.paneConnector.currentOffset - self.paneConnector.scrollOffset) < 10 {
if self.paneConnector.hasMoved {
self.paneConnector.hasMoved = false
}
} else {
if !self.paneConnector.hasMoved {
self.paneConnector.hasMoved = true
}
}
}
}

0
README.md Normal file
View File

View File

@ -2,30 +2,19 @@ import Combine
import GRDB import GRDB
import GRDBQuery import GRDBQuery
/// A player request can be used with the `@Query` property wrapper in order to
/// feed a view with a list of players.
///
/// For example:
///
/// struct MyView: View {
/// @Query(RibbonRequest(ordering: .byName)) private var players: [Ribbon]
///
/// var body: some View {
/// List(players) { player in ... )
/// }
/// }
var idColumn = Column("id") var idColumn = Column("id")
struct RibbonRequest: Queryable { struct RibbonRequest: Queryable {
// enum Ordering { enum UndoDir {
// case byScore case prev
// case byName case next
// } case current
}
/// The ordering used by the player request. /// The ordering used by the player request.
// var ordering: Ordering // var ordering: Ordering
var id: Int64! var id: Int64!
var dir: UndoDir?
var groupId: Int?
// MARK: - Queryable Implementation // MARK: - Queryable Implementation
@ -49,12 +38,108 @@ struct RibbonRequest: Queryable {
// This method is not required by Queryable, but it makes it easier // This method is not required by Queryable, but it makes it easier
func fetchValue(_ db: Database) throws -> [Ribbon] { func fetchValue(_ db: Database) throws -> [Ribbon] {
if (id == nil) {
return try Ribbon.order(Column("pos")).fetchAll(db) var ret: [Ribbon]
} else { var sql: String
return try Ribbon.filter(idColumn == id).fetchAll(db)
// this has to be a global variable
let totalLevels = 3
do {
if dir != nil && groupId != nil {
sql = """
SELECT * FROM Ribbon \
WHERE groupId = ?
LIMIT 1
"""
ret = try Ribbon.fetchAll(db, sql: sql, arguments: [groupId])
let currentLevel = ret[0].currentLevel
let minLevel = ret[0].minLevel
let maxLevel = ret[0].maxLevel
if dir == .current {
sql = """
SELECT * FROM Ribbon \
WHERE groupId = ?
AND undoLevel = ?
LIMIT 1
"""
ret = try Ribbon.fetchAll(db, sql: sql, arguments: [groupId, currentLevel])
return ret
}
var newCurrentLevel = (currentLevel - 1) %% totalLevels
// probably need more error checking to check
// if current level gets into an error state
// between minLevel and maxLevel somehow but there
// are probably a bunch of edge cases casue of the
// mod stuff
if dir == .prev {
print("calling ribbon request prev")
// no back undo steps left
if currentLevel == minLevel {
return []
}
} else if dir == .next {
// no forward redo steps left
if currentLevel == maxLevel {
return []
} else {
newCurrentLevel = (currentLevel + 1) %% totalLevels
}
}
sql = """
SELECT * FROM Ribbon \
WHERE groupId = ? AND
undoLevel = ?
LIMIT 1
"""
ret = try Ribbon.fetchAll(db, sql: sql, arguments: [groupId, newCurrentLevel])
print("dog returning: \(ret)")
return ret
} else {
if groupId != nil {
let sql = """
SELECT * from Ribbon r1 \
WHERE r1.groupId = ?
ORDER BY undoLevel ASC
"""
var ret = try Ribbon.fetchAll(db, sql: sql, arguments: [groupId])
print("all fetching ribbons: \(ret)")
return ret
} else {
let sql = """
select distinct r1.* from Ribbon r1 join Ribbon r2 ON \
r1.undoLevel = r2.currentLevel AND r1.id = r2.id ORDER BY pos ASC
"""
var ret = try Ribbon.fetchAll(db, sql: sql)
print("xxxxx fetching ribbons")
print(ret)
return ret
}
}
} catch {
print(error.localizedDescription)
print(error)
print("Error")
return []
} }
// if id == nil {
// return try Ribbon.order(Column("pos")).fetchAll(db)
// } else {
// return try Ribbon.filter(idColumn == id).fetchAll(db)
// }
// {
// if book == "" { // if book == "" {
// return try Ribbon.filter(bookColumn == Ribbon.randomBook()).fetchAll(db) // return try Ribbon.filter(bookColumn == Ribbon.randomBook()).fetchAll(db)
// } else { // } else {
@ -69,3 +154,14 @@ struct RibbonRequest: Queryable {
// } // }
} }
} }
infix operator %%
extension Int {
static func %% (_ left: Int, _ right: Int) -> Int {
let mod = left % right
return mod >= 0 ? mod : mod + right
}
}

View File

@ -11,6 +11,7 @@ struct SegDenorm: Identifiable, Equatable {
var body: String var body: String
// var lineIds: [Int64] // var lineIds: [Int64]
var book: String var book: String
var chap: Int64
} }
extension SegDenorm { extension SegDenorm {

View File

@ -24,15 +24,49 @@ struct SegDenormRequest: Queryable {
} }
func fetchValue(_ db: Database) throws -> [SegDenorm] { func fetchValue(_ db: Database) throws -> [SegDenorm] {
print("WOOOOOOF") // print("woof segs denorm fetching for \(book)")
var sql = "select seg_id as id, seg.book as book, group_concat(line.body, ';;') as body from seg join line on seg.line_id = line.rowid WHERE seg.book = 'bible.john' group by seg.seg_id" // print(book)
do {
var ret = try SegDenorm.fetchAll(db, sql: sql) // [Player] do
{
print("fetching segs")
var ret = try Ribbon.fetchAll(db, sql: """
SELECT Ribbon.* FROM SelectedRibbon \
JOIN (select distinct r1.* from Ribbon r1 join Ribbon r2 ON \
r1.undoLevel = r2.currentLevel AND r1.id = r2.id ORDER BY pos ASC) as Ribbon \
ON SelectedRibbon.ribbonGroupId = Ribbon.groupId \
WHERE SelectedRibbon.rowId = 1
""")
// print("Selected Ribbon query result: \(ret)")
var book = ret[0].book
var sql = """
select seg_id as id, line.chap as chap, seg.book as book, group_concat(line.body, ';;') as body from \
(select * from seg where seg.book = '\(book)') as seg \
join (select * from line where line.book = '\(book)') as line \
on seg.line_id = line.line_id group by seg.seg_id
"""
var ret2 = try SegDenorm.fetchAll(db, sql: sql)
// print("SEGS DENORM") // print("SEGS DENORM")
// print(ret) // print(ret[0])
return ret
// var sql2 = """
// select count(1) from seg where seg.book = '\(book)'
// """
// var ret2 = try SegDenorm.fetchAll(db, sql: sql2)
// print("test sql result")
// print(ret2[0])
return ret2
} catch let error { } catch let error {
print(error.localizedDescription) print(error.localizedDescription)
print(error) print(error)

26
SegmentedSelector.swift Normal file
View File

@ -0,0 +1,26 @@
@State var selection = 0
var body: some View {
HStack {
BackArrow()
.frame(width: CGFloat(30), height: CGFloat(30))
.foregroundColor(Color(UIColor(red: 0.30, green: 0.30, blue: 0.30, alpha: 0.4)))
.if(selection == 0) { $0.background(Color.white) }
.if(selection != 0) { $0.background(Color.black) }
.onTapGesture {
withAnimation(.spring(response: 0.5)) {
self.selection = 0
}
}
ForwardArrow()
.frame(width: CGFloat(30), height: CGFloat(30))
.foregroundColor(Color(UIColor(red: 0.30, green: 0.30, blue: 0.30, alpha: 0.4)))
.if(selection == 1) { $0.background(Color.white) }
.if(selection != 1) { $0.background(Color.black) }
.onTapGesture {
withAnimation(.spring(response: 0.5)) {
self.selection = 1
}
}
}
}

View File

@ -17,7 +17,7 @@ struct SelectedRibbon: Identifiable, Equatable {
/// Int64 is the recommended type for auto-incremented database ids. /// Int64 is the recommended type for auto-incremented database ids.
/// Use nil for players that are not inserted yet in the database. /// Use nil for players that are not inserted yet in the database.
var id: Int64? var id: Int64?
var ribbonId: Int64 var ribbonGroupId: Int64
} }
extension SelectedRibbon { extension SelectedRibbon {
@ -32,7 +32,7 @@ extension SelectedRibbon: Codable, FetchableRecord, MutablePersistableRecord {
// Define database columns from CodingKeys // Define database columns from CodingKeys
fileprivate enum Columns { fileprivate enum Columns {
static let id = Column(CodingKeys.id) static let id = Column(CodingKeys.id)
static let ribbonId = Column(CodingKeys.ribbonId) static let ribbonGroupId = Column(CodingKeys.ribbonGroupId)
} }
/// Updates a player id after it has been inserted in the database. /// Updates a player id after it has been inserted in the database.

View File

@ -51,11 +51,15 @@ struct SelectedRibbonRequest: Queryable {
// var ret3 = try Ribbon.fetchAll(db, sql: "SELECT * FROM Ribbon") // [Player] // var ret3 = try Ribbon.fetchAll(db, sql: "SELECT * FROM Ribbon") // [Player]
// print(ret3) // print(ret3)
// print("FETCH JOIN RIBBON") // print("FETCH JOIN RIBBON")
var ret = try Ribbon.fetchAll(db, sql: "SELECT Ribbon.* FROM SelectedRibbon join Ribbon on SelectedRibbon.ribbonId = ribbon.rowId WHERE SelectedRibbon.rowId = 1") // [Player] var ret = try Ribbon.fetchAll(db, sql: """
// print(ret) SELECT Ribbon.* FROM SelectedRibbon \
JOIN (select distinct r1.* from Ribbon r1 join Ribbon r2 ON \
r1.undoLevel = r2.currentLevel AND r1.id = r2.id ORDER BY pos ASC) as Ribbon \
ON SelectedRibbon.ribbonGroupId = Ribbon.groupId \
WHERE SelectedRibbon.rowId = 1
""")
// print("Selected Ribbon query result: \(ret)")
return ret return ret
} }
} }

125
Stats.swift Normal file
View File

@ -0,0 +1,125 @@
import GRDB
import GRDBQuery
import Foundation
import SwiftUI
var fontSize = CGFloat(12)
struct StatsPanel: View {
@ObservedObject var paneConnector: PaneConnector
@Query(RibbonRequest(dir: .prev, groupId: 1)) private var backRibbon: [Ribbon]
@Query(RibbonRequest(dir: .next, groupId: 1)) private var nextRibbon: [Ribbon]
@Query(RibbonRequest(groupId: 1)) private var allRibbons: [Ribbon]
@Query(SelectedRibbonRequest()) private var selectedRibbon: [Ribbon]
@Environment(\.appDatabase) private var appDatabase
let columnCount = 2
var columns: [GridItem] {
Array(repeatElement(GridItem(.flexible()), count: columnCount))
}
var body: some View {
HStack(spacing: 5) {
VStack (spacing: 5) {
if backRibbon.count > 0 {
VStack {
Text("back Ribbon")
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
RibbonDebug(ribbonDebug: RibbonDebugPrint(ribbon: backRibbon[0]))
}
}
VStack {
Text("next Ribbon")
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
if nextRibbon.count > 0 {
RibbonDebug(ribbonDebug: RibbonDebugPrint(ribbon: nextRibbon[0]))
}
}
VStack {
Text("selected Ribbon")
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
RibbonDebug(ribbonDebug: RibbonDebugPrint(ribbon: selectedRibbon[0]))
}
VStack {
Text("pc offset: \(paneConnector.currentOffset)")
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
}
VStack {
Text("pc id: \(paneConnector.currentId)")
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
}
}
VStack(spacing: 5) {
let fr = allRibbons[0]
let currentPos = (fr.currentLevel - fr.minLevel) %% totalLevels
let text = "current pos: \(currentPos)"
Text(text)
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
ForEach(RibbonMap(ribbons: allRibbons), id: \.self) { ribbon in
RibbonDebug(ribbonDebug: ribbon)
}
}
}
}
}
func RibbonMap(ribbons: [Ribbon]) -> [[String]] {
var retStrings = [[String]]()
for r in ribbons {
var debugString = RibbonDebugPrint(ribbon:r)
retStrings.append(debugString)
}
let sortedStats = retStrings.sorted {
Int($0[0])! > Int($1[0])!
}
return sortedStats
}
func RibbonDebugPrint(ribbon: Ribbon) -> [String] {
var ribbonStats = [String]()
let undoPos = (ribbon.undoLevel - ribbon.minLevel) %% totalLevels
ribbonStats.append("\(undoPos)")
ribbonStats.append("id: \(ribbon.id) ")
ribbonStats.append("scrollOffset: \(ribbon.scrollOffset)")
ribbonStats.append("scrollId: \(ribbon.scrollId)")
// ribbonStats.append("undoLevel: \(ribbon.undoLevel)")
// ribbonStats.append("currentLevel: \(ribbon.currentLevel)")
// ribbonStats.append("minLevel: \(ribbon.minLevel)")
// ribbonStats.append("maxLevel: \(ribbon.maxLevel)")
return ribbonStats
}
struct RibbonDebug: View {
var ribbonDebug: [String]
var body: some View {
VStack {
VStack {
// let ribbonStats = RibbonDebugPrint(ribbon: ribbon)
ForEach(ribbonDebug, id: \.self) {
Text($0)
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.font(Font.custom("AveriaSerifLibre-Regular", size: fontSize))
}
}
}
}
}

View File

@ -1 +1,19 @@
{"argv":["/Users/saint/.local/share/xbase/xbase-sourcekit-helper"],"bspVersion":"0.2","languages":["swift","objective-c","objective-cpp","c","cpp"],"name":"XBase","version":"0.3"} {
"argv": [
"/usr/local/bin/xcode-build-server"
],
"bspVersion": "2.0",
"languages": [
"c",
"cpp",
"objective-c",
"objective-cpp",
"swift"
],
"name": "xcode build server",
"version": "0.2",
"workspace": "/Users/saint/code/gloss/gloss.xcodeproj/project.xcworkspace",
"build_root": "/Users/saint/Library/Developer/Xcode/DerivedData/gloss-ajphzxkxxghgqicpumudnmcgeuwg",
"scheme": "gloss",
"kind": "xcode"
}

View File

@ -7,6 +7,11 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
851259B02C05281300BE70F8 /* BackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851259AF2C05281300BE70F8 /* BackButton.swift */; };
851259B22C05299200BE70F8 /* ForwardArrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851259B12C05299200BE70F8 /* ForwardArrow.swift */; };
851259B62C07560800BE70F8 /* NaviBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851259B52C07560800BE70F8 /* NaviBar.swift */; };
851259B82C0A145500BE70F8 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851259B72C0A145500BE70F8 /* Stats.swift */; };
851259BA2C0F355D00BE70F8 /* BookmarkIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851259B92C0F355D00BE70F8 /* BookmarkIcon.swift */; };
8514D5BC299EFB780054F185 /* store.db in Resources */ = {isa = PBXBuildFile; fileRef = 8514D5BB299EFB780054F185 /* store.db */; }; 8514D5BC299EFB780054F185 /* store.db in Resources */ = {isa = PBXBuildFile; fileRef = 8514D5BB299EFB780054F185 /* store.db */; };
8514D5BF299F04710054F185 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 8514D5BE299F04710054F185 /* GRDB */; }; 8514D5BF299F04710054F185 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 8514D5BE299F04710054F185 /* GRDB */; };
852774C129A150B100458CA7 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852774C029A150B100458CA7 /* Line.swift */; }; 852774C129A150B100458CA7 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 852774C029A150B100458CA7 /* Line.swift */; };
@ -22,7 +27,7 @@
85431A902905F4F600EE0760 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85431A8F2905F4F600EE0760 /* Preview Assets.xcassets */; }; 85431A902905F4F600EE0760 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85431A8F2905F4F600EE0760 /* Preview Assets.xcassets */; };
85431A922905F4F600EE0760 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85431A912905F4F600EE0760 /* Persistence.swift */; }; 85431A922905F4F600EE0760 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85431A912905F4F600EE0760 /* Persistence.swift */; };
85431A9C2905F5D800EE0760 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85431A9B2905F5D800EE0760 /* SwiftUIView.swift */; }; 85431A9C2905F5D800EE0760 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85431A9B2905F5D800EE0760 /* SwiftUIView.swift */; };
857C34492BFB7DC800661A63 /* Fenestra.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857C34482BFB7DC800661A63 /* Fenestra.swift */; }; 857C34492BFB7DC800661A63 /* Pane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857C34482BFB7DC800661A63 /* Pane.swift */; };
8590D96729A183EE001EF84F /* AppDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590D96629A183EE001EF84F /* AppDatabase.swift */; }; 8590D96729A183EE001EF84F /* AppDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590D96629A183EE001EF84F /* AppDatabase.swift */; };
8590D96929A18A6D001EF84F /* LineRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590D96829A18A6C001EF84F /* LineRequest.swift */; }; 8590D96929A18A6D001EF84F /* LineRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590D96829A18A6C001EF84F /* LineRequest.swift */; };
8590D96C29A92146001EF84F /* JsonImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590D96B29A92146001EF84F /* JsonImport.swift */; }; 8590D96C29A92146001EF84F /* JsonImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590D96B29A92146001EF84F /* JsonImport.swift */; };
@ -37,6 +42,7 @@
85942EFE29B11C0B00307621 /* john_export.json in Resources */ = {isa = PBXBuildFile; fileRef = 85942EFC29B11C0A00307621 /* john_export.json */; }; 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 */; }; 85942EFF29B11C0B00307621 /* mark_export.json in Resources */ = {isa = PBXBuildFile; fileRef = 85942EFD29B11C0B00307621 /* mark_export.json */; };
8594ED982BF6845F001213F2 /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8594ED972BF6845F001213F2 /* HexColor.swift */; }; 8594ED982BF6845F001213F2 /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8594ED972BF6845F001213F2 /* HexColor.swift */; };
85AAAF572C1A0BC700FCB723 /* acts_export.json in Resources */ = {isa = PBXBuildFile; fileRef = 85AAAF562C1A0BC700FCB723 /* acts_export.json */; };
85E00E7C29F34D2D00FF9E78 /* ScrollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E00E7B29F34D2D00FF9E78 /* ScrollState.swift */; }; 85E00E7C29F34D2D00FF9E78 /* ScrollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E00E7B29F34D2D00FF9E78 /* ScrollState.swift */; };
85E00E7E29F34D3700FF9E78 /* ScrollStateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E00E7D29F34D3700FF9E78 /* ScrollStateRequest.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 */; }; 85F01DF82978787800F317B4 /* AveriaSerifLibre-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 85F01DF72978787800F317B4 /* AveriaSerifLibre-Regular.ttf */; };
@ -45,6 +51,11 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
851259AF2C05281300BE70F8 /* BackButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackButton.swift; sourceTree = "<group>"; };
851259B12C05299200BE70F8 /* ForwardArrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardArrow.swift; sourceTree = "<group>"; };
851259B52C07560800BE70F8 /* NaviBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NaviBar.swift; sourceTree = "<group>"; };
851259B72C0A145500BE70F8 /* Stats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stats.swift; sourceTree = "<group>"; };
851259B92C0F355D00BE70F8 /* BookmarkIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkIcon.swift; sourceTree = "<group>"; };
8514D5BB299EFB780054F185 /* store.db */ = {isa = PBXFileReference; lastKnownFileType = file; path = store.db; sourceTree = "<group>"; }; 8514D5BB299EFB780054F185 /* store.db */ = {isa = PBXFileReference; lastKnownFileType = file; path = store.db; sourceTree = "<group>"; };
852774C029A150B100458CA7 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; }; 852774C029A150B100458CA7 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; };
8528897429B2B86B003F2E16 /* CrownOfThorns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrownOfThorns.swift; sourceTree = "<group>"; }; 8528897429B2B86B003F2E16 /* CrownOfThorns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrownOfThorns.swift; sourceTree = "<group>"; };
@ -58,7 +69,7 @@
85431A8F2905F4F600EE0760 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; }; 85431A8F2905F4F600EE0760 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
85431A912905F4F600EE0760 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; }; 85431A912905F4F600EE0760 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
85431A9B2905F5D800EE0760 /* SwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIView.swift; sourceTree = "<group>"; }; 85431A9B2905F5D800EE0760 /* SwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIView.swift; sourceTree = "<group>"; };
857C34482BFB7DC800661A63 /* Fenestra.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fenestra.swift; sourceTree = "<group>"; }; 857C34482BFB7DC800661A63 /* Pane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pane.swift; sourceTree = "<group>"; };
8590D96629A183EE001EF84F /* AppDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatabase.swift; sourceTree = "<group>"; }; 8590D96629A183EE001EF84F /* AppDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatabase.swift; sourceTree = "<group>"; };
8590D96829A18A6C001EF84F /* LineRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineRequest.swift; sourceTree = "<group>"; }; 8590D96829A18A6C001EF84F /* LineRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineRequest.swift; sourceTree = "<group>"; };
8590D96B29A92146001EF84F /* JsonImport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonImport.swift; sourceTree = "<group>"; }; 8590D96B29A92146001EF84F /* JsonImport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonImport.swift; sourceTree = "<group>"; };
@ -73,6 +84,7 @@
85942EFC29B11C0A00307621 /* john_export.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = john_export.json; sourceTree = "<group>"; }; 85942EFC29B11C0A00307621 /* john_export.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = john_export.json; sourceTree = "<group>"; };
85942EFD29B11C0B00307621 /* mark_export.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = mark_export.json; sourceTree = "<group>"; }; 85942EFD29B11C0B00307621 /* mark_export.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = mark_export.json; sourceTree = "<group>"; };
8594ED972BF6845F001213F2 /* HexColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexColor.swift; sourceTree = "<group>"; }; 8594ED972BF6845F001213F2 /* HexColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexColor.swift; sourceTree = "<group>"; };
85AAAF562C1A0BC700FCB723 /* acts_export.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = acts_export.json; sourceTree = "<group>"; };
85E00E7B29F34D2D00FF9E78 /* ScrollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollState.swift; sourceTree = "<group>"; }; 85E00E7B29F34D2D00FF9E78 /* ScrollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollState.swift; sourceTree = "<group>"; };
85E00E7D29F34D3700FF9E78 /* ScrollStateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollStateRequest.swift; sourceTree = "<group>"; }; 85E00E7D29F34D3700FF9E78 /* ScrollStateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollStateRequest.swift; sourceTree = "<group>"; };
85F01DF72978787800F317B4 /* AveriaSerifLibre-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "AveriaSerifLibre-Regular.ttf"; sourceTree = "<group>"; }; 85F01DF72978787800F317B4 /* AveriaSerifLibre-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "AveriaSerifLibre-Regular.ttf"; sourceTree = "<group>"; };
@ -108,7 +120,10 @@
85431A7C2905F4F500EE0760 = { 85431A7C2905F4F500EE0760 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
857C34482BFB7DC800661A63 /* Fenestra.swift */, 851259B92C0F355D00BE70F8 /* BookmarkIcon.swift */,
851259B12C05299200BE70F8 /* ForwardArrow.swift */,
851259AF2C05281300BE70F8 /* BackButton.swift */,
857C34482BFB7DC800661A63 /* Pane.swift */,
8594ED972BF6845F001213F2 /* HexColor.swift */, 8594ED972BF6845F001213F2 /* HexColor.swift */,
85E00E7B29F34D2D00FF9E78 /* ScrollState.swift */, 85E00E7B29F34D2D00FF9E78 /* ScrollState.swift */,
85E00E7D29F34D3700FF9E78 /* ScrollStateRequest.swift */, 85E00E7D29F34D3700FF9E78 /* ScrollStateRequest.swift */,
@ -117,6 +132,8 @@
85942EEE29AEA18300307621 /* SelectedRibbonRequest.swift */, 85942EEE29AEA18300307621 /* SelectedRibbonRequest.swift */,
85942EF429B108C600307621 /* Seg.swift */, 85942EF429B108C600307621 /* Seg.swift */,
85942EF629B108EA00307621 /* SegDenormRequest.swift */, 85942EF629B108EA00307621 /* SegDenormRequest.swift */,
851259B52C07560800BE70F8 /* NaviBar.swift */,
851259B72C0A145500BE70F8 /* Stats.swift */,
85942EF829B1150B00307621 /* SegDenorm.swift */, 85942EF829B1150B00307621 /* SegDenorm.swift */,
85942EEA29AD55A400307621 /* RibbonRequest.swift */, 85942EEA29AD55A400307621 /* RibbonRequest.swift */,
85942EE329ACF54A00307621 /* ScrollableView.swift */, 85942EE329ACF54A00307621 /* ScrollableView.swift */,
@ -172,6 +189,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
85942EFC29B11C0A00307621 /* john_export.json */, 85942EFC29B11C0A00307621 /* john_export.json */,
85AAAF562C1A0BC700FCB723 /* acts_export.json */,
85942EFD29B11C0B00307621 /* mark_export.json */, 85942EFD29B11C0B00307621 /* mark_export.json */,
); );
path = json; path = json;
@ -268,6 +286,7 @@
85F01DFB2978790400F317B4 /* xe-Dogma-Bold.ttf in Resources */, 85F01DFB2978790400F317B4 /* xe-Dogma-Bold.ttf in Resources */,
85F01DF82978787800F317B4 /* AveriaSerifLibre-Regular.ttf in Resources */, 85F01DF82978787800F317B4 /* AveriaSerifLibre-Regular.ttf in Resources */,
8514D5BC299EFB780054F185 /* store.db in Resources */, 8514D5BC299EFB780054F185 /* store.db in Resources */,
85AAAF572C1A0BC700FCB723 /* acts_export.json in Resources */,
85942EFF29B11C0B00307621 /* mark_export.json in Resources */, 85942EFF29B11C0B00307621 /* mark_export.json in Resources */,
85942EFE29B11C0B00307621 /* john_export.json in Resources */, 85942EFE29B11C0B00307621 /* john_export.json in Resources */,
85431A8D2905F4F600EE0760 /* Assets.xcassets in Resources */, 85431A8D2905F4F600EE0760 /* Assets.xcassets in Resources */,
@ -282,9 +301,11 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
85431A922905F4F600EE0760 /* Persistence.swift in Sources */, 85431A922905F4F600EE0760 /* Persistence.swift in Sources */,
857C34492BFB7DC800661A63 /* Fenestra.swift in Sources */, 857C34492BFB7DC800661A63 /* Pane.swift in Sources */,
85942EEB29AD55A400307621 /* RibbonRequest.swift in Sources */, 85942EEB29AD55A400307621 /* RibbonRequest.swift in Sources */,
851259BA2C0F355D00BE70F8 /* BookmarkIcon.swift in Sources */,
85431A8B2905F4F500EE0760 /* ContentView.swift in Sources */, 85431A8B2905F4F500EE0760 /* ContentView.swift in Sources */,
851259B02C05281300BE70F8 /* BackButton.swift in Sources */,
85942EF529B108C600307621 /* Seg.swift in Sources */, 85942EF529B108C600307621 /* Seg.swift in Sources */,
85E00E7C29F34D2D00FF9E78 /* ScrollState.swift in Sources */, 85E00E7C29F34D2D00FF9E78 /* ScrollState.swift in Sources */,
8594ED982BF6845F001213F2 /* HexColor.swift in Sources */, 8594ED982BF6845F001213F2 /* HexColor.swift in Sources */,
@ -297,12 +318,15 @@
85942EEF29AEA18300307621 /* SelectedRibbonRequest.swift in Sources */, 85942EEF29AEA18300307621 /* SelectedRibbonRequest.swift in Sources */,
8590D96C29A92146001EF84F /* JsonImport.swift in Sources */, 8590D96C29A92146001EF84F /* JsonImport.swift in Sources */,
85942EED29AEA04200307621 /* SelectedRibbon.swift in Sources */, 85942EED29AEA04200307621 /* SelectedRibbon.swift in Sources */,
851259B82C0A145500BE70F8 /* Stats.swift in Sources */,
85431A892905F4F500EE0760 /* glossApp.swift in Sources */, 85431A892905F4F500EE0760 /* glossApp.swift in Sources */,
8528897C29BD69B2003F2E16 /* VisibilityTrackingScrollView.swift in Sources */, 8528897C29BD69B2003F2E16 /* VisibilityTrackingScrollView.swift in Sources */,
85942EF929B1150B00307621 /* SegDenorm.swift in Sources */, 85942EF929B1150B00307621 /* SegDenorm.swift in Sources */,
85431A9C2905F5D800EE0760 /* SwiftUIView.swift in Sources */, 85431A9C2905F5D800EE0760 /* SwiftUIView.swift in Sources */,
851259B22C05299200BE70F8 /* ForwardArrow.swift in Sources */,
8528897E29BD69B2003F2E16 /* VisibilityTracker.swift in Sources */, 8528897E29BD69B2003F2E16 /* VisibilityTracker.swift in Sources */,
85942EF729B108EA00307621 /* SegDenormRequest.swift in Sources */, 85942EF729B108EA00307621 /* SegDenormRequest.swift in Sources */,
851259B62C07560800BE70F8 /* NaviBar.swift in Sources */,
8528897D29BD69B2003F2E16 /* VisibilityTrackingModifier.swift in Sources */, 8528897D29BD69B2003F2E16 /* VisibilityTrackingModifier.swift in Sources */,
85942EE429ACF54A00307621 /* ScrollableView.swift in Sources */, 85942EE429ACF54A00307621 /* ScrollableView.swift in Sources */,
); );
@ -440,7 +464,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"gloss/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"gloss/Preview Content\"";
DEVELOPMENT_TEAM = V8B2B34W7R; DEVELOPMENT_TEAM = C8XWX9329P;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = gloss/Info.plist; INFOPLIST_FILE = gloss/Info.plist;
@ -478,7 +502,7 @@
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"gloss/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"gloss/Preview Content\"";
DEVELOPMENT_TEAM = V8B2B34W7R; DEVELOPMENT_TEAM = C8XWX9329P;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = gloss/Info.plist; INFOPLIST_FILE = gloss/Info.plist;

View File

@ -1,10 +1,10 @@
import Foundation import Foundation
import GRDB import GRDB
/// AppDatabase lets the application access the database.
///
/// It applies the pratices recommended at let totalLevels = 3
/// <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
struct AppDatabase { struct AppDatabase {
/// Creates an `AppDatabase`, and make sure the database schema is ready. /// Creates an `AppDatabase`, and make sure the database schema is ready.
init(_ dbWriter: any DatabaseWriter) throws { init(_ dbWriter: any DatabaseWriter) throws {
@ -39,6 +39,7 @@ struct AppDatabase {
t.autoIncrementedPrimaryKey("id") t.autoIncrementedPrimaryKey("id")
t.column("body", .text).notNull() t.column("body", .text).notNull()
t.column("chap", .integer).notNull() t.column("chap", .integer).notNull()
t.column("line_id", .integer).notNull()
t.column("book", .text).notNull() t.column("book", .text).notNull()
t.column("verse", .integer) t.column("verse", .integer)
} }
@ -53,6 +54,13 @@ struct AppDatabase {
try db.create(table: "Ribbon") { t in try db.create(table: "Ribbon") { t in
t.autoIncrementedPrimaryKey("id") t.autoIncrementedPrimaryKey("id")
t.column("pos", .integer).notNull() t.column("pos", .integer).notNull()
t.column("groupId", .integer).notNull()
t.column("undoLevel", .integer).notNull()
t.column("currentLevel", .integer).notNull()
t.column("minLevel", .integer).notNull()
t.column("maxLevel", .integer).notNull()
.defaults(to: 1)
t.column("title", .text).notNull() t.column("title", .text).notNull()
t.column("book", .text).notNull() t.column("book", .text).notNull()
t.column("scrollOffset", .integer).notNull() t.column("scrollOffset", .integer).notNull()
@ -61,7 +69,7 @@ struct AppDatabase {
try db.create(table: "SelectedRibbon") { t in try db.create(table: "SelectedRibbon") { t in
t.autoIncrementedPrimaryKey("id") t.autoIncrementedPrimaryKey("id")
t.column("ribbonId", .integer).notNull() t.column("ribbonGroupId", .integer).notNull()
} }
try db.create(table: "ScrollState") { t in try db.create(table: "ScrollState") { t in
@ -70,7 +78,8 @@ struct AppDatabase {
t.column("scrollOffset", .integer).notNull() t.column("scrollOffset", .integer).notNull()
} }
try db.create(table: "foo2") { t in // change this to nuke/remake the database
try db.create(table: "foo4") { t in
t.autoIncrementedPrimaryKey("id") t.autoIncrementedPrimaryKey("id")
t.column("ribbonId", .integer).notNull() t.column("ribbonId", .integer).notNull()
} }
@ -113,6 +122,22 @@ func load<T: Decodable>(_ filename: String) -> T {
extension AppDatabase { extension AppDatabase {
func getSelectedRibbon() async throws -> [Ribbon] {
try await dbWriter.write { db in
var sr = try Ribbon.fetchAll(db, sql: """
SELECT Ribbon.* FROM SelectedRibbon \
JOIN (select distinct r1.* from Ribbon r1 join Ribbon r2 ON \
r1.undoLevel = r2.currentLevel AND r1.id = r2.id ORDER BY pos ASC) as Ribbon \
ON SelectedRibbon.ribbonGroupId = Ribbon.groupId \
WHERE SelectedRibbon.rowId = 1
""")
print("meow get selected ribbon \(sr)")
return sr
}
}
func updateRibbonPosition(_ ribbon: inout Ribbon, _ oldPos: Int, _ newPos: Int) async throws { func updateRibbonPosition(_ ribbon: inout Ribbon, _ oldPos: Int, _ newPos: Int) async throws {
try await dbWriter.write { [ribbon] db in try await dbWriter.write { [ribbon] db in
@ -193,6 +218,135 @@ extension AppDatabase {
} }
} }
func redoRibbon(_ ribbon: inout Ribbon) async throws {
let currentLevel = ribbon.currentLevel
let minLevel = ribbon.maxLevel
if currentLevel == minLevel {
print("no where to redo")
return
}
let newCurrent = (ribbon.currentLevel + 1) %% totalLevels
do {
try await dbWriter.write { [ribbon] db in
try db.execute(sql: """
UPDATE Ribbon \
SET currentLevel = ? WHERE groupId = ?
""", arguments: [newCurrent, ribbon.groupId])
}
} catch {
print("Redo Ribbon Error info: \(error)")
}
}
// this sets the current undoLevel to the previous value
// if you can go back
func undoRibbon(_ ribbon: inout Ribbon) async throws {
let currentLevel = ribbon.currentLevel
let minLevel = ribbon.minLevel
if currentLevel == minLevel {
print("no where to undo")
return
}
let newCurrent = (ribbon.currentLevel - 1) %% totalLevels
do {
try await dbWriter.write { [ribbon] db in
try db.execute(sql: """
UPDATE Ribbon \
SET currentLevel = ? WHERE groupId = ?
""", arguments: [newCurrent, ribbon.groupId])
}
} catch {
print("Undo Ribbon Error info: \(error)")
}
}
// deletes all undo steps above the current undo level
// and adds new undo level at the new current level,
// adjusts the minLevel and maxLevel
func bumpRibbon(_ ribbon: inout Ribbon) async throws -> [Ribbon] {
var level = ribbon.currentLevel
let maxLevel = ribbon.maxLevel
// gets all the levels from the current to the max
// so they can be deleted
var delLevels2 = [Int]()
if level != maxLevel {
repeat {
level = (level + 1) %% totalLevels
delLevels2.append(level)
} while level != maxLevel
}
let delLevels = delLevels2
let newMax = (ribbon.currentLevel + 1) %% totalLevels
let newCurrent = newMax
let newMin = newMax == ribbon.minLevel ? (ribbon.minLevel + 1) %% totalLevels
: ribbon.minLevel
ribbon.minLevel = newMin
ribbon.maxLevel = newMax
ribbon.undoLevel = newCurrent
ribbon.currentLevel = newCurrent
ribbon.id = nil
do {
try await dbWriter.write { [ribbon] db in
for l in delLevels {
try db.execute(sql: """
DELETE FROM Ribbon \
WHERE groupId = ? \
AND undoLevel = ?
""", arguments: [ribbon.groupId, l])
}
try db.execute(sql: """
UPDATE Ribbon \
SET minLevel = ?, maxLevel = ?, currentLevel = ? WHERE groupId = ?
""", arguments: [newMin, newMax, newCurrent, ribbon.groupId])
// upsert
var ret = try Ribbon.fetchAll(db, sql: """
SELECT * from Ribbon WHERE groupId = ? AND undoLevel = ?
""", arguments: [ribbon.groupId, ribbon.undoLevel])
if ret.count == 0 {
// insert
_ = try ribbon.inserted(db)
} else {
var updatedRibbon = ret[0]
updatedRibbon.minLevel = newMin
updatedRibbon.maxLevel = newMax
updatedRibbon.undoLevel = newCurrent
updatedRibbon.currentLevel = newCurrent
updatedRibbon.scrollId = ribbon.scrollId
updatedRibbon.scrollOffset = ribbon.scrollOffset
try updatedRibbon.update(db)
}
ret = try Ribbon.fetchAll(db, sql: """
SELECT * from Ribbon WHERE groupId = ? AND undoLevel = ?
""", arguments: [ribbon.groupId, newCurrent])
return ret
}
} catch {
print("Error info: \(error)")
}
return []
}
func saveSelectedRibbon(_ selectedRibbon: inout SelectedRibbon) async throws { func saveSelectedRibbon(_ selectedRibbon: inout SelectedRibbon) async throws {
// if ribbon.name.isEmpty { // if ribbon.name.isEmpty {
// throw ValidationError.missingName // throw ValidationError.missingName
@ -212,35 +366,108 @@ extension AppDatabase {
} }
func importJson(_ filename: String, _ db: Database) throws { func importJson(_ filename: String, _ db: Database) throws {
let importJson : JsonImport = load(filename) let importJson: JsonImport = load(filename)
if try Line.all().isEmpty(db) { var x = 0
for l in importJson.lines { // if try Line.all().isEmpty(db) {
print("importing Lines") for l in importJson.lines {
_ = try l.inserted(db) // print("importing Lines")
if x < 5 {
print(l)
x += 1
} }
_ = try l.inserted(db)
}
for l in importJson.segs { x = 0
print("importing SEGS") for l in importJson.segs {
_ = try l.inserted(db) // print("importing SEGS")
if x < 5 {
print(l)
x += 1
} }
_ = try l.inserted(db)
} }
} }
/// Create random Lines if the database is empty. /// Create random Lines if the database is empty.
func initDatabase() throws { func initDatabase() throws {
try dbWriter.write { db in do {
try dbWriter.write { db in
if try Line.all().isEmpty(db)
{
try importJson("john_export.json", db)
try importJson("mark_export.json", db)
try importJson("acts_export.json", db)
_ = try Ribbon(id: 1,
groupId: 1,
pos: 1,
undoLevel: 0,
currentLevel: 0,
minLevel: 0,
maxLevel: 0,
title: "Gospel of John",
book: "bible.john",
scrollId: "1",
scrollOffset: 0).inserted(db)
if try Line.all().isEmpty(db) { _ = try Ribbon(id: 2,
groupId: 2,
pos: 2,
undoLevel: 0,
currentLevel: 0,
minLevel: 0,
maxLevel: 0,
title: "Gospel according to Mark",
book: "bible.mark",
scrollId: "1",
scrollOffset: 300).inserted(db)
try importJson("john_export.json", db) /////
try importJson("mark_export.json", db)
_ = try Ribbon(id: 1, pos: 1, title: "John", book: "bible.john", scrollId: "1", scrollOffset: 0).inserted(db) _ = try Ribbon(id: 3,
_ = try Ribbon(id: 2, pos: 2, title: "Gospel of Mark", book: "bible.mark", scrollId: "1", scrollOffset: 300).inserted(db) groupId: 3,
_ = try Ribbon(id: 3, pos: 3, title: "John 2", book: "bible.john", scrollId: "1", scrollOffset: 0).inserted(db) pos: 3,
_ = try SelectedRibbon(id: 1, ribbonId: 1).inserted(db) undoLevel: 0,
currentLevel: 2,
minLevel: 0,
maxLevel: 2,
title: "Acts",
book: "bible.acts",
scrollId: "1",
scrollOffset: 0).inserted(db)
_ = try Ribbon(id: 4,
groupId: 3,
pos: 3,
undoLevel: 1,
currentLevel: 2,
minLevel: 0,
maxLevel: 2,
title: "Acts",
book: "bible.acts",
scrollId: "1",
scrollOffset: 0).inserted(db)
_ = try Ribbon(id: 5,
groupId: 3,
pos: 3,
undoLevel: 2,
currentLevel: 2,
minLevel: 0,
maxLevel: 2,
title: "Acts",
book: "bible.acts",
scrollId: "1",
scrollOffset: 0).inserted(db)
_ = try SelectedRibbon(id: 1, ribbonGroupId: 1).inserted(db)
} }
} }
} catch {
print("Error info: \(error)")
}
} }
} }

View File

@ -16,11 +16,13 @@ let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network
var currentId: String? var currentId: String?
var currentOffset: CGFloat? var currentOffset: CGFloat?
var gTracker: VisibilityTracker<String>?
var printCount: Int64 = 0
var disableDrop = false var disableDrop = false
//TODO: move to globals file
let mainBackgroundColor = Color(red: 0.1, green: 0.1, blue: 0.1)
let mainTextColor = Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00))
let secondBackgroundColor = Color(red: 0.18, green: 0.18, blue: 0.18)
public extension UserDefaults { public extension UserDefaults {
func optionalInt(forKey defaultName: String) -> Int? { func optionalInt(forKey defaultName: String) -> Int? {
let defaults = self let defaults = self
@ -39,70 +41,78 @@ public extension UserDefaults {
} }
} }
func createUndoState(selectedRibbon: Ribbon,
appDatabase : AppDatabase,
paneConnector : PaneConnector) async throws -> [Ribbon]
{
let updateThreshold = 30
var updatedRibbon = selectedRibbon
var scrollOffsetToSave = Int(floor(paneConnector.currentOffset))
var scrollIdToSave = paneConnector.currentId
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)
return ret
}
print("meow no bump")
return []
}
func goToRibbon(selectedRibbon: Ribbon, func goToRibbon(selectedRibbon: Ribbon,
destRibbon: Ribbon, destRibbon: Ribbon,
scrollId: Binding<String?>,
scrollOffset: Binding<CGFloat?>,
refresh: Binding<Bool>,
showOverlay: Binding<Bool>,
appDatabase: AppDatabase, appDatabase: AppDatabase,
paneConnector: PaneConnector,
loading: Bool) loading: Bool)
{ {
Task { print("meow goto ribbon - selected ribbon: \(selectedRibbon), dest ribbon: \(destRibbon) ")
// print("SELECTED RIBBON", selectedRibbon)
var scrollOffsetToSave = currentOffset
var scrollIdToSave = currentId
var updatedRibbon = selectedRibbon DispatchQueue.main.asyncAfter(deadline: .now()) {
Task {
var scrollOffsetToSave = paneConnector.currentOffset
var scrollIdToSave = paneConnector.currentId
if selectedRibbon.id != destRibbon.id! || loading { print("go to ribbon")
print("switching ribbons") print("\(selectedRibbon.id) \(destRibbon.id!)")
// withAnimation(.spring(response: 0.05)) { // if selectedRibbon.id != destRibbon.id! || loading {
// showOverlay.wrappedValue = true if true {
showOverlay.wrappedValue = true print("switching ribbons")
// }
if loading { paneConnector.showOverlay = true
currentId = destRibbon.scrollId
// currentOffset = CGFloat(destRibbon.scrollOffset) 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")
}
} }
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 { extension View {
@ -115,21 +125,21 @@ extension View {
struct RibbonCrown: View { struct RibbonCrown: View {
var ribbon: Ribbon var ribbon: Ribbon
@Binding var scrollId: String? @ObservedObject var paneConnector: PaneConnector
@Binding var scrollOffset: CGFloat?
@Binding var showOverlay: Bool
@Binding var refresh: Bool
var draggedRibbon: Ribbon? var draggedRibbon: Ribbon?
var isDragging: Bool var isDragging: Bool
var height = CGFloat(45) var height = CGFloat(41)
var scale = 0.65 var width = CGFloat(70)
var scale = 0.70
@Environment(\.appDatabase) private var appDatabase @Environment(\.appDatabase) private var appDatabase
@Query(SelectedRibbonRequest()) private var sr: [Ribbon] @Query(SelectedRibbonRequest()) private var selectedRibbon: [Ribbon]
@State var saveOffset = CGFloat() @State var saveOffset = CGFloat()
@Binding var refresh: Bool
var body: some View { var body: some View {
ZStack { ZStack {
MyIcon().frame( MyIcon().frame(
@ -142,8 +152,8 @@ struct RibbonCrown: View {
Text(ribbon.title) Text(ribbon.title)
.foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00))) .foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00)))
.frame(minWidth: CGFloat(70), .frame(minWidth: width,
maxWidth: CGFloat(70), maxWidth: width,
minHeight: height, minHeight: height,
maxHeight: height, maxHeight: height,
alignment: .center) alignment: .center)
@ -157,20 +167,29 @@ struct RibbonCrown: View {
} }
.onTapGesture { .onTapGesture {
Task { Task {
goToRibbon(selectedRibbon: sr[0], let sr = selectedRibbon[0]
destRibbon: ribbon, let updatedRibbon = try await createUndoState(selectedRibbon: sr,
scrollId: $scrollId, appDatabase: appDatabase,
scrollOffset: $scrollOffset, paneConnector: paneConnector)
refresh: $refresh,
showOverlay: $showOverlay, if sr.id == ribbon.id {
appDatabase: appDatabase, paneConnector.scrollId = paneConnector.currentId
loading: false) paneConnector.scrollOffset = paneConnector.currentOffset
} paneConnector.hasMoved = false
} else {
goToRibbon(selectedRibbon: sr,
destRibbon: ribbon,
appDatabase: appDatabase,
paneConnector: paneConnector,
loading: false)
}
}
} }
.frame(width: CGFloat(100 * 1.66 * scale + 10), height: CGFloat(100 * scale + 5)) .frame(width: CGFloat(100 * 1.66 * scale + 10), height: CGFloat(100 * scale + 5))
} }
} }
// object used for JSON decoding of verses
class Verse: NSObject, Codable { class Verse: NSObject, Codable {
var body: String var body: String
var verse: Int var verse: Int
@ -195,38 +214,56 @@ func makeVerseView(seg: SegDenorm) -> some View {
return retView return retView
} }
class PaneConnector: NSObject, ObservableObject {
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
var setScrollOffset: CGFloat?
}
struct ContentView: View { struct ContentView: View {
@State var viewState = CGSize.zero // this is for the whole view swiping
@State var mainSwipe = CGSize.zero
@State var pulledOut = CGSize.zero @State var pulledOut = CGSize.zero
@State var thisScrollView: UIScrollView? @State var selection = 0
@State var scrollUpdate = false
@State var initLoad = false
// set this to scroll to area @StateObject var paneConnector = PaneConnector()
@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 refresh: Bool = false
@State var refresh2: Bool = false
@State var readOffset = CGPoint()
@Query(SegDenormRequest(book: "bible.john")) private var segs: [SegDenorm]
@State var draggedRibbon: Ribbon? @State var draggedRibbon: Ribbon?
@State var isDragging = false @State var isDragging = false
@State var reorder = true @State var dragOffset = CGFloat(0)
enum SwipeStartState {
case center
case right
case left
}
enum SwipeStartDir {
case left
case right
}
// @State var curSwipeState: SwipeState = .start
@State var startSwipeState: SwipeStartState?
@State var startSwipeDir: SwipeStartDir?
@Environment(\.appDatabase) private var appDatabase
@Query(RibbonRequest()) private var ribbons: [Ribbon] @Query(RibbonRequest()) private var ribbons: [Ribbon]
@Query<SelectedRibbonRequest> var selectedRibbon: [Ribbon] @Query<SelectedRibbonRequest> var selectedRibbon: [Ribbon]
@ -248,14 +285,13 @@ struct ContentView: View {
ZStack(alignment: .top) { ZStack(alignment: .top) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
VStack { VStack {
ForEach(ribbons) { ribbon in ForEach(ribbons) { ribbon in
RibbonCrown(ribbon: ribbon, RibbonCrown(ribbon: ribbon,
scrollId: $scrollId, paneConnector: paneConnector,
scrollOffset: $scrollOffset,
showOverlay: $showOverlay,
refresh: $refresh,
draggedRibbon: draggedRibbon, draggedRibbon: draggedRibbon,
isDragging: isDragging) isDragging: isDragging,
refresh: $refresh)
.onDrag { .onDrag {
self.draggedRibbon = ribbon self.draggedRibbon = ribbon
return NSItemProvider() return NSItemProvider()
@ -278,266 +314,212 @@ struct ContentView: View {
.background(Color(red: 0.1, green: 0.1, blue: 0.1)) .background(Color(red: 0.1, green: 0.1, blue: 0.1))
.frame(alignment: .topLeading) .frame(alignment: .topLeading)
VStack { VStack {
ScrollViewReader { proxy in NaviBar(paneConnector: paneConnector)
VisibilityTrackingScrollView(action: handleVisibilityChanged) { StatsPanel(paneConnector: paneConnector)
// ScrollView { .offset(y:20)
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")
// if (endedDrag) {
// endedDrag = false
// scrollOffset = readOffset.y - 20
// // _ = Print("meow")
// }
//// logger.error("hello222")
//// NSLog("hellooo")
// Print(viewState.width)
// if (abs(gesture.translation.width) > 20) {
// viewState.width = gesture.translation.width
// }
////offset.y = gesture.translation.width
//// logger.log("hello")
}
// .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
// }
// }
)
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) // .frame(maxWidth: 300)
.offset(x: geometry.size.width - 300)
VStack {
// Top pane
Pane(paneConnector: paneConnector,
selectedRibbon: selectedRibbon,
width: geometry.size.width - 15,
height: geometry.size.height + 20,
dragOffset: dragOffset)
///////////////////////////////////
// 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
// }
// )
// // Bottom pane
// 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)")
// }
// }
// .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))
///////////////////////////////////
}
.offset(x: 20, y: 0)
.offset(x: pulledOut.width) .offset(x: pulledOut.width)
.offset(x: viewState.width, y: viewState.height) .offset(x: mainSwipe.width)
.gesture( .gesture(
DragGesture() DragGesture()
.onChanged { gesture in .onChanged { value in
// Calculate the offset
if endedDrag { let margin = CGFloat(30)
endedDrag = false
scrollOffset = readOffset.y - 20 var newOffset = value.translation.width
// _ = Print("meow") if startSwipeState == nil {
if pulledOut.width == 0 {
startSwipeState = .center
} else if pulledOut.width < 0 {
startSwipeState = .left
} else {
startSwipeState = .right
}
print("start swipe meow: \(startSwipeState)")
} }
// logger.error("hello222")
// NSLog("hellooo") if newOffset > 0 {
Print(viewState.width) startSwipeDir = .right
if abs(gesture.translation.width) > 20 { } else {
viewState.width = gesture.translation.width startSwipeDir = .left
if gesture.translation.width < -50, pulledOut.width == CGFloat(0) { }
dragOffset = gesture.translation.width + 50
// Apply resistance if out of bounds
var maxOffsetRight: CGFloat = 110
var maxOffsetLeft: CGFloat = 200
if startSwipeState == .right {
if startSwipeDir == .right {
maxOffsetRight = .zero
maxOffsetLeft = .zero
} else if startSwipeDir == .left {
maxOffsetRight = CGFloat(110)
maxOffsetLeft = CGFloat(-30)
}
} else if startSwipeState == .left {
if startSwipeDir == .left {
maxOffsetLeft = .zero
maxOffsetRight = .zero
} else if startSwipeDir == .right {
maxOffsetLeft = CGFloat(200)
maxOffsetRight = CGFloat(10)
} }
} }
// offset.y = gesture.translation.width
// logger.log("hello") if newOffset + pulledOut.width < -maxOffsetLeft {
} if startSwipeState == .right && startSwipeDir == .left {
.onEnded { _ in newOffset = -maxOffsetLeft + rubberBandEffect(newOffset + maxOffsetLeft) - pulledOut.width
endedDrag = true } else {
var pulledOutWidth = CGFloat(0) newOffset = -maxOffsetLeft + rubberBandEffect(newOffset + maxOffsetLeft)
if viewState.width < 0 { }
pulledOutWidth = CGFloat(0)
} else if abs(viewState.width + pulledOut.width) > 30 { } else if newOffset + pulledOut.width > maxOffsetRight {
pulledOutWidth = CGFloat(200) if startSwipeState == .left, startSwipeDir == .right {
newOffset = maxOffsetRight + rubberBandEffect(newOffset - maxOffsetRight) - pulledOut.width
} else {
newOffset = maxOffsetRight + rubberBandEffect(newOffset - maxOffsetRight)
}
} }
self.mainSwipe.width = newOffset
// dragOffset is what is used to make the text be readable
// with the right pane being visible
// if mainSwipe.width < -margin && pulledOut.width <= 0 {
if mainSwipe.width < -margin && pulledOut.width <= 0 {
dragOffset = margin + mainSwipe.width + pulledOut.width
}
if mainSwipe.width > 0 && pulledOut.width < 0 {
dragOffset = margin + mainSwipe.width + pulledOut.width
}
}
.onEnded { _ in
var finalSwipe = CGFloat(0)
let swipeLeftFinal = CGFloat(-200)
let swipeRightFinal = CGFloat(110)
let margin = CGFloat(30)
var setDragOffset = CGFloat(0)
if startSwipeState == .center {
if startSwipeDir == .right {
finalSwipe = swipeRightFinal
} else {
finalSwipe = swipeLeftFinal
setDragOffset = margin + swipeLeftFinal
}
} else if startSwipeState == .right {
finalSwipe = .zero
if startSwipeDir == .left {
finalSwipe = .zero
} else {
finalSwipe = swipeRightFinal
}
} else if startSwipeState == .left {
if startSwipeDir == .left {
finalSwipe = swipeLeftFinal
setDragOffset = margin + swipeLeftFinal
} else {
finalSwipe = .zero
}
}
startSwipeState = nil
startSwipeDir = nil
print("foo")
// if mainSwipe.width < 0 && pulledOut.width > 0 {
// setPulledOutWith = CGFloat(0)
// } else if mainSwipe.width > 0 && pulledOut.width < 0 {
// setPulledOutWith = CGFloat(0)
// } else if (mainSwipe.width < 0 && pulledOut.width < 0) ||
// (mainSwipe.width < 0 && pulledOut.width == 0) {
// setPulledOutWith = pulledOutRight
// setDragOffset = margin + setPulledOutWith
// } else if abs(mainSwipe.width + pulledOut.width) > 30 {
// setPulledOutWith = pulledOutLeft
// }
withAnimation(.spring(response: 0.2)) { withAnimation(.spring(response: 0.2)) {
pulledOut.width = pulledOutWidth pulledOut.width = finalSwipe
viewState = .zero dragOffset = setDragOffset
dragOffset = .zero mainSwipe = .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)
}
} }
} }
.background(Color(red: 0.1, green: 0.1, blue: 0.1))
} }
func handleVisibilityChanged2(_: String, change _: VisibilityChange, tracker _: VisibilityTracker<String>) {} func handleVisibilityChanged2(_: String, change _: VisibilityChange, tracker _: VisibilityTracker<String>) {}
func handleVisibilityChanged(_: String, change _: VisibilityChange, tracker: VisibilityTracker<String>) { func rubberBandEffect(_ offset: CGFloat) -> CGFloat {
// var printRate: Int64 = 10 let resistance: CGFloat = 0.55
gTracker = tracker return resistance * pow(abs(offset), 0.7) * (offset < 0 ? -1 : 1)
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!]!
} }
} }
@ -638,6 +620,7 @@ struct ContentView_Previews: PreviewProvider {
} }
extension View { extension View {
@discardableResult
func Print(_ vars: Any...) -> some View { func Print(_ vars: Any...) -> some View {
for v in vars { for v in vars {
print(v) print(v)

View File

@ -1,39 +1,17 @@
import GRDB import GRDB
/// The Line struct. /// The Line struct.
///
/// Identifiable conformance supports SwiftUI list animations, and type-safe
/// GRDB primary key methods.
/// Equatable conformance supports tests.
struct Line: Identifiable, Equatable { struct Line: Identifiable, Equatable {
/// The player id.
///
/// 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 id: Int64?
var chap: Int var chap: Int
var line_id: Int // this is a line_id per book
var verse: Int var verse: Int
var body: String var body: String
var book: String var book: String
} }
extension Line { extension Line {
private static let books = [ private static let books = [
"John", "Matthew", "Imitation of Christ"] "John", "Matthew", "Imitation of Christ"]
/// Creates a new player with empty name and zero score
// static func new() -> Line {
// Line(id: nil, chap: 1, body: "")
// }
/// Returns a random score
static func randomScore() -> Int {
10 * Int.random(in: 0...100)
}
static func randomBook() -> String {
books.randomElement()!
}
} }
// MARK: - Persistence // MARK: - Persistence

View File

@ -49,11 +49,12 @@ struct LineRequest: Queryable {
// This method is not required by Queryable, but it makes it easier // This method is not required by Queryable, but it makes it easier
// to test LineRequest. // to test LineRequest.
func fetchValue(_ db: Database) throws -> [Line] { func fetchValue(_ db: Database) throws -> [Line] {
if book == "" { return try Line.filter(bookColumn == book).fetchAll(db)
return try Line.filter(bookColumn == Line.randomBook()).fetchAll(db) // if book == "" {
} else { // return try Line.filter(bookColumn == Line.randomBook()).fetchAll(db)
return try Line.filter(bookColumn == book).fetchAll(db) // } else {
} // return try Line.filter(bookColumn == book).fetchAll(db)
// }
// switch ordering { // switch ordering {
// case .byScore: // case .byScore:
// return try Line.all().fetchAll(db) // return try Line.all().fetchAll(db)

View File

@ -40,6 +40,7 @@ extension AppDatabase {
// // demo purpose. // // demo purpose.
// try appDatabase.createRandomPlayersIfEmpty() // try appDatabase.createRandomPlayersIfEmpty()
// } // }
print("initing database")
try appDatabase.initDatabase() try appDatabase.initDatabase()

View File

@ -17,7 +17,12 @@ struct Ribbon: Identifiable, Equatable {
/// Int64 is the recommended type for auto-incremented database ids. /// Int64 is the recommended type for auto-incremented database ids.
/// Use nil for players that are not inserted yet in the database. /// Use nil for players that are not inserted yet in the database.
var id: Int64? var id: Int64?
var groupId: Int
var pos: Int var pos: Int
var undoLevel: Int
var currentLevel: Int
var minLevel: Int
var maxLevel: Int
var title: String var title: String
var book: String var book: String
var scrollId: String var scrollId: String
@ -27,7 +32,6 @@ struct Ribbon: Identifiable, Equatable {
extension Ribbon { extension Ribbon {
} }
// MARK: - Persistence
/// Make Line a Codable Record. /// Make Line a Codable Record.
/// ///

1
json/acts_export.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long