feat: #24 公开库发现 + 订阅管理

- KnowledgeBaseService 新增 discover/subscribe/unsubscribe API
- DiscoverView: 浏览公开库 + 订阅/取消订阅
- Route.discoverPublic + ProfileView 入口

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-06 19:36:34 +08:00
parent 9e1a8982a3
commit 5fe29a7d8c
17 changed files with 3332 additions and 1 deletions

View File

@ -255,13 +255,26 @@
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly; ENABLE_USER_SELECTED_FILES = readonly;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/AIStudyApp/Core/Services",
);
INFOPLIST_FILE = Info.plist; INFOPLIST_FILE = Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "知习"; INFOPLIST_KEY_CFBundleDisplayName = "知习";
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/AIStudyApp/Core/ZxDocumentRuntime.xcframework/ios-arm64-simulator",
);
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = (
"$(inherited)",
"$(PROJECT_DIR)/AIStudyApp/Core/ZxDocumentRuntime.xcframework/ios-arm64",
);
MACOSX_DEPLOYMENT_TARGET = 26.4; MACOSX_DEPLOYMENT_TARGET = 26.4;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
OTHER_LINKER_FLAGS = "$(inherited) -lzx_document_ffi";
PRODUCT_BUNDLE_IDENTIFIER = cloud.longde.AIStudyApp; PRODUCT_BUNDLE_IDENTIFIER = cloud.longde.AIStudyApp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@ -271,6 +284,7 @@
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INCLUDE_PATHS = "$(inherited) $(PROJECT_DIR)/AIStudyApp/Core/Services";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7"; TARGETED_DEVICE_FAMILY = "1,2,7";
@ -291,13 +305,26 @@
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly; ENABLE_USER_SELECTED_FILES = readonly;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/AIStudyApp/Core/Services",
);
INFOPLIST_FILE = Info.plist; INFOPLIST_FILE = Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "知习"; INFOPLIST_KEY_CFBundleDisplayName = "知习";
IPHONEOS_DEPLOYMENT_TARGET = 17.6; IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/AIStudyApp/Core/ZxDocumentRuntime.xcframework/ios-arm64-simulator",
);
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = (
"$(inherited)",
"$(PROJECT_DIR)/AIStudyApp/Core/ZxDocumentRuntime.xcframework/ios-arm64",
);
MACOSX_DEPLOYMENT_TARGET = 26.4; MACOSX_DEPLOYMENT_TARGET = 26.4;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
OTHER_LINKER_FLAGS = "$(inherited) -lzx_document_ffi";
PRODUCT_BUNDLE_IDENTIFIER = cloud.longde.AIStudyApp; PRODUCT_BUNDLE_IDENTIFIER = cloud.longde.AIStudyApp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@ -307,6 +334,7 @@
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_INCLUDE_PATHS = "$(inherited) $(PROJECT_DIR)/AIStudyApp/Core/Services";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7"; TARGETED_DEVICE_FAMILY = "1,2,7";

View File

