scroll setting works, now need to read scroll offset

undo
Saint 2023-02-27 17:27:15 -05:00
commit 9af28ef0df
21 changed files with 1328 additions and 0 deletions

65
RibbonRequest.swift Normal file
View File

@ -0,0 +1,65 @@
import Combine
import GRDB
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 ... )
/// }
/// }
struct RibbonRequest: Queryable {
// enum Ordering {
// case byScore
// case byName
// }
/// The ordering used by the player request.
// var ordering: Ordering
// var book: String
// MARK: - Queryable Implementation
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] {
return try Ribbon.fetchAll(db)
// if book == "" {
// return try Ribbon.filter(bookColumn == Ribbon.randomBook()).fetchAll(db)
// } else {
// return try Ribbon.filter(bookColumn == book).fetchAll(db)
// }
// switch ordering {
// case .byScore:
// return try Ribbon.all().fetchAll(db)
// case .byName:
// // return try Ribbon.all().orderedByName().fetchAll(db)
// return try Ribbon.all().fetchAll(db)
// }
}
}

209
ScrollableView.swift Normal file
View File

@ -0,0 +1,209 @@
import SwiftUI
struct ScrollableView<Content: View>: UIViewControllerRepresentable, Equatable {
// MARK: - Coordinator
final class Coordinator: NSObject, UIScrollViewDelegate {
// MARK: - Properties
private let scrollView: UIScrollView
var offset: Binding<CGPoint>
// MARK: - Init
init(_ scrollView: UIScrollView, offset: Binding<CGPoint>) {
self.scrollView = scrollView
self.offset = offset
super.init()
self.scrollView.delegate = self
}
// MARK: - UIScrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
DispatchQueue.main.async {
self.offset.wrappedValue = scrollView.contentOffset
}
}
}
// MARK: - Type
typealias UIViewControllerType = UIScrollViewController<Content>
// MARK: - Properties
var offset: Binding<CGPoint>
var animationDuration: TimeInterval
var showsScrollIndicator: Bool
var axis: Axis
var content: () -> Content
var onScale: ((CGFloat)->Void)?
var disableScroll: Bool
var forceRefresh: Bool
var stopScrolling: Binding<Bool>
private let scrollViewController: UIViewControllerType
// MARK: - Init
init(_ offset: Binding<CGPoint>, animationDuration: TimeInterval, showsScrollIndicator: Bool = true, axis: Axis = .vertical, onScale: ((CGFloat)->Void)? = nil, disableScroll: Bool = false, forceRefresh: Bool = false, stopScrolling: Binding<Bool> = .constant(false), @ViewBuilder content: @escaping () -> Content) {
self.offset = offset
self.onScale = onScale
self.animationDuration = animationDuration
self.content = content
self.showsScrollIndicator = showsScrollIndicator
self.axis = axis
self.disableScroll = disableScroll
self.forceRefresh = forceRefresh
self.stopScrolling = stopScrolling
self.scrollViewController = UIScrollViewController(rootView: self.content(), offset: self.offset, axis: self.axis, onScale: self.onScale)
}
// MARK: - Updates
func makeUIViewController(context: UIViewControllerRepresentableContext<Self>) -> UIViewControllerType {
self.scrollViewController
}
func updateUIViewController(_ viewController: UIViewControllerType, context: UIViewControllerRepresentableContext<Self>) {
viewController.scrollView.showsVerticalScrollIndicator = self.showsScrollIndicator
viewController.scrollView.showsHorizontalScrollIndicator = self.showsScrollIndicator
viewController.updateContent(self.content)
let duration: TimeInterval = self.duration(viewController)
let newValue: CGPoint = self.offset.wrappedValue
viewController.scrollView.isScrollEnabled = !self.disableScroll
if self.stopScrolling.wrappedValue {
viewController.scrollView.setContentOffset(viewController.scrollView.contentOffset, animated:false)
return
}
guard duration != .zero else {
viewController.scrollView.contentOffset = newValue
return
}
UIView.animate(withDuration: duration, delay: 0, options: [.allowUserInteraction, .curveEaseInOut, .beginFromCurrentState], animations: {
viewController.scrollView.contentOffset = newValue
}, completion: nil)
}
func makeCoordinator() -> Coordinator {
Coordinator(self.scrollViewController.scrollView, offset: self.offset)
}
//Calcaulte max offset
private func newContentOffset(_ viewController: UIViewControllerType, newValue: CGPoint) -> CGPoint {
let maxOffsetViewFrame: CGRect = viewController.view.frame
let maxOffsetFrame: CGRect = viewController.hostingController.view.frame
let maxOffsetX: CGFloat = maxOffsetFrame.maxX - maxOffsetViewFrame.maxX
let maxOffsetY: CGFloat = maxOffsetFrame.maxY - maxOffsetViewFrame.maxY
return CGPoint(x: min(newValue.x, maxOffsetX), y: min(newValue.y, maxOffsetY))
}
//Calculate animation speed
private func duration(_ viewController: UIViewControllerType) -> TimeInterval {
var diff: CGFloat = 0
switch axis {
case .horizontal:
diff = abs(viewController.scrollView.contentOffset.x - self.offset.wrappedValue.x)
default:
diff = abs(viewController.scrollView.contentOffset.y - self.offset.wrappedValue.y)
}
if diff == 0 {
return .zero
}
let percentageMoved = diff / UIScreen.main.bounds.height
return self.animationDuration * min(max(TimeInterval(percentageMoved), 0.25), 1)
}
// MARK: - Equatable
static func == (lhs: ScrollableView, rhs: ScrollableView) -> Bool {
return !lhs.forceRefresh && lhs.forceRefresh == rhs.forceRefresh
}
}
final class UIScrollViewController<Content: View> : UIViewController, ObservableObject {
// MARK: - Properties
var offset: Binding<CGPoint>
var onScale: ((CGFloat)->Void)?
let hostingController: UIHostingController<Content>
private let axis: Axis
lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.canCancelContentTouches = true
scrollView.delaysContentTouches = true
scrollView.scrollsToTop = false
scrollView.backgroundColor = .clear
if self.onScale != nil {
scrollView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(self.onGesture)))
}
return scrollView
}()
@objc func onGesture(gesture: UIPinchGestureRecognizer) {
self.onScale?(gesture.scale)
}
// MARK: - Init
init(rootView: Content, offset: Binding<CGPoint>, axis: Axis, onScale: ((CGFloat)->Void)?) {
self.offset = offset
self.hostingController = UIHostingController<Content>(rootView: rootView)
self.hostingController.view.backgroundColor = .clear
self.axis = axis
self.onScale = onScale
super.init(nibName: nil, bundle: nil)
}
// MARK: - Update
func updateContent(_ content: () -> Content) {
self.hostingController.rootView = content()
self.scrollView.addSubview(self.hostingController.view)
var contentSize: CGSize = self.hostingController.view.intrinsicContentSize
switch axis {
case .vertical:
contentSize.width = self.scrollView.frame.width
case .horizontal:
contentSize.height = self.scrollView.frame.height
}
self.hostingController.view.frame.size = contentSize
self.scrollView.contentSize = contentSize
self.view.updateConstraintsIfNeeded()
self.view.layoutIfNeeded()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.scrollView)
self.createConstraints()
self.view.setNeedsUpdateConstraints()
self.view.updateConstraintsIfNeeded()
self.view.layoutIfNeeded()
}
// MARK: - Constraints
fileprivate func createConstraints() {
NSLayoutConstraint.activate([
self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor),
self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
}
}

