feat: M-IOS-INFO V2 reading event system + iOS 26 build fixes

- Add zx_documentFFI bridging header + modulemap
- Update zx_document.swift (patched for static linking + iOS 26 SDK)
- Replace XCFramework with V2 symbols
- Add V2 types: ReadingMaterialContext, ReadingEventUploadItem, ReadingRuntimeAdapter
- Add V2 session manager: ReadingRuntimeSessionManager
- Add V2 position adapter: ReadingPositionAdapter
- Add V2 event mapper: ReadingEventMapper
- Add upload queue + pipeline: ReadingEventUploadQueue
- Add reading API client: ReadingAPI
- Fix QuickNoteSheet for new NoteAnchor fields (positionSnapshot)
- Restore MaterialReaderView lifecycle events (onAppear/onDisappear)
- Add ScenePhase handling for app background/foreground

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-09 19:58:57 +08:00
parent 5fe29a7d8c
commit a6dde9f0c6
23 changed files with 3703 additions and 235 deletions

View File

@ -129,6 +129,8 @@
05F6CD242FA886350043A7BC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/AIStudyApp/BridgingHeader.h";
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
@ -191,6 +193,8 @@
05F6CD252FA886350043A7BC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/AIStudyApp/BridgingHeader.h";
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
@ -245,6 +249,8 @@
05F6CD272FA886350043A7BC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/AIStudyApp/BridgingHeader.h";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AIStudyApp.entitlements;
@ -295,6 +301,8 @@
05F6CD282FA886350043A7BC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/AIStudyApp/BridgingHeader.h";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = AIStudyApp.entitlements;

View File

