Files
tianma-if cd4f010d8f feat(ios): add native SwiftUI client with Android UI parity
Replace the abandoned RN iOS path with a SwiftUI app (iOS 17+) that reuses
the shared TipTap editor bundle, GRDB local mirror/outbox, and Android-aligned
shell chrome for list, create, detail, and Me. Package the editor HTML in the
app resources so Markdown renders, present edit from the workspace root with a
UIKit FAB, and keep caret stable while typing.
2026-08-07 02:52:01 +08:00

120 lines
4.3 KiB
Swift

import Foundation
import GRDB
enum AppDatabase {
static let fileName = "edgeever-ios.sqlite"
static func makeShared() throws -> DatabaseQueue {
let dir = try FileManager.default.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
).appendingPathComponent("EdgeEver", isDirectory: true)
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
let path = dir.appendingPathComponent(fileName).path
var config = Configuration()
config.prepareDatabase { db in
try db.execute(sql: "PRAGMA foreign_keys = ON")
}
let dbQueue = try DatabaseQueue(path: path, configuration: config)
try dbQueue.writeWithoutTransaction { db in
try db.execute(sql: "PRAGMA journal_mode = WAL")
}
try migrator.migrate(dbQueue)
return dbQueue
}
/// In-memory queue for unit tests.
static func makeEmpty() throws -> DatabaseQueue {
let dbQueue = try DatabaseQueue()
try migrator.migrate(dbQueue)
return dbQueue
}
private static var migrator: DatabaseMigrator {
var migrator = DatabaseMigrator()
migrator.registerMigration("v1_mirror_outbox_drafts") { db in
try db.execute(sql: """
CREATE TABLE mobile_notebooks (
scope TEXT NOT NULL,
id TEXT NOT NULL,
name TEXT NOT NULL,
sort_order INTEGER NOT NULL,
data_json TEXT NOT NULL,
PRIMARY KEY (scope, id)
);
CREATE TABLE mobile_memos (
scope TEXT NOT NULL,
id TEXT NOT NULL,
notebook_id TEXT NOT NULL,
title TEXT NOT NULL,
content_text TEXT NOT NULL,
tags_text TEXT NOT NULL,
is_pinned INTEGER NOT NULL,
is_deleted INTEGER NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
deleted_at TEXT,
data_json TEXT NOT NULL,
PRIMARY KEY (scope, id)
);
CREATE INDEX idx_mobile_memos_feed
ON mobile_memos(scope, is_deleted, updated_at DESC);
CREATE INDEX idx_mobile_memos_notebook
ON mobile_memos(scope, notebook_id, is_deleted, updated_at DESC);
CREATE TABLE mobile_sync_meta (
scope TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (scope, key)
);
CREATE TABLE mobile_id_mappings (
scope TEXT NOT NULL,
temporary_id TEXT NOT NULL,
remote_id TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (scope, temporary_id)
);
CREATE TABLE mobile_sync_outbox (
scope TEXT NOT NULL,
id TEXT NOT NULL,
kind TEXT NOT NULL,
memo_id TEXT NOT NULL,
status TEXT NOT NULL,
payload_json TEXT NOT NULL,
attempt_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT,
next_attempt_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (scope, id)
);
CREATE INDEX idx_mobile_outbox_flush
ON mobile_sync_outbox(scope, status, next_attempt_at, created_at);
CREATE TABLE mobile_drafts (
scope TEXT NOT NULL,
draft_key TEXT NOT NULL,
title TEXT NOT NULL,
content_markdown TEXT NOT NULL,
content_json TEXT,
notebook_id TEXT NOT NULL,
tags_text TEXT NOT NULL,
expected_revision INTEGER,
updated_at TEXT NOT NULL,
PRIMARY KEY (scope, draft_key)
);
""")
}
return migrator
}
}