147
gloss/AppDatabase.swift Normal file
View File

@ -0,0 +1,147 @@
import Foundation
import GRDB
/// AppDatabase lets the application access the database.
///
/// It applies the pratices recommended at
/// <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
struct AppDatabase {
/// Creates an `AppDatabase`, and make sure the database schema is ready.
init(_ dbWriter: any DatabaseWriter) throws {
self.dbWriter = dbWriter
try migrator.migrate(dbWriter)
}
/// Provides access to the database.
///
/// Application can use a `DatabasePool`, while SwiftUI previews and tests
/// can use a fast in-memory `DatabaseQueue`.
///
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>
private let dbWriter: any DatabaseWriter
/// The DatabaseMigrator that defines the database schema.
///
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
private var migrator: DatabaseMigrator {
var migrator = DatabaseMigrator()
#if DEBUG
// Speed up development by nuking the database when migrations change
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
migrator.eraseDatabaseOnSchemaChange = true
#endif
migrator.registerMigration("createLine") { db in
// Create a table
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseschema>
try db.create(table: "Line") { t in
t.autoIncrementedPrimaryKey("id")
t.column("body", .text).notNull()
t.column("chap", .integer).notNull()
t.column("book", .text).notNull()
t.column("verse", .integer)
}
try db.create(table: "Ribbon") { t in
t.autoIncrementedPrimaryKey("id")
t.column("book", .text).notNull()
t.column("scrollOffset", .integer).notNull()
}
}
// Migrations for future application versions will be inserted here:
// migrator.registerMigration(...) { db in
// ...
// }
return migrator
}
}
// MARK: - Database Access: Writes
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
extension AppDatabase {
/// Create random Lines if the database is empty.
func createRandomLinesIfEmpty() throws {
let imports : JsonImport = load("john_export.json")
try dbWriter.write { db in
if try Line.all().isEmpty(db) {
for l in imports.lines {
print("here")
print(l.body)
_ = try l.inserted(db)
}
// print("cat")
// try createRandomLines(db)
_ = try Ribbon(id: nil, book: "bible.john", scrollOffset: 0).inserted(db)
_ = try Ribbon(id: nil, book: "bible.john", scrollOffset: 2000).inserted(db)
}
}
}
// static let uiTestLines = [
// Line(id: nil, name: "Arthur", score: 5),
// Line(id: nil, name: "Barbara", score: 6),
// Line(id: nil, name: "Craig", score: 8),
// Line(id: nil, name: "David", score: 4),
// Line(id: nil, name: "Elena", score: 1),
// Line(id: nil, name: "Frederik", score: 2),
// Line(id: nil, name: "Gilbert", score: 7),
// Line(id: nil, name: "Henriette", score: 3)]
// func createLinesForUITests() throws {
// try dbWriter.write { db in
// try AppDatabase.uiTestLines.forEach { Line in
// _ = try Line.inserted(db) // insert but ignore inserted id
// }
// }
// }
// /// Support for `createRandomLinesIfEmpty()` and `refreshLines()`.
private func createRandomLines(_ db: Database) throws {
for _ in 0..<500 {
_ = try Line.makeRandom().inserted(db) // insert but ignore inserted id
}
}
}
// MARK: - Database Access: Reads
// This demo app does not provide any specific reading method, and instead
// gives an unrestricted read-only access to the rest of the application.
// In your app, you are free to choose another path, and define focused
// reading methods.
extension AppDatabase {
/// Provides a read-only access to the database
var reader: DatabaseReader {
dbWriter
}
}

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