@ -6,6 +6,7 @@ struct AIStudyAppApp: App {
@AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
@AppStorage("appAppearance") private var appAppearance = "system"
@StateObject private var authManager = AuthManager()
@Environment(\.scenePhase) private var scenePhase
private var effectiveColorScheme: ColorScheme? {
switch appAppearance {
@ -60,6 +61,31 @@ struct AIStudyAppApp: App {
.task {
await authManager.restoreSession()
}
.onChange(of: scenePhase) { _, newPhase in
switch newPhase {
case .background:
// Pause active session + stop heartbeat
if ReadingRuntimeSessionManager.shared.state == .active {
ReadingRuntimeSessionManager.shared.pause()
}
// Flush events: export enqueue (quick upload in background task)
Task {
let pipeline = ReadingEventUploadPipeline.shared
pipeline.exportAndEnqueue(contexts: [:]) // contexts populated from active sessions
await pipeline.flush()
}
case .active:
// Resume session + reload stale events from Rust
ReadingRuntimeSessionManager.shared.resume()
Task {
ReadingEventUploadPipeline.shared.reloadOnLaunch(contexts: [:])
}
case .inactive:
break
@unknown default:
break
}
}
}
}
}

View File

@ -0,0 +1 @@
#import "Core/Services/zx_documentFFI.h"

View File

@ -0,0 +1,128 @@
import Foundation
// MARK: - Upload Item
struct ReadingEventUploadItem: Equatable {
let eventId: String
let clientSessionId: String
let materialId: String
let eventType: String
let position: ReadingPosition?
let activeSecondsDelta: Int
let clientTimestampMs: Int64
let sequence: UInt64
let readingTargetType: String
let platform: String
let appVersion: String
let clientTimezoneOffsetMinutes: Int
}
// MARK: - Codable (manual ReadingPosition is UniFFI, not Codable)
extension ReadingEventUploadItem: Codable {
enum CodingKeys: String, CodingKey {
case eventId, clientSessionId, materialId, eventType
case activeSecondsDelta, clientTimestampMs, sequence
case readingTargetType, platform, appVersion, clientTimezoneOffsetMinutes
case position
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
eventId = try c.decode(String.self, forKey: .eventId)
clientSessionId = try c.decode(String.self, forKey: .clientSessionId)
materialId = try c.decode(String.self, forKey: .materialId)
eventType = try c.decode(String.self, forKey: .eventType)
activeSecondsDelta = try c.decode(Int.self, forKey: .activeSecondsDelta)
clientTimestampMs = try c.decode(Int64.self, forKey: .clientTimestampMs)
sequence = try c.decode(UInt64.self, forKey: .sequence)
readingTargetType = try c.decode(String.self, forKey: .readingTargetType)
platform = try c.decode(String.self, forKey: .platform)
appVersion = try c.decode(String.self, forKey: .appVersion)
clientTimezoneOffsetMinutes = try c.decode(Int.self, forKey: .clientTimezoneOffsetMinutes)
position = nil // Won't decode from API response only sent, never received
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(eventId, forKey: .eventId)
try c.encode(clientSessionId, forKey: .clientSessionId)
try c.encode(materialId, forKey: .materialId)
try c.encode(eventType, forKey: .eventType)
try c.encode(activeSecondsDelta, forKey: .activeSecondsDelta)
try c.encode(clientTimestampMs, forKey: .clientTimestampMs)
try c.encode(sequence, forKey: .sequence)
try c.encode(readingTargetType, forKey: .readingTargetType)
try c.encode(platform, forKey: .platform)
try c.encode(appVersion, forKey: .appVersion)
try c.encode(clientTimezoneOffsetMinutes, forKey: .clientTimezoneOffsetMinutes)
// Encode position as nested JSON object
if let pos = position {
try c.encode(PositionWrapper(from: pos), forKey: .position)
}
}
}
/// Codable wrapper for ReadingPosition (UniFFI enum without Codable).
private struct PositionWrapper: Encodable {
let raw: [String: AnyCodableValue]
init(from pos: ReadingPosition) {
switch pos {
case .markdown(let blockId, let scrollProgress):
raw = ["type": .string("Markdown"), "blockId": .string(blockId), "scrollProgress": .float(scrollProgress)]
case .text(let lineNumber, let scrollProgress):
raw = ["type": .string("Text"), "lineNumber": .int(Int(lineNumber)), "scrollProgress": .float(scrollProgress)]
case .pdf(let pageNumber, let pageProgress, let overallProgress):
raw = ["type": .string("Pdf"), "pageNumber": .int(Int(pageNumber)), "pageProgress": .float(pageProgress), "overallProgress": .float(overallProgress)]
case .image(let zoomScale, let offsetX, let offsetY):
raw = ["type": .string("Image"), "zoomScale": .float(zoomScale), "offsetX": .float(offsetX), "offsetY": .float(offsetY)]
case .epub(let chapterId, let chapterProgress, let overallProgress):
raw = ["type": .string("Epub"), "chapterId": .string(chapterId), "chapterProgress": .float(chapterProgress), "overallProgress": .float(overallProgress)]
case .unknown:
raw = ["type": .string("Unknown")]
}
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: DynamicCodingKey.self)
for (key, value) in raw {
let codingKey = DynamicCodingKey(stringValue: key)
switch value {
case .string(let s): try c.encode(s, forKey: codingKey)
case .int(let i): try c.encode(i, forKey: codingKey)
case .float(let f): try c.encode(f, forKey: codingKey)
}
}
}
}
private enum AnyCodableValue {
case string(String), int(Int), float(Float)
}
private struct DynamicCodingKey: CodingKey {
let stringValue: String; let intValue: Int? = nil
init(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { nil }
}
// MARK: - Batch Request / Response
struct ReadingEventBatchRequest: Codable {
let events: [ReadingEventUploadItem]
}
struct ReadingEventBatchResponse: Codable {
let processed: Int
let duplicate: Int
let failed: Int
let warnings: [WarningItem]?
struct WarningItem: Codable {
let eventId: String?
let code: String
let message: String
}
}

View File

@ -0,0 +1,31 @@
import Foundation
// MARK: - ReadingTargetType
enum ReadingTargetType: String, Codable, Equatable {
case knowledgeSource = "knowledge_source"
case temporaryFile = "temporary_file"
}
// MARK: - ReadingMaterialContext
/// iOS-side context that supplements Rust's materialId with business fields.
/// Rust only knows materialId; iOS adds readingTargetType/knowledgeBaseId for API upload.
struct ReadingMaterialContext: Equatable {
let readingTargetType: ReadingTargetType
let materialId: String
let knowledgeBaseId: String?
let title: String?
init(
readingTargetType: ReadingTargetType,
materialId: String,
knowledgeBaseId: String? = nil,
title: String? = nil
) {
self.readingTargetType = readingTargetType
self.materialId = materialId
self.knowledgeBaseId = knowledgeBaseId
self.title = title
}
}

View File

@ -0,0 +1,113 @@
import Foundation
// MARK: - Reading API Service
@MainActor
class ReadingAPIService {
static let shared = ReadingAPIService()
private let client = APIClient.shared
/// POST /learning/reading-events/batch
func uploadReadingEvents(_ events: [ReadingEventUploadItem]) async throws -> ReadingEventBatchResponse {
let body = ReadingEventBatchRequest(events: events)
return try await client.request("/learning/reading-events/batch", method: "POST", body: body)
}
/// GET /materials/:id/reading-progress
func getReadingProgress(materialId: String, targetType: String) async throws -> MaterialReadingProgressDTO {
return try await client.request(
"/materials/\(materialId)/reading-progress",
queryItems: [URLQueryItem(name: "readingTargetType", value: targetType)]
)
}
/// GET /learning/continue
func getContinueLearning() async throws -> ContinueLearningResponse {
return try await client.request("/learning/continue")
}
/// GET /learning/summary
func getLearningSummary() async throws -> LearningSummaryResponse {
return try await client.request("/learning/summary")
}
/// GET /learning/trend?days=7
func getLearningTrend(days: Int = 7) async throws -> LearningTrendResponse {
return try await client.request(
"/learning/trend",
queryItems: [URLQueryItem(name: "days", value: String(days))]
)
}
/// GET /learning/records
func getLearningRecords(cursor: String? = nil, limit: Int = 20, type: String? = nil) async throws -> LearningRecordsResponse {
var items = [URLQueryItem(name: "limit", value: String(limit))]
if let c = cursor { items.append(URLQueryItem(name: "cursor", value: c)) }
if let t = type { items.append(URLQueryItem(name: "type", value: t)) }
return try await client.request("/learning/records", queryItems: items)
}
}
// MARK: - Response DTOs
struct MaterialReadingProgressDTO: Codable {
let status: String
let lastProgress: Float?
let totalActiveSeconds: Int
let isMarkedRead: Bool
let firstOpenedAt: String?
let lastReadAt: String?
}
struct ContinueLearningResponse: Codable {
let type: String?
let materialId: String?
let title: String?
let lastProgress: Float?
let totalActiveSeconds: Int?
let lastReadAt: String?
}
struct LearningSummaryResponse: Codable {
let todaySeconds: Int
let weekSeconds: Int
let totalSeconds: Int
let activeDays: Int
let sessionsCount: Int
let materialsReadCount: Int
let markedReadCount: Int
let dailyAverageSeconds: Int
}
struct LearningTrendResponse: Codable {
let days: Int
let series: [TrendPoint]
struct TrendPoint: Codable {
let date: String
let value: Int
}
}
struct LearningRecordsResponse: Codable {
let items: [RecordItem]
let nextCursor: String?
struct RecordItem: Codable {
let id: String
let recordType: String
let title: String
let description: String?
let durationSeconds: Int
let occurredAt: String
let metadata: RecordMetadata?
let createdAt: String
}
struct RecordMetadata: Codable {
let materialId: String?
let readingTargetType: String?
let knowledgeBaseId: String?
let totalActiveSeconds: Int?
}
}

View File

@ -0,0 +1,100 @@
import Foundation
// MARK: - Runtime Session Wrapper
/// Holds the Rust client_session_id for the active reading session.
struct ReadingRuntimeSession {
let sessionId: String
let materialContext: ReadingMaterialContext
let startedAt: Date
}
// MARK: - ReadingRuntimeAdapter Protocol
/// Abstracts all Rust zx_document FFI calls.
/// The default implementation wraps ZxDocumentRuntime (uniffi-generated).
protocol ReadingRuntimeAdapter {
// Session Lifecycle
func startSession(material: ReadingMaterialRef, timestampMs: Int64) throws -> String
func pauseSession(_ sessionId: String) throws
func resumeSession(_ sessionId: String) throws
func closeSession(_ sessionId: String) throws
// Event Push
func pushOpened(sessionId: String, materialId: String, timestampMs: Int64) throws -> ReadingEventV2
func pushClosed(sessionId: String, materialId: String, delta: UInt32, timestampMs: Int64) throws -> ReadingEventV2
func pushPositionChanged(sessionId: String, materialId: String, position: ReadingPosition, timestampMs: Int64) throws -> ReadingEventV2
func pushHeartbeat(sessionId: String, materialId: String, delta: UInt32, position: ReadingPosition?, timestampMs: Int64) throws -> ReadingEventV2
func pushMarkedAsRead(sessionId: String, materialId: String, timestampMs: Int64) throws -> ReadingEventV2
// Buffer Management
func exportEvents(limit: UInt32, timestampMs: Int64) -> [ReadingEventV2]
func ackEvents(_ eventIds: [String]) -> UInt32
func markFailed(_ eventIds: [String]) -> UInt32
func reloadStaleEvents() -> UInt32
func cleanupStaleSessions(nowMs: Int64, maxAgeMs: Int64) -> UInt32
}
// MARK: - Default Implementation (Rust FFI)
final class RustReadingRuntimeAdapter: ReadingRuntimeAdapter {
func startSession(material: ReadingMaterialRef, timestampMs: Int64) throws -> String {
let result = try startReadingSessionV2(material: material, timestampMs: timestampMs)
return result
}
func pauseSession(_ sessionId: String) throws {
try pauseReadingSessionV2(sessionId: sessionId)
}
func resumeSession(_ sessionId: String) throws {
try resumeReadingSessionV2(sessionId: sessionId)
}
func closeSession(_ sessionId: String) throws {
try closeReadingSessionV2(sessionId: sessionId)
}
func pushOpened(sessionId: String, materialId: String, timestampMs: Int64) throws -> ReadingEventV2 {
return try pushMaterialOpenedV2(sessionId: sessionId, materialId: materialId, timestampMs: timestampMs)
}
func pushClosed(sessionId: String, materialId: String, delta: UInt32, timestampMs: Int64) throws -> ReadingEventV2 {
return try pushMaterialClosedV2(sessionId: sessionId, materialId: materialId, activeSecondsDelta: delta, timestampMs: timestampMs)
}
func pushPositionChanged(sessionId: String, materialId: String, position: ReadingPosition, timestampMs: Int64) throws -> ReadingEventV2 {
return try pushPositionChangedV2(sessionId: sessionId, materialId: materialId, position: position, timestampMs: timestampMs)
}
func pushHeartbeat(sessionId: String, materialId: String, delta: UInt32, position: ReadingPosition?, timestampMs: Int64) throws -> ReadingEventV2 {
return try pushHeartbeatV2(sessionId: sessionId, materialId: materialId, activeSecondsDelta: delta, position: position, timestampMs: timestampMs)
}
func pushMarkedAsRead(sessionId: String, materialId: String, timestampMs: Int64) throws -> ReadingEventV2 {
return try pushMarkedAsReadV2(sessionId: sessionId, materialId: materialId, timestampMs: timestampMs)
}
func exportEvents(limit: UInt32, timestampMs: Int64) -> [ReadingEventV2] {
return exportPendingEventsV2(limit: limit, timestampMs: timestampMs)
}
func ackEvents(_ eventIds: [String]) -> UInt32 {
return ackEventsV2(eventIds: eventIds)
}
func markFailed(_ eventIds: [String]) -> UInt32 {
return markEventsFailedV2(eventIds: eventIds)
}
func reloadStaleEvents() -> UInt32 {
return reloadStaleEventsV2()
}
func cleanupStaleSessions(nowMs: Int64, maxAgeMs: Int64) -> UInt32 {
return cleanupStaleSessionsFfi(nowMs: nowMs, maxAgeMs: maxAgeMs)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -242,17 +242,37 @@ typedef struct UniffiForeignFutureResultVoid {
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_ack_events_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint32_t uniffi_zx_document_ffi_fn_func_cleanup_stale_sessions_ffi(int64_t now_ms, int64_t max_age_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
void uniffi_zx_document_ffi_fn_func_clear_exported_events(uint32_t count, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_close_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor_from_search(RustBuffer material_id, RustBuffer result, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
RustBuffer uniffi_zx_document_ffi_fn_func_detect_material_type(RustBuffer file_path, RustCallStatus *_Nonnull out_status
@ -262,6 +282,31 @@ RustBuffer uniffi_zx_document_ffi_fn_func_detect_material_type(RustBuffer file_p
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events_v2(uint32_t limit, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_extract_pdf_text_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_get_office_preview_config_ffi(RustBuffer material_type, uint64_t file_size, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
int8_t uniffi_zx_document_ffi_fn_func_is_office_type_ffi(RustBuffer material_type, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
uint32_t uniffi_zx_document_ffi_fn_func_mark_events_failed_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_MARKDOWN
@ -274,31 +319,107 @@ RustBuffer uniffi_zx_document_ffi_fn_func_parse_markdown(RustBuffer content, Rus
RustBuffer uniffi_zx_document_ffi_fn_func_parse_text(RustBuffer content, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_pause_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_heartbeat_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_marked_as_read_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_closed_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_opened_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_position_changed_v2(RustBuffer session_id, RustBuffer material_id, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
void uniffi_zx_document_ffi_fn_func_push_reading_event(RustBuffer event, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_chapters_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
RustBuffer uniffi_zx_document_ffi_fn_func_read_image_meta(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_pdf_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
RustBuffer uniffi_zx_document_ffi_fn_func_read_text_stats(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_reload_stale_events_v2(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_restore_position_from_anchor(RustBuffer anchor, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_resume_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_search_epub_chapters_ffi(RustBuffer chapter_ids, RustBuffer chapter_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
RustBuffer uniffi_zx_document_ffi_fn_func_search_markdown_blocks(RustBuffer blocks, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
RustBuffer uniffi_zx_document_ffi_fn_func_search_pdf_pages(RustBuffer page_numbers, RustBuffer page_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
RustBuffer uniffi_zx_document_ffi_fn_func_search_text_content(RustBuffer content, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
RustBuffer uniffi_zx_document_ffi_fn_func_start_reading_session_v2(RustBuffer material, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
void uniffi_zx_document_ffi_fn_func_update_reading_position(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
@ -562,18 +683,42 @@ void ffi_zx_document_ffi_rust_future_free_void(uint64_t handle
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
void ffi_zx_document_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_ack_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_cleanup_stale_sessions_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_clear_exported_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_close_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor_from_search(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_DETECT_MATERIAL_TYPE
@ -586,6 +731,36 @@ uint16_t uniffi_zx_document_ffi_checksum_func_detect_material_type(void
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_extract_pdf_text_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_get_office_preview_config_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_is_office_type_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_mark_events_failed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_MARKDOWN
@ -598,36 +773,126 @@ uint16_t uniffi_zx_document_ffi_checksum_func_parse_markdown(void
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_TEXT
uint16_t uniffi_zx_document_ffi_checksum_func_parse_text(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_pause_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_heartbeat_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_marked_as_read_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_closed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_opened_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_position_changed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
uint16_t uniffi_zx_document_ffi_checksum_func_push_reading_event(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_chapters_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_metadata_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
uint16_t uniffi_zx_document_ffi_checksum_func_read_image_meta(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_pdf_metadata_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
uint16_t uniffi_zx_document_ffi_checksum_func_read_text_stats(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_reload_stale_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_restore_position_from_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_resume_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_search_epub_chapters_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
uint16_t uniffi_zx_document_ffi_checksum_func_search_markdown_blocks(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
uint16_t uniffi_zx_document_ffi_checksum_func_search_pdf_pages(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
uint16_t uniffi_zx_document_ffi_checksum_func_search_text_content(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_start_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_UPDATE_READING_POSITION

View File

@ -1,7 +0,0 @@
module zx_documentFFI {
header "zx_documentFFI.h"
export *
use "Darwin"
use "_Builtin_stdbool"
use "_Builtin_stdint"
}

View File

@ -0,0 +1 @@
module zx_documentFFI { header "zx_documentFFI.h"; export * }

View File

@ -0,0 +1,910 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// The following structs are used to implement the lowest level
// of the FFI, and thus useful to multiple uniffied crates.
// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
#ifdef UNIFFI_SHARED_H
// We also try to prevent mixing versions of shared uniffi header structs.
// If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4
#ifndef UNIFFI_SHARED_HEADER_V4
#error Combining helper code from multiple versions of uniffi is not supported
#endif // ndef UNIFFI_SHARED_HEADER_V4
#else
#define UNIFFI_SHARED_H
#define UNIFFI_SHARED_HEADER_V4
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
typedef struct RustBuffer
{
uint64_t capacity;
uint64_t len;
uint8_t *_Nullable data;
} RustBuffer;
typedef struct ForeignBytes
{
int32_t len;
const uint8_t *_Nullable data;
} ForeignBytes;
// Error definitions
typedef struct RustCallStatus {
int8_t code;
RustBuffer errorBuf;
} RustCallStatus;
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
#endif // def UNIFFI_SHARED_H
#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK
typedef void (*UniffiForeignFutureDroppedCallback)(uint64_t
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
typedef void (*UniffiCallbackInterfaceFree)(uint64_t
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE
typedef uint64_t (*UniffiCallbackInterfaceClone)(uint64_t
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT
typedef struct UniffiForeignFutureDroppedCallbackStruct {
uint64_t handle;
UniffiForeignFutureDroppedCallback _Nonnull free;
} UniffiForeignFutureDroppedCallbackStruct;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8
typedef struct UniffiForeignFutureResultU8 {
uint8_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU8;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureResultU8
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8
typedef struct UniffiForeignFutureResultI8 {
int8_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI8;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureResultI8
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16
typedef struct UniffiForeignFutureResultU16 {
uint16_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU16;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureResultU16
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16
typedef struct UniffiForeignFutureResultI16 {
int16_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI16;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureResultI16
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32
typedef struct UniffiForeignFutureResultU32 {
uint32_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU32;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureResultU32
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32
typedef struct UniffiForeignFutureResultI32 {
int32_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI32;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureResultI32
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64
typedef struct UniffiForeignFutureResultU64 {
uint64_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU64;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureResultU64
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64
typedef struct UniffiForeignFutureResultI64 {
int64_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI64;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureResultI64
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32
typedef struct UniffiForeignFutureResultF32 {
float returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultF32;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureResultF32
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64
typedef struct UniffiForeignFutureResultF64 {
double returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultF64;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureResultF64
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER
typedef struct UniffiForeignFutureResultRustBuffer {
RustBuffer returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultRustBuffer;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureResultRustBuffer
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID
typedef struct UniffiForeignFutureResultVoid {
RustCallStatus callStatus;
} UniffiForeignFutureResultVoid;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_ack_events_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint32_t uniffi_zx_document_ffi_fn_func_cleanup_stale_sessions_ffi(int64_t now_ms, int64_t max_age_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
void uniffi_zx_document_ffi_fn_func_clear_exported_events(uint32_t count, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_close_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor_from_search(RustBuffer material_id, RustBuffer result, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
RustBuffer uniffi_zx_document_ffi_fn_func_detect_material_type(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events_v2(uint32_t limit, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_extract_pdf_text_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_get_office_preview_config_ffi(RustBuffer material_type, uint64_t file_size, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
int8_t uniffi_zx_document_ffi_fn_func_is_office_type_ffi(RustBuffer material_type, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
uint32_t uniffi_zx_document_ffi_fn_func_mark_events_failed_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_MARKDOWN
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_MARKDOWN
RustBuffer uniffi_zx_document_ffi_fn_func_parse_markdown(RustBuffer content, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_TEXT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_TEXT
RustBuffer uniffi_zx_document_ffi_fn_func_parse_text(RustBuffer content, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_pause_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_heartbeat_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_marked_as_read_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_closed_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_opened_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_position_changed_v2(RustBuffer session_id, RustBuffer material_id, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
void uniffi_zx_document_ffi_fn_func_push_reading_event(RustBuffer event, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_chapters_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
RustBuffer uniffi_zx_document_ffi_fn_func_read_image_meta(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_pdf_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
RustBuffer uniffi_zx_document_ffi_fn_func_read_text_stats(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_reload_stale_events_v2(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_restore_position_from_anchor(RustBuffer anchor, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_resume_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_search_epub_chapters_ffi(RustBuffer chapter_ids, RustBuffer chapter_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
RustBuffer uniffi_zx_document_ffi_fn_func_search_markdown_blocks(RustBuffer blocks, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
RustBuffer uniffi_zx_document_ffi_fn_func_search_pdf_pages(RustBuffer page_numbers, RustBuffer page_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
RustBuffer uniffi_zx_document_ffi_fn_func_search_text_content(RustBuffer content, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
RustBuffer uniffi_zx_document_ffi_fn_func_start_reading_session_v2(RustBuffer material, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
void uniffi_zx_document_ffi_fn_func_update_reading_position(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_ALLOC
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_ALLOC
RustBuffer ffi_zx_document_ffi_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FROM_BYTES
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FROM_BYTES
RustBuffer ffi_zx_document_ffi_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FREE
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FREE
void ffi_zx_document_ffi_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_RESERVE
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_RESERVE
RustBuffer ffi_zx_document_ffi_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U8
void ffi_zx_document_ffi_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U8
void ffi_zx_document_ffi_rust_future_cancel_u8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U8
void ffi_zx_document_ffi_rust_future_free_u8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U8
uint8_t ffi_zx_document_ffi_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I8
void ffi_zx_document_ffi_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I8
void ffi_zx_document_ffi_rust_future_cancel_i8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I8
void ffi_zx_document_ffi_rust_future_free_i8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I8
int8_t ffi_zx_document_ffi_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U16
void ffi_zx_document_ffi_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U16
void ffi_zx_document_ffi_rust_future_cancel_u16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U16
void ffi_zx_document_ffi_rust_future_free_u16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U16
uint16_t ffi_zx_document_ffi_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I16
void ffi_zx_document_ffi_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I16
void ffi_zx_document_ffi_rust_future_cancel_i16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I16
void ffi_zx_document_ffi_rust_future_free_i16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I16
int16_t ffi_zx_document_ffi_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U32
void ffi_zx_document_ffi_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U32
void ffi_zx_document_ffi_rust_future_cancel_u32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U32
void ffi_zx_document_ffi_rust_future_free_u32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U32
uint32_t ffi_zx_document_ffi_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I32
void ffi_zx_document_ffi_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I32
void ffi_zx_document_ffi_rust_future_cancel_i32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I32
void ffi_zx_document_ffi_rust_future_free_i32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I32
int32_t ffi_zx_document_ffi_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U64
void ffi_zx_document_ffi_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U64
void ffi_zx_document_ffi_rust_future_cancel_u64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U64
void ffi_zx_document_ffi_rust_future_free_u64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U64
uint64_t ffi_zx_document_ffi_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I64
void ffi_zx_document_ffi_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I64
void ffi_zx_document_ffi_rust_future_cancel_i64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I64
void ffi_zx_document_ffi_rust_future_free_i64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I64
int64_t ffi_zx_document_ffi_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F32
void ffi_zx_document_ffi_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F32
void ffi_zx_document_ffi_rust_future_cancel_f32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F32
void ffi_zx_document_ffi_rust_future_free_f32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F32
float ffi_zx_document_ffi_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F64
void ffi_zx_document_ffi_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F64
void ffi_zx_document_ffi_rust_future_cancel_f64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F64
void ffi_zx_document_ffi_rust_future_free_f64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F64
double ffi_zx_document_ffi_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_RUST_BUFFER
void ffi_zx_document_ffi_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER
void ffi_zx_document_ffi_rust_future_cancel_rust_buffer(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_RUST_BUFFER
void ffi_zx_document_ffi_rust_future_free_rust_buffer(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER
RustBuffer ffi_zx_document_ffi_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_VOID
void ffi_zx_document_ffi_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_VOID
void ffi_zx_document_ffi_rust_future_cancel_void(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_VOID
void ffi_zx_document_ffi_rust_future_free_void(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
void ffi_zx_document_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_ack_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_cleanup_stale_sessions_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_clear_exported_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_close_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor_from_search(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_DETECT_MATERIAL_TYPE
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_DETECT_MATERIAL_TYPE
uint16_t uniffi_zx_document_ffi_checksum_func_detect_material_type(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_extract_pdf_text_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_get_office_preview_config_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_is_office_type_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_mark_events_failed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_MARKDOWN
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_MARKDOWN
uint16_t uniffi_zx_document_ffi_checksum_func_parse_markdown(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_TEXT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_TEXT
uint16_t uniffi_zx_document_ffi_checksum_func_parse_text(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_pause_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_heartbeat_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_marked_as_read_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_closed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_opened_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_position_changed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
uint16_t uniffi_zx_document_ffi_checksum_func_push_reading_event(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_chapters_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_metadata_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
uint16_t uniffi_zx_document_ffi_checksum_func_read_image_meta(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_pdf_metadata_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
uint16_t uniffi_zx_document_ffi_checksum_func_read_text_stats(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_reload_stale_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_restore_position_from_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_resume_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_search_epub_chapters_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
uint16_t uniffi_zx_document_ffi_checksum_func_search_markdown_blocks(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
uint16_t uniffi_zx_document_ffi_checksum_func_search_pdf_pages(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
uint16_t uniffi_zx_document_ffi_checksum_func_search_text_content(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_start_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_UPDATE_READING_POSITION
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_UPDATE_READING_POSITION
uint16_t uniffi_zx_document_ffi_checksum_func_update_reading_position(void
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_UNIFFI_CONTRACT_VERSION
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_UNIFFI_CONTRACT_VERSION
uint32_t ffi_zx_document_ffi_uniffi_contract_version(void
);
#endif

View File

@ -105,7 +105,8 @@ struct MaterialReaderView: View {
private let title: String
private let knowledgeBaseId: String?
// Event collector records reading events during the session
// V2 session manager (primary) + V1 collector (fallback)
private let sessionManager = ReadingRuntimeSessionManager.shared
private let collector = ReadingEventCollector.shared
private let positionStore = ReadingPositionStore.shared
@ -181,16 +182,11 @@ struct MaterialReaderView: View {
)
}
.onAppear {
// FIXME: collector calls Rust FFI with struct-passing (broken on ARM64 iOS)
// collector.open(materialId: vm.materialId)
openReadingSession()
hasRestoredPosition = false
}
.onDisappear {
// FIXME: collector calls Rust FFI with struct-passing (broken on ARM64 iOS)
// if let lastPos = collector.lastPosition {
// positionStore.save(materialId: vm.materialId, position: lastPos)
// }
// _ = collector.close(materialId: vm.materialId)
closeReadingSession()
}
.onChange(of: vm.loadingState) { _, newState in
if newState == .loaded, !hasRestoredPosition {
@ -229,7 +225,7 @@ struct MaterialReaderView: View {
}
ForEach(Array(vm.blocks.enumerated()), id: \.offset) { _, block in
DocumentBlockView(block: block)
.id(blockId(from: block))
.id(ReadingPositionAdapter.blockId(from: block))
}
}
.padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 100)
@ -259,9 +255,48 @@ struct MaterialReaderView: View {
}
}
// MARK: - Reading Session Lifecycle
private func openReadingSession() {
let context = ReadingMaterialContext(
readingTargetType: knowledgeBaseId != nil ? .knowledgeSource : .temporaryFile,
materialId: vm.materialId,
knowledgeBaseId: knowledgeBaseId,
title: title.isEmpty ? nil : title
)
// V2 primary
if let _ = try? sessionManager.openMaterial(context) {
print("[READER] V2 session opened — materialId=\(vm.materialId)")
return
}
// V1 fallback
print("[READER] V2 unavailable, falling back to V1")
collector.open(materialId: vm.materialId)
}
private func closeReadingSession() {
// V2 primary
if sessionManager.state == .active || sessionManager.state == .paused {
if let lastPos = sessionManager.lastPosition {
positionStore.save(materialId: vm.materialId, position: lastPos)
}
sessionManager.closeMaterial()
print("[READER] V2 session closed")
return
}
// V1 fallback
if let lastPos = collector.lastPosition {
positionStore.save(materialId: vm.materialId, position: lastPos)
}
_ = collector.close(materialId: vm.materialId)
print("[READER] V1 session closed")
}
/// Build a NoteAnchor from the current scroll position (for quick note).
private func buildAnchor() -> NoteAnchor? {
guard let pos = collector.lastPosition else { return nil }
let pos = sessionManager.lastPosition ?? collector.lastPosition
guard let pos else { return nil }
return createNoteAnchor(materialId: vm.materialId, position: pos)
}
@ -274,45 +309,34 @@ struct MaterialReaderView: View {
case .text(let lineNumber, _):
let idx = Int(lineNumber) - 1
if idx >= 0, idx < vm.blocks.count {
restoreBlockId = blockId(from: vm.blocks[idx])
restoreBlockId = ReadingPositionAdapter.blockId(from: vm.blocks[idx])
}
case .pdf, .image, .epub, .unknown:
break // PDF/image restoration needs platform-specific handling
}
}
/// Report current scroll position to the event collector.
/// Report current scroll position to the active session manager.
private func reportScrollPosition() {
guard !vm.blocks.isEmpty else { return }
let idx = max(0, min(vm.blocks.count - 1,
Int(scrollProgress * CGFloat(vm.blocks.count))))
let block = vm.blocks[idx]
let bId = blockId(from: block)
let sp = Float(scrollProgress)
let pos: ReadingPosition
switch vm.materialType {
case .markdown:
pos = .markdown(blockId: bId, scrollProgress: sp)
case .text:
pos = .text(lineNumber: UInt32(idx + 1), scrollProgress: sp)
default:
pos = .markdown(blockId: bId, scrollProgress: sp)
}
collector.updatePosition(materialId: vm.materialId, position: pos)
}
guard let pos = ReadingPositionAdapter.fromBlockScroll(
materialType: vm.materialType,
blocks: vm.blocks,
scrollProgress: sp,
blockIndex: idx
) else { return }
private func blockId(from block: DocumentBlock) -> String {
switch block {
case .heading(let id, _, _): return id
case .paragraph(let id, _): return id
case .list(let id, _, _): return id
case .codeBlock(let id, _, _): return id
case .quote(let id, _): return id
case .table(let id, _, _): return id
case .imageBlock(let id, _, _): return id
case .horizontalRule(let id): return id
// V2 primary
if sessionManager.state == .active {
sessionManager.updatePosition(pos)
} else {
// V1 fallback
collector.updatePosition(materialId: vm.materialId, position: pos)
}
}

View File

@ -123,11 +123,11 @@ struct QuickNoteSheet: View {
private var anchorDescription: String? {
guard let a = anchor else { return nil }
switch a {
case .markdownBlock(_, let blockId):
case .markdownBlock(_, let blockId, _):
return "block \(String(blockId.prefix(8)))"
case .textLine(_, let lineNumber):
case .textLine(_, let lineNumber, _):
return "\(lineNumber)"
case .pdfPage(_, let pageNumber):
case .pdfPage(_, let pageNumber, _):
return "\(pageNumber)"
default:
return nil
@ -138,12 +138,13 @@ struct QuickNoteSheet: View {
guard let a = anchor else { return (nil, nil, nil, nil) }
switch a {
case .material: return ("material", nil, nil, nil)
case .markdownBlock(_, let bid): return ("markdown", bid, nil, nil)
case .textLine(_, let ln): return ("text", nil, ln, nil)
case .pdfPage(_, let pn): return ("pdf", nil, nil, pn)
case .markdownBlock(_, let bid, _): return ("markdown", bid, nil, nil)
case .textLine(_, let ln, _): return ("text", nil, ln, nil)
case .pdfPage(_, let pn, _): return ("pdf", nil, nil, pn)
case .image: return ("image", nil, nil, nil)
case .epubChapter(_, let cid): return ("epub", cid, nil, nil)
case .epubChapter(_, let cid, _): return ("epub", cid, nil, nil)
case .knowledgeItem: return ("knowledge_item", nil, nil, nil)
case .searchResultAnchor: return ("search_result", nil, nil, nil)
}
}

View File

@ -1,16 +1,17 @@
import SwiftUI
// MARK: - Reading Event Collector
// MARK: - Reading Event Collector (V1 deprecated)
/// Collects reading events (MaterialOpened, MaterialClosed, PositionChanged,
/// Heartbeat, MarkedAsRead) into the Rust event buffer for cross-platform export.
/// V1 event collector using deprecated `push_reading_event` / `export_pending_events`.
/// Migrate to V2 `ReadingRuntimeSessionManager` which uses session-aware
/// `push_material_opened_v2` / `export_pending_events_v2` / `ack_events_v2`.
///
/// V1 is kept as a fallback for environments where V2 FFI is not available.
@MainActor
final class ReadingEventCollector {
static let shared = ReadingEventCollector()
/// The last known reading position (for save-on-exit).
private(set) var lastPosition: ReadingPosition?
private var activeMaterialId: String?
private var activeSeconds: UInt32 = 0
private var heartbeatTimer: Timer?
@ -18,10 +19,12 @@ final class ReadingEventCollector {
private init() {}
// MARK: - Public API
/// Call when a material is opened.
@available(*, deprecated, message: "Use ReadingRuntimeSessionManager.openMaterial() with V2 FFI")
func open(materialId: String) {
if #available(iOS 16, *) {
print("[ReadingEvent] ⚠️ V1 collector active — consider upgrading to V2 ReadingRuntimeSessionManager")
}
flush() // commit any prior session first
activeMaterialId = materialId
activeSeconds = 0

View File

@ -0,0 +1,58 @@
import Foundation
// MARK: - ReadingEventMapper
/// Maps Rust `ReadingEventV2` to API `ReadingEventUploadItem`,
/// supplementing iOS-specific fields (readingTargetType, platform, appVersion, timezone).
enum ReadingEventMapper {
/// Rust ReadingEventTypeV2 API snake_case string.
static func apiEventType(_ eventType: ReadingEventTypeV2) -> String {
switch eventType {
case .materialOpened: return "material_opened"
case .materialClosed: return "material_closed"
case .positionChanged: return "position_changed"
case .heartbeat: return "heartbeat"
case .markedAsRead: return "marked_as_read"
}
}
/// Batch map: Rust events API upload items. Skips events without context.
static func map(
rustEvents: [ReadingEventV2],
contexts: [String: ReadingMaterialContext],
appVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
) -> [ReadingEventUploadItem] {
return rustEvents.compactMap { event in
let ctx = contexts[event.materialId]
return mapOne(event: event, context: ctx, appVersion: appVersion)
}
}
/// Map a single event. Returns nil if no context found (event will be dropped).
static func mapOne(
event: ReadingEventV2,
context: ReadingMaterialContext?,
appVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
) -> ReadingEventUploadItem? {
guard let ctx = context else {
print("[Mapper] ⚠️ No context for materialId=\(event.materialId), skipping event \(event.eventId)")
return nil
}
return ReadingEventUploadItem(
eventId: event.eventId,
clientSessionId: event.clientSessionId,
materialId: event.materialId,
eventType: apiEventType(event.eventType),
position: event.position,
activeSecondsDelta: Int(event.activeSecondsDelta),
clientTimestampMs: event.timestampMs,
sequence: event.sequence,
readingTargetType: ctx.readingTargetType.rawValue,
platform: "ios",
appVersion: appVersion,
clientTimezoneOffsetMinutes: TimeZone.current.secondsFromGMT() / 60
)
}
}

View File

@ -0,0 +1,245 @@
import Foundation
// MARK: - Queue Item
struct ReadingEventQueueItem: Codable, Equatable, Identifiable {
let id: String // UUID
let eventId: String // Rust ReadingEventV2.eventId
let payload: ReadingEventUploadItem
var status: QueueItemStatus
var retryCount: Int
var lastErrorCode: String?
var lastTriedAt: Date?
let createdAt: Date
var updatedAt: Date
enum QueueItemStatus: String, Codable {
case pending
case uploading
case failed // retryable
case failedPermanent // will not retry
}
}
// MARK: - Upload Queue
/// Local queue for reading event upload items. Persisted to JSON file.
/// Coordinates with Rust buffer: export enqueue API upload ack/markFailed.
@MainActor
final class ReadingEventUploadQueue {
static let shared = ReadingEventUploadQueue()
private var items: [ReadingEventQueueItem] = []
private let maxRetryCount = 3
private let storageURL: URL
var pendingCount: Int { items.filter { $0.status == .pending }.count }
var failedCount: Int { items.filter { $0.status == .failed }.count }
private init() {
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
storageURL = docs.appendingPathComponent("reading_event_queue.json")
loadFromDisk()
}
// MARK: - Public API
/// Enqueue a batch of upload items. Deduplicates by eventId.
func enqueue(_ uploadItems: [ReadingEventUploadItem]) {
let existingIds = Set(items.map(\.eventId))
let now = Date()
let newItems = uploadItems
.filter { !existingIds.contains($0.eventId) }
.map { item in
ReadingEventQueueItem(
id: UUID().uuidString,
eventId: item.eventId,
payload: item,
status: .pending,
retryCount: 0,
lastErrorCode: nil,
lastTriedAt: nil,
createdAt: now,
updatedAt: now
)
}
items.append(contentsOf: newItems)
saveToDisk()
}
/// Fetch a batch of pending items (max `limit`).
func fetchPendingBatch(limit: Int = 100) -> [ReadingEventQueueItem] {
return Array(items.filter { $0.status == .pending }.prefix(limit))
}
/// Mark items as uploaded (will be removed from queue).
func markUploaded(ids: [String]) {
items.removeAll { ids.contains($0.id) }
saveToDisk()
}
/// Mark items for retry (increment retry count, set failed status).
func markRetry(ids: [String], errorCode: String? = nil) {
let now = Date()
for i in items.indices {
guard ids.contains(items[i].id) else { continue }
items[i].retryCount += 1
items[i].lastErrorCode = errorCode
items[i].lastTriedAt = now
items[i].updatedAt = now
items[i].status = items[i].retryCount >= maxRetryCount ? .failedPermanent : .failed
}
saveToDisk()
}
/// Mark items as permanently failed (will not retry).
func markPermanentFailed(ids: [String], errorCode: String? = nil) {
let now = Date()
for i in items.indices {
guard ids.contains(items[i].id) else { continue }
items[i].status = .failedPermanent
items[i].lastErrorCode = errorCode
items[i].lastTriedAt = now
items[i].updatedAt = now
}
saveToDisk()
}
/// Retry all failed items (move back to pending).
func retryFailed() {
let now = Date()
for i in items.indices {
guard items[i].status == .failed else { continue }
items[i].status = .pending
items[i].updatedAt = now
}
saveToDisk()
}
/// Remove all permanently failed items.
func clearPermanentFailed() {
items.removeAll { $0.status == .failedPermanent }
saveToDisk()
}
/// Remove all items.
func clearAll() {
items.removeAll()
saveToDisk()
}
// MARK: - Persistence
private func saveToDisk() {
do {
let data = try JSONEncoder().encode(items)
try data.write(to: storageURL, options: .atomic)
} catch {
print("[UploadQueue] Failed to save: \(error)")
}
}
private func loadFromDisk() {
guard FileManager.default.fileExists(atPath: storageURL.path) else {
items = []
return
}
do {
let data = try Data(contentsOf: storageURL)
items = try JSONDecoder().decode([ReadingEventQueueItem].self, from: data)
// Recover stuck uploading items (crash recovery)
let now = Date()
for i in items.indices where items[i].status == .uploading {
items[i].status = .pending
items[i].updatedAt = now
}
print("[UploadQueue] Loaded \(items.count) items from disk")
} catch {
print("[UploadQueue] Failed to load: \(error)")
items = []
}
}
}
// MARK: - Upload Pipeline
/// Orchestrates: Rust export mapper enqueue API upload ack/markFailed.
@MainActor
final class ReadingEventUploadPipeline {
static let shared = ReadingEventUploadPipeline()
private let queue = ReadingEventUploadQueue.shared
private let adapter: ReadingRuntimeAdapter = RustReadingRuntimeAdapter()
private let readingAPI = ReadingAPIService.shared
private init() {}
/// Full pipeline: export from Rust enqueue.
func exportAndEnqueue(contexts: [String: ReadingMaterialContext]) {
let rustEvents = adapter.exportEvents(limit: 100, timestampMs: nowMs())
guard !rustEvents.isEmpty else { return }
let uploadItems = ReadingEventMapper.map(rustEvents: rustEvents, contexts: contexts)
guard !uploadItems.isEmpty else { return }
queue.enqueue(uploadItems)
print("[UploadPipeline] Exported \(rustEvents.count) events, enqueued \(uploadItems.count)")
}
/// Flush pending items to API.
func flush() async {
let batch = queue.fetchPendingBatch(limit: 100)
guard !batch.isEmpty else { return }
queue.markUploading(ids: batch.map(\.id))
do {
let response = try await readingAPI.uploadReadingEvents(batch.map(\.payload))
// Ack successful events in Rust
let ackedIds = batch.map(\.eventId)
_ = adapter.ackEvents(ackedIds)
queue.markUploaded(ids: batch.map(\.id))
print("[UploadPipeline] Flushed \(response.processed) events")
} catch let error as APIError {
let errorCode = error.errorCode ?? "NETWORK_ERROR"
queue.markRetry(ids: batch.map(\.id), errorCode: errorCode)
_ = adapter.markFailed(batch.map(\.eventId))
print("[UploadPipeline] Flush failed: \(errorCode)")
} catch {
queue.markRetry(ids: batch.map(\.id), errorCode: "UNKNOWN")
_ = adapter.markFailed(batch.map(\.eventId))
}
}
/// Reload on app launch: reload stale Rust events, enqueue, retry failed.
func reloadOnLaunch(contexts: [String: ReadingMaterialContext]) {
_ = adapter.reloadStaleEvents()
_ = adapter.cleanupStaleSessions(nowMs: nowMs(), maxAgeMs: 30 * 60 * 1000)
exportAndEnqueue(contexts: contexts)
queue.retryFailed()
}
// MARK: - Helpers
private func nowMs() -> Int64 {
Int64(Date().timeIntervalSince1970 * 1000)
}
}
// MARK: - Queue Helpers
extension ReadingEventUploadQueue {
func markUploading(ids: [String]) {
let now = Date()
for i in items.indices {
guard ids.contains(items[i].id) else { continue }
items[i].status = .uploading
items[i].lastTriedAt = now
items[i].updatedAt = now
}
saveToDisk()
}
}

View File

@ -0,0 +1,97 @@
import Foundation
// MARK: - Reading Position Adapter
/// Generates the correct `ReadingPosition` variant for each material type.
/// Handles progress clamping (0~1) and camelCase field mapping.
struct ReadingPositionAdapter {
/// Build a ReadingPosition from the current viewport state.
static func buildPosition(
materialType: MaterialType,
blockId: String? = nil,
scrollProgress: Float? = nil,
lineNumber: UInt32? = nil,
pageNumber: UInt32? = nil,
pageProgress: Float? = nil,
overallProgress: Float? = nil,
chapterId: String? = nil,
chapterProgress: Float? = nil,
zoomScale: Float? = nil,
offsetX: Float? = nil,
offsetY: Float? = nil
) -> ReadingPosition? {
switch materialType {
case .markdown:
guard let bid = blockId else { return nil }
return .markdown(blockId: bid, scrollProgress: clamp(scrollProgress ?? 0))
case .text:
return .text(lineNumber: lineNumber ?? 1, scrollProgress: clamp(scrollProgress ?? 0))
case .pdf:
return .pdf(
pageNumber: pageNumber ?? 1,
pageProgress: clamp(pageProgress ?? 0),
overallProgress: clamp(overallProgress ?? 0)
)
case .image:
return .image(
zoomScale: zoomScale ?? 1.0,
offsetX: offsetX ?? 0,
offsetY: offsetY ?? 0
)
case .epub:
guard let cid = chapterId else { return nil }
return .epub(
chapterId: cid,
chapterProgress: clamp(chapterProgress ?? 0),
overallProgress: clamp(overallProgress ?? 0)
)
case .word, .excel, .powerPoint, .unknown:
return .unknown
}
}
/// Build a Markdown position from block index + scroll progress.
static func fromBlockScroll(
materialType: MaterialType,
blocks: [DocumentBlock],
scrollProgress: Float,
blockIndex: Int
) -> ReadingPosition? {
guard !blocks.isEmpty, blockIndex >= 0, blockIndex < blocks.count else { return nil }
let bId = blockId(from: blocks[blockIndex])
return buildPosition(
materialType: materialType,
blockId: bId,
scrollProgress: clamp(scrollProgress),
lineNumber: UInt32(blockIndex + 1)
)
}
// MARK: - Helpers
private static func clamp(_ value: Float) -> Float {
if value.isNaN || value < 0 { return 0 }
if value > 1 { return 1 }
return value
}
static func blockId(from block: DocumentBlock) -> String {
switch block {
case .heading(let id, _, _): return id
case .paragraph(let id, _): return id
case .list(let id, _, _): return id
case .codeBlock(let id, _, _): return id
case .quote(let id, _): return id
case .table(let id, _, _): return id
case .imageBlock(let id, _, _): return id
case .horizontalRule(let id): return id
}
}
}

View File

@ -0,0 +1,175 @@
import Foundation
// MARK: - Session State
enum ReadingSessionState {
case idle
case active
case paused
case closed
}
// MARK: - ReadingRuntimeSessionManager
/// V2 session-aware manager using the ReadingRuntimeAdapter protocol.
/// Replaces the deprecated V1 ReadingEventCollector.
@MainActor
final class ReadingRuntimeSessionManager {
static let shared = ReadingRuntimeSessionManager()
// MARK: - Public State
private(set) var state: ReadingSessionState = .idle
private(set) var activeSessionId: String?
private(set) var activeContext: ReadingMaterialContext?
private(set) var lastPosition: ReadingPosition?
// MARK: - Dependencies
var adapter: ReadingRuntimeAdapter = RustReadingRuntimeAdapter()
// MARK: - Private
private var heartbeatTimer: Timer?
private var positionDebounceTask: Task<Void, Never>?
private var lastHeartbeatAtMs: Int64 = 0
private init() {}
// MARK: - Session Lifecycle
/// Start a reading session for a material.
/// - Throws if a session is already active (must close first).
func openMaterial(_ context: ReadingMaterialContext) throws {
guard state == .idle || state == .closed else {
throw SessionError.alreadyActive
}
let material = ReadingMaterialRef(materialId: context.materialId)
let now = nowMs()
let sessionId = try adapter.startSession(material: material, timestampMs: now)
activeSessionId = sessionId
activeContext = context
state = .active
lastPosition = nil
lastHeartbeatAtMs = now
// Push MaterialOpened
_ = try adapter.pushOpened(sessionId: sessionId, materialId: context.materialId, timestampMs: now)
startHeartbeat()
}
/// Close the current session. Can be called from any non-idle state.
func closeMaterial() {
guard state != .idle, let sessionId = activeSessionId, let ctx = activeContext else { return }
stopHeartbeat()
flushPosition()
let now = nowMs()
let sinceLastTick = lastHeartbeatAtMs > 0 ? UInt32((now - lastHeartbeatAtMs) / 1000) : 0
_ = try? adapter.pushClosed(sessionId: sessionId, materialId: ctx.materialId, delta: sinceLastTick, timestampMs: now)
_ = try? adapter.closeSession(sessionId)
activeSessionId = nil
activeContext = nil
lastPosition = nil
state = .closed
}
func pause() {
guard state == .active, let sessionId = activeSessionId else { return }
stopHeartbeat()
flushPosition()
_ = try? adapter.pauseSession(sessionId)
state = .paused
}
func resume() {
guard state == .paused, let sessionId = activeSessionId else { return }
_ = try? adapter.resumeSession(sessionId)
state = .active
startHeartbeat()
}
// MARK: - Position
func updatePosition(_ position: ReadingPosition) {
guard state == .active, let sessionId = activeSessionId, let ctx = activeContext else { return }
lastPosition = position
positionDebounceTask?.cancel()
positionDebounceTask = Task { [weak self] in
try? await Task.sleep(nanoseconds: 2_000_000_000)
guard let self, !Task.isCancelled, let pos = self.lastPosition,
let sid = self.activeSessionId, let c = self.activeContext else { return }
_ = try? self.adapter.pushPositionChanged(
sessionId: sid, materialId: c.materialId,
position: pos, timestampMs: self.nowMs()
)
}
}
func markAsRead() {
guard state == .active, let sessionId = activeSessionId, let ctx = activeContext else { return }
_ = try? adapter.pushMarkedAsRead(sessionId: sessionId, materialId: ctx.materialId, timestampMs: nowMs())
}
// MARK: - Heartbeat
private func startHeartbeat(interval: TimeInterval = 15) {
stopHeartbeat()
heartbeatTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
Task { @MainActor [weak self] in
guard let self,
self.state == .active,
let sid = self.activeSessionId,
let ctx = self.activeContext else { return }
let now = self.nowMs()
_ = try? self.adapter.pushHeartbeat(
sessionId: sid, materialId: ctx.materialId,
delta: UInt32(interval), position: self.lastPosition,
timestampMs: now
)
self.lastHeartbeatAtMs = now
}
}
}
private func stopHeartbeat() {
heartbeatTimer?.invalidate()
heartbeatTimer = nil
}
private func flushPosition() {
positionDebounceTask?.cancel()
// Force-send last position before clearing
if let pos = lastPosition, let sid = activeSessionId, let ctx = activeContext {
_ = try? adapter.pushPositionChanged(
sessionId: sid, materialId: ctx.materialId,
position: pos, timestampMs: nowMs()
)
}
positionDebounceTask = nil
}
// MARK: - Helpers
private func nowMs() -> Int64 {
Int64(Date().timeIntervalSince1970 * 1000)
}
}
// MARK: - Errors
enum SessionError: Error, LocalizedError {
case alreadyActive
var errorDescription: String? {
switch self {
case .alreadyActive: return "A reading session is already active. Close it first."
}
}
}

View File

@ -0,0 +1,315 @@
# iOS 学习信息采集 总设计
> IOS-INFO-000 | v1.0 | 2026-06-08
## 1. 概述
iOS 端学习信息采集系统的目标:从 MaterialReader 生命周期的各个环节采集阅读行为数据,经 Rust document runtime 生成事件,再经本地上传队列可靠投递到 API 服务端。
### 职责边界
```
┌─────────────────────────────────────────────────────────┐
│ iOS App │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Reader View │──▶│ Rust Runtime │──▶│ UploadQueue │ │
│ │ (Lifecycle) │ │ (Event V2) │ │ (ack/retry) │ │
│ └─────────────┘ └──────────────┘ └──────┬──────┘ │
│ │ │
│ ┌─────────────┐ ┌──────────────┐ │ │
│ │ Continue │ │ Progress │ │ │
│ │ Learning │ │ Restore │ │ │
│ └─────────────┘ └──────────────┘ │ │
└───────────────────────────────────────────────┼────────┘
POST /learning/reading-events/batch
┌───────▼────────┐
│ API Server │
│ (M8) │
└────────────────┘
```
**Rust 负责:** 事件生成UUID/sequence/delta、position normalize、session 管理、buffer 状态机
**iOS 负责:** 生命周期触发、readingTargetType 补充、上传队列、ack/reload、位置缓存、UI 联动
## 2. V1 → V2 迁移
### 当前状态V1
```swift
// ReadingEventCollector.swift — V1 API
pushReadingEvent(event: ReadingEvent.materialOpened(...))
exportPendingEvents() // → [ReadingEvent]
clearExportedEvents(count: n) // 无 ack无重试
```
### 致命问题F1-F4 审查发现)
| ID | 问题 | 影响 |
|----|------|------|
| F1 | `open/close` 被注释 | 事件链路断裂,无数据 |
| F2 | 无上传队列 | 事件丢失无重试 |
| F3 | 继续学习硬编码 | 无法动态更新 |
| F4 | 位置恢复仅本地 | 跨设备无法同步 |
### 目标状态V2
```swift
// ReadingRuntimeSessionManager.swift — V2 API
let sid = try startReadingSessionV2(material: material, timestampMs: now)
let ev = try pushMaterialOpenedV2(sessionId: sid, materialId: id, timestampMs: now)
let batch = exportPendingEventsV2(limit: 100, timestampMs: now)
ackEventsV2(eventIds: ids) // 成功后确认删除
markEventsFailedV2(eventIds: ids) // 失败后标记重试
reloadStaleEventsV2() // App 启动恢复
```
### 迁移对照
| V1 API (废弃) | V2 API (使用) |
|--------------|-------------|
| `pushReadingEvent(ReadingEvent.materialOpened)` | `pushMaterialOpenedV2(sessionId, materialId, ts)` |
| `pushReadingEvent(ReadingEvent.heartbeat)` | `pushHeartbeatV2(sessionId, materialId, delta, pos, ts)` |
| `exportPendingEvents()` | `exportPendingEventsV2(limit, ts)` |
| `clearExportedEvents(count)` | `ackEventsV2(eventIds)` |
| — | `markEventsFailedV2(eventIds)` |
| — | `reloadStaleEventsV2()` |
| `activeSeconds` (累计) | `activeSecondsDelta` (增量) |
## 3. 核心类型
### ReadingMaterialContext
```swift
struct ReadingMaterialContext {
let materialId: String
let readingTargetType: ReadingTargetType // knowledge_source | temporary_file
let title: String?
let knowledgeBaseId: String?
let materialType: MaterialType // Rust MaterialType
let previewMode: PreviewMode // Rust PreviewMode
}
enum ReadingTargetType: String {
case knowledgeSource = "knowledge_source"
case temporaryFile = "temporary_file"
}
```
### ReadingRuntimeAdapter 协议
```swift
protocol ReadingRuntimeAdapter {
// Session
func startSession(material: ReadingMaterialRef, timestampMs: Int64) throws -> String
func closeSession(_ sessionId: String) throws
func pauseSession(_ sessionId: String) throws
func resumeSession(_ sessionId: String) throws
// Events
func pushOpened(sessionId: String, materialId: String, timestampMs: Int64) throws -> ReadingEventV2
func pushClosed(sessionId: String, materialId: String, delta: UInt32, timestampMs: Int64) throws -> ReadingEventV2
func pushPositionChanged(sessionId: String, materialId: String, position: ReadingPosition, timestampMs: Int64) throws -> ReadingEventV2
func pushHeartbeat(sessionId: String, materialId: String, delta: UInt32, position: ReadingPosition?, timestampMs: Int64) throws -> ReadingEventV2
func pushMarkedAsRead(sessionId: String, materialId: String, timestampMs: Int64) throws -> ReadingEventV2
// Buffer
func exportEvents(limit: UInt32, timestampMs: Int64) -> [ReadingEventV2]
func ackEvents(_ eventIds: [String]) -> UInt32
func markFailed(_ eventIds: [String]) -> UInt32
func reloadStale() -> UInt32
func cleanupStaleSessions(nowMs: Int64, maxAgeMs: Int64) -> UInt32
}
```
### ReadingEventUploadItem
```swift
struct ReadingEventUploadItem: Codable {
// From Rust ReadingEventV2
let eventId: String
let clientSessionId: String
let materialId: String
let eventType: String
let position: ReadingPosition?
let activeSecondsDelta: Int
let clientTimestampMs: Int64
let sequence: UInt64
// iOS supplements
let readingTargetType: String
let platform: String = "ios"
let appVersion: String
let clientTimezoneOffsetMinutes: Int
}
```
## 4. 架构ReadingRuntimeSessionManager
```swift
@MainActor
final class ReadingRuntimeSessionManager {
static let shared = ReadingRuntimeSessionManager()
private var activeSessionId: String?
private var context: ReadingMaterialContext?
private var heartbeatTimer: Timer?
private var lastPosition: ReadingPosition?
// MARK: - Lifecycle
func openMaterial(_ ctx: ReadingMaterialContext)
func closeMaterial()
func appDidEnterBackground()
func appWillEnterForeground()
// MARK: - Position
func updatePosition(_ pos: ReadingPosition) // debounced
func markAsRead()
// MARK: - Heartbeat
func startHeartbeat(interval: TimeInterval = 15)
func stopHeartbeat()
}
```
## 5. 上传队列ReadingEventUploadQueue
### 状态机
```
Rust export
LocalQueue (Swift array, persisted to disk)
POST /learning/reading-events/batch
┌─────┴─────┐
▼ ▼
成功(200) 失败/网络错误
│ │
▼ ▼
ackEvents markFailed
(Rust delete) (Rust retry mark)
│ │
▼ ▼
remove from keep in queue
local queue for next attempt
```
### UploadQueue 核心方法
```swift
final class ReadingEventUploadQueue {
func enqueue(_ items: [ReadingEventUploadItem])
func flush() async -> FlushResult // called on timer + app background
func retryFailed() async -> FlushResult // called on network recovery
func reloadOnLaunch() // reloadStaleEventsV2 + load persisted
}
struct FlushResult {
let uploaded: Int
let failed: Int
let warnings: [String]
}
```
## 6. 关键时机
| 时机 | 触发方 | 动作 |
|------|--------|------|
| MaterialReader onAppear | SwiftUI | `openMaterial(ctx)` |
| MaterialReader onDisappear | SwiftUI | `closeMaterial()` |
| Scroll/page change | Reader View | `updatePosition(pos)` |
| Tap "Mark as Read" | UI Button | `markAsRead()` |
| App → background | ScenePhase | `flush()` + stop heartbeat |
| App → foreground | ScenePhase | `reloadStale()` + `cleanupStaleSessions()` |
| Network recovery | NWPathMonitor | `retryFailed()` |
| Every 30s (timer) | UploadScheduler | `flush()` |
| On launch | App init | `reloadOnLaunch()` |
## 7. 继续学习
### 当前F3 硬编码)
```swift
// 硬编码"推荐继续阅读"文案,无数据源
```
### 目标
```swift
struct ContinueLearningService {
func fetch() async -> ContinueLearningItem? {
let resp = try await api.get("/learning/continue")
switch resp.type {
case "knowledge_source", "temporary_file":
return ContinueLearningItem(
materialId: resp.materialId,
title: resp.title ?? "Untitled",
lastPosition: resp.lastPosition,
lastProgress: resp.lastProgress
)
case "none":
return nil
default:
return nil
}
}
}
```
## 8. 阅读位置恢复
### 当前F4 仅本地)
```swift
// ReadingPositionStore: UserDefaults only
```
### 目标
```swift
struct ReadingPositionRestoreService {
func restore(for materialId: String, targetType: ReadingTargetType) async -> ReadingPosition? {
// 1. Try API: GET /materials/:id/reading-progress
if let remote = try? await api.getProgress(materialId, targetType),
remote.status != "not_started",
let pos = remote.lastPosition {
return pos
}
// 2. Fallback: local cache (ReadingPositionStore)
return ReadingPositionStore.shared.get(for: materialId)
}
}
```
## 9. 分析页对齐
| API | iOS 界面 |
|-----|---------|
| GET /learning/summary | AnalysisHomeView 顶部卡片 |
| GET /learning/trend?days=7 | 趋势曲线 |
| GET /activity/heatmap?days=365 | 热力图 |
## 10. 错误处理
| 错误码 | iOS 动作 |
|--------|---------|
| MATERIAL_ACCESS_DENIED | 不重试从队列移除toast 提示 |
| TEMPORARY_MATERIAL_EXPIRED | 从队列移除 |
| DUPLICATE_EVENT | 直接 ack 移除 |
| SOURCE_DELETED | toast 提示,从队列移除 |
| INVALID_* | 从队列移除,记录日志 |
| 网络错误 | 保留队列,等网络恢复后 retry |
| 5xx | 指数退避重试1s/2s/4s/8s max 3 retry |
## 11. 离线策略
- 事件始终写入 Rust buffer + 本地队列
- 无网络时 export 继续执行Rust buffer 正常upload 跳过
- 网络恢复时 `reloadStaleEventsV2()` + `flush()`
- 本地队列持久化到文件JSONApp 重启可恢复