@ -12,6 +12,7 @@ enum Route: Hashable {
// Library // Library
case librarySearch case librarySearch
case libraryDetail(knowledgeBaseId: String) case libraryDetail(knowledgeBaseId: String)
case discoverPublic
case libraryImport case libraryImport
case libraryCreate case libraryCreate
case addKnowledge(knowledgeBaseId: String) case addKnowledge(knowledgeBaseId: String)
@ -54,6 +55,7 @@ extension Route {
case .reviewCard: "ReviewCardView" case .reviewCard: "ReviewCardView"
case .librarySearch: "LibrarySearchView" case .librarySearch: "LibrarySearchView"
case .libraryDetail: "LibraryDetailPage" case .libraryDetail: "LibraryDetailPage"
case .discoverPublic: "DiscoverView"
case .libraryImport: "ImportPage" case .libraryImport: "ImportPage"
case .libraryCreate: "CreateLibraryPage" case .libraryCreate: "CreateLibraryPage"
case .addKnowledge: "AddKnowledgePage" case .addKnowledge: "AddKnowledgePage"
@ -89,6 +91,7 @@ extension Route {
case .librarySearch: AnyView(LibrarySearchView()) case .librarySearch: AnyView(LibrarySearchView())
case .libraryDetail(let id): AnyView(LibraryDetailPage(knowledgeBaseId: id)) case .libraryDetail(let id): AnyView(LibraryDetailPage(knowledgeBaseId: id))
case .discoverPublic: AnyView(DiscoverView())
case .libraryImport: AnyView(ImportPage()) case .libraryImport: AnyView(ImportPage())
case .libraryCreate: AnyView(CreateLibraryPage()) case .libraryCreate: AnyView(CreateLibraryPage())
case .addKnowledge(let id): AnyView(AddKnowledgePage(knowledgeBaseId: id)) case .addKnowledge(let id): AnyView(AddKnowledgePage(knowledgeBaseId: id))

View File

@ -21,6 +21,10 @@ actor APIClient {
self.token = token self.token = token
} }
func getToken() -> String? {
return token
}
// MARK: - Generic request // MARK: - Generic request
func request<T: Decodable>( func request<T: Decodable>(

View File

@ -137,6 +137,22 @@ class KnowledgeBaseService {
]) ])
} }
func discover(page: Int = 1, limit: Int = 20) async throws -> [KnowledgeBase] {
return try await client.request("/knowledge-bases", queryItems: [
URLQueryItem(name: "visibility", value: "public"),
URLQueryItem(name: "page", value: String(page)),
URLQueryItem(name: "limit", value: String(limit)),
])
}
func subscribe(kbId: String) async throws -> GenericSuccessResponse {
return try await client.request("/knowledge-bases/\(kbId)/subscribe", method: "POST")
}
func unsubscribe(kbId: String) async throws -> GenericSuccessResponse {
return try await client.request("/knowledge-bases/\(kbId)/subscribe", method: "DELETE")
}
func delete(id: String) async throws -> GenericSuccessResponse { func delete(id: String) async throws -> GenericSuccessResponse {
return try await client.request("/knowledge-bases/\(id)", method: "DELETE") return try await client.request("/knowledge-bases/\(id)", method: "DELETE")
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
namespace zx_document {};
[Error]
enum DocumentError {
"FileNotFound",
"UnsupportedFormat",
"ParseError",
"InvalidEncoding",
"IoError",
};
[Enum]
interface MaterialType {
Markdown();
Text();
Pdf();
Image();
Epub();
Word();
Excel();
PowerPoint();
Unknown();
};
[Enum]
interface PreviewMode {
NativeReader();
PlatformPreview();
ExternalOpen();
Unsupported();
};
dictionary DocumentInfo {
string material_id;
string title;
MaterialType material_type;
PreviewMode preview_mode;
u64 file_size;
u32? page_count;
u32? word_count;
string? created_at;
};
[Enum]
interface ReadingPosition {
Markdown(string block_id, f32 scroll_progress);
Text(u32 line_number, f32 scroll_progress);
Pdf(u32 page_number, f32 page_progress, f32 overall_progress);
Image(f32 zoom_scale, f32 offset_x, f32 offset_y);
Epub(string chapter_id, f32 chapter_progress, f32 overall_progress);
Unknown();
};
[Enum]
interface ReadingEvent {
MaterialOpened(string material_id, i64 timestamp_ms);
MaterialClosed(string material_id, i64 timestamp_ms, u32 active_seconds);
PositionChanged(string material_id, ReadingPosition position, i64 timestamp_ms);
Heartbeat(string material_id, u32 active_seconds, ReadingPosition? position, i64 timestamp_ms);
MarkedAsRead(string material_id, i64 timestamp_ms);
};
[Enum]
interface NoteAnchor {
Material(string material_id);
MarkdownBlock(string material_id, string block_id);
TextLine(string material_id, u32 line_number);
PdfPage(string material_id, u32 page_number);
Image(string material_id);
EpubChapter(string material_id, string chapter_id);
KnowledgeItem(string knowledge_item_id);
};
dictionary ImageMeta {
u32 width;
u32 height;
string format;
u64 file_size;
};
dictionary SearchResult {
string block_id;
u32? line_number;
string snippet;
u64 match_start;
u64 match_end;
};
dictionary TextStats {
u32 line_count;
u32 word_count;
};
[Enum]
interface DocumentBlock {
Heading(string id, u8 level, string text);
Paragraph(string id, string text);
List(string id, boolean ordered, sequence<string> items);
CodeBlock(string id, string? language, string code);
Quote(string id, string text);
Table(string id, sequence<string> headers, sequence<sequence<string>> rows);
ImageBlock(string id, string src, string? alt);
HorizontalRule(string id);
};

View File

@ -0,0 +1,645 @@
// 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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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

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

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AvailableLibraries</key>
<array>
<dict>
<key>BinaryPath</key>
<string>libzx_document_ffi.a</string>
<key>LibraryIdentifier</key>
<string>ios-arm64-simulator</string>
<key>LibraryPath</key>
<string>libzx_document_ffi.a</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>libzx_document_ffi.a</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>libzx_document_ffi.a</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
</dict>
</array>
<key>CFBundlePackageType</key>
<string>XFWK</string>
<key>XCFrameworkFormatVersion</key>
<string>1.0</string>
</dict>
</plist>

View File

@ -0,0 +1,72 @@
import SwiftUI
struct DiscoverView: View {
@State private var kbs: [KnowledgeBase] = []
@State private var isLoading = false
@State private var subbedIds: Set<String> = []
var body: some View {
ZStack {
Color.zxBg0.ignoresSafeArea()
if isLoading && kbs.isEmpty {
ProgressView().tint(Color.zxPurple)
} else if kbs.isEmpty {
Text("暂无公开知识库").font(.system(size: 14)).foregroundColor(Color.zxF04)
} else {
ScrollView {
LazyVStack(spacing: 12) {
ForEach(kbs) { kb in
HStack(spacing: 12) {
VStack(alignment: .leading, spacing: 4) {
Text(kb.title).font(.system(size: 15, weight: .semibold)).foregroundColor(Color.zxF0)
if let desc = kb.description, !desc.isEmpty {
Text(desc).font(.system(size: 12)).foregroundColor(Color.zxF04).lineLimit(2)
}
if let count = kb.itemCount {
Text("\(count) 个知识点").font(.system(size: 11)).foregroundColor(Color.zxF04)
}
}
Spacer()
Button {
Task {
let isSubbed = subbedIds.contains(kb.id)
if isSubbed {
try? await KnowledgeBaseService.shared.unsubscribe(kbId: kb.id)
subbedIds.remove(kb.id)
} else {
try? await KnowledgeBaseService.shared.subscribe(kbId: kb.id)
subbedIds.insert(kb.id)
}
}
} label: {
Text(subbedIds.contains(kb.id) ? "已订阅" : "订阅")
.font(.system(size: 12, weight: .medium))
.foregroundColor(subbedIds.contains(kb.id) ? Color.zxF04 : .white)
.padding(.horizontal, 16).padding(.vertical, 6)
.background(subbedIds.contains(kb.id) ? Color.zxFill005 : Color.zxPurple)
.clipShape(Capsule())
}
}
.padding(16)
.background(Color.zxFill004)
.clipShape(RoundedRectangle(cornerRadius: 14))
}
}
.padding(.horizontal, 20).padding(.top, 8)
}
.scrollIndicators(.hidden)
}
}
.navigationTitle("发现公开库")
.navigationBarTitleDisplayMode(.inline)
.task { await load() }
}
private func load() async {
isLoading = true
kbs = (try? await KnowledgeBaseService.shared.discover()) ?? []
let subs = (try? await KnowledgeBaseService.shared.listSubscribed()) ?? []
subbedIds = Set(subs.map { $0.id })
isLoading = false
}
}

View File

@ -0,0 +1,167 @@
import SwiftUI
import Combine
// MARK: - Note model
struct QuickNote: Codable, Identifiable {
var id: String = UUID().uuidString
let materialId: String
let content: String
let anchorType: String? // "markdown", "text", "pdf", etc.
let anchorBlockId: String?
let anchorLineNumber: UInt32?
let anchorPageNumber: UInt32?
let createdAt: String
var anchorDescription: String? {
if let bid = anchorBlockId { return "位置: block \(bid.prefix(8))" }
if let ln = anchorLineNumber { return "位置: 第 \(ln)" }
if let pn = anchorPageNumber { return "位置: 第 \(pn)" }
return nil
}
}
// MARK: - Local note store
@MainActor
final class NoteStore: ObservableObject {
static let shared = NoteStore()
@Published private(set) var notes: [QuickNote] = []
private let key = "quick_notes_store"
private init() {
load()
}
func add(_ note: QuickNote) {
notes.insert(note, at: 0)
save()
}
func notesFor(materialId: String) -> [QuickNote] {
notes.filter { $0.materialId == materialId }
}
func delete(_ note: QuickNote) {
notes.removeAll { $0.id == note.id }
save()
}
private func load() {
guard let data = UserDefaults.standard.data(forKey: key),
let decoded = try? JSONDecoder().decode([QuickNote].self, from: data) else { return }
notes = decoded
}
private func save() {
guard let data = try? JSONEncoder().encode(notes) else { return }
UserDefaults.standard.set(data, forKey: key)
}
}
// MARK: - Quick Note Sheet
struct QuickNoteSheet: View {
let materialId: String
let materialName: String
let anchor: NoteAnchor?
@Environment(\.dismiss) private var dismiss
@State private var text: String = ""
@FocusState private var isFocused: Bool
var body: some View {
NavigationStack {
VStack(spacing: 0) {
// Material context
HStack(spacing: 8) {
Image(systemName: "doc.text").font(.system(size: 13)).foregroundColor(Color.zxF04)
Text(materialName).font(.system(size: 13, weight: .medium)).foregroundColor(Color.zxF0)
.lineLimit(1)
Spacer()
if let anchorDesc = anchorDescription {
Text(anchorDesc).font(.system(size: 11)).foregroundColor(Color.zxF03)
.padding(.horizontal, 8).padding(.vertical, 2)
.background(Color.zxFill004).clipShape(Capsule())
}
}
.padding(.horizontal, 16).padding(.vertical, 10)
.background(Color.zxSurface)
// Editor
TextEditor(text: $text)
.focused($isFocused)
.font(.system(size: 15)).foregroundColor(Color.zxF0).lineSpacing(5)
.scrollContentBackground(.hidden)
.background(Color.zxCanvas)
.overlay(alignment: .topLeading) {
if text.isEmpty {
Text("记下你的想法…").font(.system(size: 15)).foregroundColor(Color.zxF03)
.padding(.horizontal, 4).padding(.top, 8)
.allowsHitTesting(false)
}
}
.padding(.horizontal, 16).padding(.top, 8)
}
.navigationTitle("新建笔记")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("取消") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button("保存") { save() }
.fontWeight(.bold)
.disabled(text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
}
}
.onAppear { isFocused = true }
}
private var anchorDescription: String? {
guard let a = anchor else { return nil }
switch a {
case .markdownBlock(_, let blockId):
return "block \(String(blockId.prefix(8)))"
case .textLine(_, let lineNumber):
return "\(lineNumber)"
case .pdfPage(_, let pageNumber):
return "\(pageNumber)"
default:
return nil
}
}
private func anchorFields(from anchor: NoteAnchor?) -> (type: String?, blockId: String?, line: UInt32?, page: UInt32?) {
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 .image: return ("image", nil, nil, nil)
case .epubChapter(_, let cid): return ("epub", cid, nil, nil)
case .knowledgeItem: return ("knowledge_item", nil, nil, nil)
}
}
private func save() {
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
let fields = anchorFields(from: anchor)
let note = QuickNote(
materialId: materialId,
content: trimmed,
anchorType: fields.type,
anchorBlockId: fields.blockId,
anchorLineNumber: fields.line,
anchorPageNumber: fields.page,
createdAt: ISO8601DateFormatter().string(from: Date())
)
NoteStore.shared.add(note)
dismiss()
}
}

View File

@ -0,0 +1,136 @@
import SwiftUI
// MARK: - Reading Event Collector
/// Collects reading events (MaterialOpened, MaterialClosed, PositionChanged,
/// Heartbeat, MarkedAsRead) into the Rust event buffer for cross-platform export.
@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?
private var positionDebounceTask: Task<Void, Never>?
private init() {}
// MARK: - Public API
/// Call when a material is opened.
func open(materialId: String) {
flush() // commit any prior session first
activeMaterialId = materialId
activeSeconds = 0
let event = ReadingEvent.materialOpened(
materialId: materialId,
timestampMs: nowMs()
)
push(event)
startHeartbeat()
}
/// Call when a material is closed. Returns active seconds.
func close(materialId: String) -> UInt32 {
stopHeartbeat()
flushPosition()
let seconds = activeSeconds
let event = ReadingEvent.materialClosed(
materialId: materialId,
timestampMs: nowMs(),
activeSeconds: seconds
)
push(event)
activeMaterialId = nil
activeSeconds = 0
lastPosition = nil
return seconds
}
/// Call on scroll / page change. Debounced to avoid flooding.
func updatePosition(materialId: String, position: ReadingPosition) {
guard activeMaterialId == materialId else { return }
lastPosition = position
positionDebounceTask?.cancel()
positionDebounceTask = Task {
try? await Task.sleep(nanoseconds: 2_000_000_000) // 2s debounce
guard !Task.isCancelled, let pos = lastPosition else { return }
let event = ReadingEvent.positionChanged(
materialId: materialId,
position: pos,
timestampMs: nowMs()
)
push(event)
}
}
/// Call when user manually marks as read.
func markAsRead(materialId: String) {
let event = ReadingEvent.markedAsRead(
materialId: materialId,
timestampMs: nowMs()
)
push(event)
}
/// Export all pending events from Rust buffer.
func exportPending() -> [ReadingEvent] {
return exportPendingEvents()
}
/// Remove first `count` events after successful upload.
func clearExported(_ count: Int) {
clearExportedEvents(count: UInt32(count))
}
/// Clear everything (on sign-out, error, etc).
func flush() {
flushPosition()
// Clear Rust side by exporting and discarding
let pending = exportPendingEvents()
clearExportedEvents(count: UInt32(pending.count))
}
// MARK: - Internal
private func push(_ event: ReadingEvent) {
pushReadingEvent(event: event)
}
private func startHeartbeat() {
stopHeartbeat()
heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 15, repeats: true) { [weak self] _ in
Task { @MainActor in
guard let self, let id = self.activeMaterialId else { return }
self.activeSeconds += 15
let event = ReadingEvent.heartbeat(
materialId: id,
activeSeconds: self.activeSeconds,
position: self.lastPosition,
timestampMs: self.nowMs()
)
self.push(event)
}
}
}
private func stopHeartbeat() {
heartbeatTimer?.invalidate()
heartbeatTimer = nil
}
private func flushPosition() {
positionDebounceTask?.cancel()
positionDebounceTask = nil
}
private func nowMs() -> Int64 {
Int64(Date().timeIntervalSince1970 * 1000)
}
}