231
gloss/ContentView.swift Normal file
View File

@ -0,0 +1,231 @@
//
// ContentView.swift
// gloss
//
// Created by Saint on 10/23/22.
//
import SwiftUI
import GRDB
import GRDBQuery
import Introspect
import os
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network")
// var curBook = "John"
extension UINavigationController {
override open func viewDidLoad() {
super.viewDidLoad()
let standard = UINavigationBarAppearance()
standard.backgroundColor = .red //When you scroll or you have title (small one)
let compact = UINavigationBarAppearance()
compact.backgroundColor = .green //compact-height
let scrollEdge = UINavigationBarAppearance()
scrollEdge.backgroundColor = .blue //When you have large title
navigationBar.standardAppearance = standard
navigationBar.compactAppearance = compact
navigationBar.scrollEdgeAppearance = scrollEdge
}
}
struct BlueButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.font(.headline)
.frame(width: 160)
.contentShape(Rectangle())
.foregroundColor(configuration.isPressed ? Color.white.opacity(0.5) : Color.black)
.background(configuration.isPressed ? Color.purple.opacity(0.5) : Color.purple)
.listRowBackground(configuration.isPressed ? Color.blue.opacity(0.5) : Color.black)
}
}
struct SwitchButton : View {
var ribbon: Ribbon
@Binding var selectedRibbon : Ribbon?
@Binding var book : String
@Binding var scrollOffset : CGFloat
var body: some View {
Button("meow") {
book = ribbon.book
selectedRibbon = ribbon
Print(ribbon.scrollOffset)
Print(scrollOffset)
scrollOffset = CGFloat(ribbon.scrollOffset)
//Print("hellosdf dddd")
}
.buttonStyle(BlueButtonStyle())
}
}
struct ContentView: View {
@State var viewState = CGSize.zero
@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 selectedRibbon: Ribbon!
@Query(LineRequest(ordering: .byScore, book: "bible.john")) private var lines: [Line]
@Query(RibbonRequest()) private var ribbons: [Ribbon]
// @Query<LineRequest> private var lines: [Line]
init() {
UITableView.appearance().backgroundColor = UIColor(Color(red: 0.2, green: 0.2, blue: 0.2))
// _lines = Query(LineRequest(ordering: .byScore, book: curBook))
}
// @Environment(\.managedObjectContext) private var viewContext
var body: some View {
GeometryReader { geometry in
ZStack{
VStack{
ForEach(ribbons) { ribbon in
SwitchButton(ribbon: ribbon,
selectedRibbon: $selectedRibbon,
book: $lines.book,
scrollOffset: $scrollOffset)
.buttonStyle(BlueButtonStyle())
}
}
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading)
.background(Color.red)
// VStack (spacing: 0){
ScrollViewReader { proxy in
//ScrollableView($offset, animationDuration: 0) {
ScrollView {
// List {
LazyVStack {
ForEach(lines) { line in
Text(String(line.body))
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
.font(Font.custom("AveriaSerifLibre-Regular", size: 30))
.listRowBackground(Color(red: 0.2, green: 0.8, blue: 0.2))
.foregroundColor(Color.white)
.listRowInsets(EdgeInsets())
.padding(EdgeInsets(top: 10, leading: 20, bottom: 40, trailing: 20))
.listRowSeparator(.hidden)
.id(line.id!)
.onTapGesture {
selectedLine = line.id!
//Print(selectedLine)
}
// }
}
}
.background(Color(red: 0.2, green: 0.2, blue: 0.2))
//.onChange(of: scrollTo1) { scrollTo in
// // scrollTo1 = 0
// let target = scrollTo
// //Print("hellosdf")
// //Print("value:", target)
// //Print("value2:", scrollTo)
// proxy.scrollTo(target, anchor: .top)
//}
}
.introspectScrollView { scrollView in
//let width = scrollView.contentSize.width - scrollView.frame.width
//Print(scrollView.contentOffset.x)
scrollView.contentOffset.y = scrollOffset
}
.listStyle(PlainListStyle())
}
// }
.background(Color(red: 0.2, green: 0.2, blue: 0.2))
.frame(width: geometry.size.width - 50)
.offset(x:30 , y:0)
.offset(x: pulledOut.width)
.offset(x: viewState.width, y: viewState.height)
.onAppear {
if (selectedRibbon != nil) {
Print(selectedRibbon)
//scrollOffset.y = scrollOffset
}
}
.gesture(
DragGesture()
.onChanged { gesture in
// logger.error("hello222")
// NSLog("hellooo")
//Print(viewState.width)
viewState.width = gesture.translation.width
//offset.y = gesture.translation.width
// logger.log("hello")
}
.onEnded { _ in
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
}
}
)
}
}
}
}
private struct LineRow: View {
var line: Line
var body: some View {
HStack {
//Print("Here I am", line.body)
Text(line.body)
Spacer()
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
ContentView().environment(\.appDatabase, .random())
}
}
extension View {
func Print(_ vars: Any...) -> some View {
for v in vars { print(v) }
return EmptyView()
}
}

11
gloss/Info.plist Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIAppFonts</key>
<array>
<string>AveriaSerifLibre-Regular.ttf</string>
<string>xe-Dogma-Bold.ttf</string>
</array>
</dict>
</plist>

11
gloss/JsonImport.swift Normal file
View File

@ -0,0 +1,11 @@
//
// JsonImport.swift
// gloss
//
// Created by Saint on 2/24/23.
//
struct JsonImport: Decodable {
var lines : [Line]
// var segs: [Seg]
}

123
gloss/Line.swift Normal file
View File

@ -0,0 +1,123 @@
import GRDB
/// The Line struct.
///
/// Identifiable conformance supports SwiftUI list animations, and type-safe
/// GRDB primary key methods.
/// Equatable conformance supports tests.
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 chap: Int
var verse: Int
var body: String
var book: String
}
extension Line {
private static let names = [
"Arthur", "Anita", "Barbara", "Bernard", "Craig", "Chiara", "David",
"Dean", "Éric", "Elena", "Fatima", "Frederik", "Gilbert", "Georgette",
"Henriette", "Hassan", "Ignacio", "Irene", "Julie", "Jack", "Karl",
"Kristel", "Louis", "Liz", "Masashi", "Mary", "Noam", "Nicole",
"Ophelie", "Oleg", "Pascal", "Patricia", "Quentin", "Quinn", "Raoul",
"Rachel", "Stephan", "Susie", "Tristan", "Tatiana", "Ursule", "Urbain",
"Victor", "Violette", "Wilfried", "Wilhelmina", "Yvon", "Yann",
"Zazie", "Zoé"]
private static let books = [
"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: "")
// }
// Creates a new player with random name and random score
static func makeRandom() -> Line {
Line(id: nil, chap: randomScore(), verse: 1, body: randomName(), book: randomBook())
}
/// Returns a random name
static func randomName() -> String {
names.randomElement()!
}
/// Returns a random score
static func randomScore() -> Int {
10 * Int.random(in: 0...100)
}
static func randomBook() -> String {
books.randomElement()!
}
}
// MARK: - Persistence
/// Make Line a Codable Record.
///
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#records>
extension Line: Codable, FetchableRecord, MutablePersistableRecord {
// Define database columns from CodingKeys
fileprivate enum Columns {
static let id = Column(CodingKeys.id)
static let chap = Column(CodingKeys.chap)
}
/// Updates a player id after it has been inserted in the database.
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
// MARK: - Line Database Requests
/// Define some player requests used by the application.
///
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#requests>
/// See <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
extension DerivableRequest<Line> {
/// A request of players ordered by name.
///
/// For example:
///
/// let players: [Line] = try dbWriter.read { db in
/// try Line.all().orderedByName().fetchAll(db)
/// }
//func orderedByName() -> Self {
// // Sort by name in a localized case insensitive fashion
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
// order(Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
//}
///// A request of players ordered by score.
/////
///// For example:
/////
///// let players: [Line] = try dbWriter.read { db in
///// try Line.all().orderedByScore().fetchAll(db)
///// }
///// let bestLine: Line? = try dbWriter.read { db in
///// try Line.all().orderedByScore().fetchOne(db)
///// }
//func orderedByScore() -> Self {
// // Sort by descending score, and then by name, in a
// // localized case insensitive fashion
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
// order(
// Line.Columns.score.desc,
// Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
//}
}
//
// Line.swift
// gloss
//
// Created by Saint on 2/18/23.
//
import Foundation

65
gloss/LineRequest.swift Normal file
View File

@ -0,0 +1,65 @@
import Combine
import GRDB
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(LineRequest(ordering: .byName)) private var players: [Line]
///
/// var body: some View {
/// List(players) { player in ... )
/// }
/// }
var bookColumn = Column("book")
struct LineRequest: Queryable {
enum Ordering {
case byScore
case byName
}
/// The ordering used by the player request.
var ordering: Ordering
var book: String
// MARK: - Queryable Implementation
static var defaultValue: [Line] { [] }
func publisher(in appDatabase: AppDatabase) -> AnyPublisher<[Line], 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
// to test LineRequest.
func fetchValue(_ db: Database) throws -> [Line] {
if book == "" {
return try Line.filter(bookColumn == Line.randomBook()).fetchAll(db)
} else {
return try Line.filter(bookColumn == book).fetchAll(db)
}
// switch ordering {
// case .byScore:
// return try Line.all().fetchAll(db)
// case .byName:
// // return try Line.all().orderedByName().fetchAll(db)
// return try Line.all().fetchAll(db)
// }
}
}

75
gloss/Persistence.swift Normal file
View File

@ -0,0 +1,75 @@
import GRDB
import Foundation
extension AppDatabase {
/// The database for the application
static let shared = makeShared()
private static func makeShared() -> AppDatabase {
do {
// Pick a folder for storing the SQLite database, as well as
// the various temporary files created during normal database
// operations (https://sqlite.org/tempfiles.html).
let fileManager = FileManager()
let folderURL = try fileManager
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("database", isDirectory: true)
// Support for tests: delete the database if requested
if CommandLine.arguments.contains("-reset") {
try? fileManager.removeItem(at: folderURL)
}
// Create the database folder if needed
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true)
// Connect to a database on disk
// See https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections
let dbURL = folderURL.appendingPathComponent("db.sqlite")
let dbPool = try DatabasePool(path: dbURL.path)
// Create the AppDatabase
let appDatabase = try AppDatabase(dbPool)
// // Prepare the database with test fixtures if requested
// if CommandLine.arguments.contains("-fixedTestData") {
// try appDatabase.createPlayersForUITests()
// } else {
// // Otherwise, populate the database if it is empty, for better
// // demo purpose.
// try appDatabase.createRandomPlayersIfEmpty()
// }
try appDatabase.createRandomLinesIfEmpty()
return appDatabase
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate.
//
// Typical reasons for an error here include:
// * The parent directory cannot be created, or disallows writing.
// * The database is not accessible, due to permissions or data protection when the device is locked.
// * The device is out of space.
// * The database could not be migrated to its latest schema version.
// Check the error message to determine what the actual problem was.
fatalError("Unresolved error \(error)")
}
}
/// Creates an empty database for SwiftUI previews
static func empty() -> AppDatabase {
// Connect to an in-memory database
// See https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections
let dbQueue = try! DatabaseQueue()
return try! AppDatabase(dbQueue)
}
/// Creates a database full of random players for SwiftUI previews
static func random() -> AppDatabase {
let appDatabase = empty()
try! appDatabase.createRandomLinesIfEmpty()
return appDatabase
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

92
gloss/Ribbon.swift Normal file
View File

@ -0,0 +1,92 @@
//
// Ribbon.swift
// gloss
//
// Created by Saint on 2/24/23.
//
import GRDB
/// The Line struct.
///
/// Identifiable conformance supports SwiftUI list animations, and type-safe
/// GRDB primary key methods.
/// Equatable conformance supports tests.
struct Ribbon: 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 book: String
var scrollOffset: Int
}
extension Ribbon {
}
// MARK: - Persistence
/// Make Line a Codable Record.
///
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#records>
extension Ribbon: Codable, FetchableRecord, MutablePersistableRecord {
// Define database columns from CodingKeys
fileprivate enum Columns {
static let id = Column(CodingKeys.id)
static let book = Column(CodingKeys.book)
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: - Line Database Requests
/// Define some player requests used by the application.
///
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#requests>
/// See <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
extension DerivableRequest<Line> {
/// A request of players ordered by name.
///
/// For example:
///
/// let players: [Line] = try dbWriter.read { db in
/// try Line.all().orderedByName().fetchAll(db)
/// }
//func orderedByName() -> Self {
// // Sort by name in a localized case insensitive fashion
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
// order(Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
//}
///// A request of players ordered by score.
/////
///// For example:
/////
///// let players: [Line] = try dbWriter.read { db in
///// try Line.all().orderedByScore().fetchAll(db)
///// }
///// let bestLine: Line? = try dbWriter.read { db in
///// try Line.all().orderedByScore().fetchOne(db)
///// }
//func orderedByScore() -> Self {
// // Sort by descending score, and then by name, in a
// // localized case insensitive fashion
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
// order(
// Line.Columns.score.desc,
// Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
//}
}
// Ribbon.swift
// gloss
//
// Created by Saint on 2/27/23.
//
import Foundation

85
gloss/Seg.swift Normal file
View File

@ -0,0 +1,85 @@
//
// Seg.swift
// gloss
//
// Created by Saint on 2/24/23.
//
import GRDB
/// The Line struct.
///
/// Identifiable conformance supports SwiftUI list animations, and type-safe
/// GRDB primary key methods.
/// Equatable conformance supports tests.
struct Seg: 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 rowids: String
var book: String
}
extension Seg {
}
// MARK: - Persistence
/// Make Line a Codable Record.
///
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#records>
extension Seg: Codable, FetchableRecord, MutablePersistableRecord {
// Define database columns from CodingKeys
fileprivate enum Columns {
static let id = Column(CodingKeys.id)
static let rowids = Column(CodingKeys.rowids)
static let book = Column(CodingKeys.book)
}
/// Updates a player id after it has been inserted in the database.
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
// MARK: - Line Database Requests
/// Define some player requests used by the application.
///
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#requests>
/// See <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
extension DerivableRequest<Line> {
/// A request of players ordered by name.
///
/// For example:
///
/// let players: [Line] = try dbWriter.read { db in
/// try Line.all().orderedByName().fetchAll(db)
/// }
//func orderedByName() -> Self {
// // Sort by name in a localized case insensitive fashion
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
// order(Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
//}
///// A request of players ordered by score.
/////
///// For example:
/////
///// let players: [Line] = try dbWriter.read { db in
///// try Line.all().orderedByScore().fetchAll(db)
///// }
///// let bestLine: Line? = try dbWriter.read { db in
///// try Line.all().orderedByScore().fetchOne(db)
///// }
//func orderedByScore() -> Self {
// // Sort by descending score, and then by name, in a
// // localized case insensitive fashion
// // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
// order(
// Line.Columns.score.desc,
// Line.Columns.name.collating(.localizedCaseInsensitiveCompare))
//}
}

20
gloss/SwiftUIView.swift Normal file
View File

@ -0,0 +1,20 @@
//
// SwiftUIView.swift
// gloss
//
// Created by Saint on 10/23/22.
//
import SwiftUI
struct SwiftUIView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}

8
gloss/gloss.entitlements Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.authentication-services.autofill-credential-provider</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>gloss.xcdatamodel</string>
</dict>
</plist>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="false" userDefinedModelVersionIdentifier="">
<entity name="Item" representedClassName="Item" syncable="YES" codeGenerationType="class">
<attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<elements>
<element name="Item" positionX="-63" positionY="-18" width="128" height="44"/>
</elements>
</model>

47
gloss/glossApp.swift Normal file
View File

@ -0,0 +1,47 @@
//
// glossApp.swift
// gloss
//
// Created by Saint on 10/23/22.
//
import GRDBQuery
import SwiftUI
@main
struct glossApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.appDatabase, .shared)
}
}
}
// MARK: - Give SwiftUI access to the database
//
// Define a new environment key that grants access to an AppDatabase.
//
// The technique is documented at
// <https://developer.apple.com/documentation/swiftui/environmentkey>.
private struct AppDatabaseKey: EnvironmentKey {
static var defaultValue: AppDatabase { .empty() }
}
extension EnvironmentValues {
var appDatabase: AppDatabase {
get { self[AppDatabaseKey.self] }
set { self[AppDatabaseKey.self] = newValue }
}
}
// In this demo app, views observe the database with the @Query property
// wrapper, defined in the GRDBQuery package. Its documentation recommends to
// define a dedicated initializer for `appDatabase` access, so we comply:
extension Query where Request.DatabaseContext == AppDatabase {
/// Convenience initializer for requests that feed from `AppDatabase`.
init(_ request: Request) {
self.init(request, in: \.appDatabase)
}
}

1
json/john_export.json Normal file

File diff suppressed because one or more lines are too long