View File

@ -0,0 +1,93 @@
import Foundation
// MARK: - Position store
/// Persists ReadingPosition per materialId in UserDefaults,
/// enabling "continue reading" across app launches.
@MainActor
final class ReadingPositionStore {
static let shared = ReadingPositionStore()
private let defaults = UserDefaults.standard
private let prefix = "reading_position."
private init() {}
// MARK: - Public
/// Save the last reading position for a material.
func save(materialId: String, position: ReadingPosition) {
let dict = positionToDict(position)
defaults.set(dict, forKey: key(materialId))
}
/// Load the last reading position, if any.
func load(materialId: String) -> ReadingPosition? {
guard let dict = defaults.dictionary(forKey: key(materialId)) else { return nil }
return positionFromDict(dict)
}
/// Remove saved position (e.g. when material is deleted).
func remove(materialId: String) {
defaults.removeObject(forKey: key(materialId))
}
/// Check if a saved position exists.
func hasPosition(materialId: String) -> Bool {
defaults.dictionary(forKey: key(materialId)) != nil
}
// MARK: - Internal
private func key(_ materialId: String) -> String {
"\(prefix)\(materialId)"
}
private func positionToDict(_ pos: ReadingPosition) -> [String: Any] {
switch pos {
case .markdown(let blockId, let scrollProgress):
return ["t": "markdown", "b": blockId, "s": scrollProgress]
case .text(let lineNumber, let scrollProgress):
return ["t": "text", "l": lineNumber, "s": scrollProgress]
case .pdf(let pageNumber, let pageProgress, let overallProgress):
return ["t": "pdf", "p": pageNumber, "pp": pageProgress, "op": overallProgress]
case .image(let zoomScale, let offsetX, let offsetY):
return ["t": "image", "z": zoomScale, "x": offsetX, "y": offsetY]
case .epub(let chapterId, let chapterProgress, let overallProgress):
return ["t": "epub", "c": chapterId, "cp": chapterProgress, "op": overallProgress]
case .unknown:
return ["t": "unknown"]
}
}
private func positionFromDict(_ dict: [String: Any]) -> ReadingPosition? {
guard let type = dict["t"] as? String else { return nil }
switch type {
case "markdown":
guard let b = dict["b"] as? String else { return nil }
let s = (dict["s"] as? Float) ?? 0
return .markdown(blockId: b, scrollProgress: s)
case "text":
let l = (dict["l"] as? UInt32) ?? 1
let s = (dict["s"] as? Float) ?? 0
return .text(lineNumber: l, scrollProgress: s)
case "pdf":
let p = (dict["p"] as? UInt32) ?? 1
let pp = (dict["pp"] as? Float) ?? 0
let op = (dict["op"] as? Float) ?? 0
return .pdf(pageNumber: p, pageProgress: pp, overallProgress: op)
case "image":
let z = (dict["z"] as? Float) ?? 1
let x = (dict["x"] as? Float) ?? 0
let y = (dict["y"] as? Float) ?? 0
return .image(zoomScale: z, offsetX: x, offsetY: y)
case "epub":
guard let c = dict["c"] as? String else { return nil }
let cp = (dict["cp"] as? Float) ?? 0
let op = (dict["op"] as? Float) ?? 0
return .epub(chapterId: c, chapterProgress: cp, overallProgress: op)
default:
return .unknown
}
}
}

View File

@ -11,7 +11,15 @@ struct ProfileView: View {
HStack { HStack {
Text("我的").font(.system(size: 22, weight: .heavy)).foregroundColor(Color.zxF0).tracking(-0.5) Text("我的").font(.system(size: 22, weight: .heavy)).foregroundColor(Color.zxF0).tracking(-0.5)
Spacer() Spacer()
NavigationLink(value: Route.notificationList) { NavigationLink(value: Route.discoverPublic) {
HStack {
Image(systemName: "globe").resizable().scaledToFit().frame(width: 20, height: 20).foregroundColor(Color.zxF05)
Text("发现公开库").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0)
Spacer(); Image("icon-chevron-right").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03)
}.padding(.horizontal, 16).padding(.vertical, 14)
}.foregroundColor(.primary)
ZXProfileDivider()
NavigationLink(value: Route.notificationList) {
Image("icon-notifications").resizable().scaledToFit().frame(width: 22, height: 22).foregroundColor(Color.zxF05) Image("icon-notifications").resizable().scaledToFit().frame(width: 22, height: 22).foregroundColor(Color.zxF05)
} }
.accessibilityLabel("通知中心") .accessibilityLabel("通知中心")
@ -103,6 +111,14 @@ struct ProfileView: View {
}.padding(.horizontal, 16).padding(.vertical, 14) }.padding(.horizontal, 16).padding(.vertical, 14)
}.foregroundColor(.primary) }.foregroundColor(.primary)
ZXProfileDivider() ZXProfileDivider()
NavigationLink(value: Route.discoverPublic) {
HStack {
Image(systemName: "globe").resizable().scaledToFit().frame(width: 20, height: 20).foregroundColor(Color.zxF05)
Text("发现公开库").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0)
Spacer(); Image("icon-chevron-right").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03)
}.padding(.horizontal, 16).padding(.vertical, 14)
}.foregroundColor(.primary)
ZXProfileDivider()
NavigationLink(value: Route.notificationList) { NavigationLink(value: Route.notificationList) {
HStack { HStack {
Image("icon-bell-on").resizable().scaledToFit().frame(width: 20, height: 20).foregroundColor(Color.zxF05) Image("icon-bell-on").resizable().scaledToFit().frame(width: 20, height: 20).foregroundColor(Color.zxF05)