mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-07-24 07:45:57 +02:00
Cross-platform groundwork: platform detection, iOS config, Windows drag-drop fix, Android 10 storage fix (#72), bundled fonts
This commit is contained in:
+20
-16
@@ -11,18 +11,18 @@ use tauri::{AppHandle, Manager, State};
|
||||
pub fn open_vault(app: AppHandle, state: State<'_, AppState>, path: String) -> Result<(), String> {
|
||||
operations::ensure_vault_structure(&path)?;
|
||||
|
||||
// Initialize search index - rebuild in background on Android (FUSE is slow)
|
||||
// Initialize search index - rebuild in background on mobile (sandboxed FS is slow)
|
||||
let search = std::sync::Arc::new(SearchIndex::new(&path)?);
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(mobile)]
|
||||
{
|
||||
let search_bg = search.clone();
|
||||
let vault = path.clone();
|
||||
std::thread::spawn(move || {
|
||||
let _ = search_bg.rebuild(&vault);
|
||||
log::info!("Android: search index rebuild complete");
|
||||
log::info!("mobile: search index rebuild complete");
|
||||
});
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
search.rebuild(&path)?;
|
||||
}
|
||||
@@ -670,7 +670,7 @@ pub fn save_vault_state(state: State<'_, AppState>, vault_state: VaultState) ->
|
||||
|
||||
/// Read image from system clipboard (bypasses WebKitGTK clipboard bug).
|
||||
/// Returns PNG bytes as Vec<u8>, or error if no image on clipboard.
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
#[tauri::command]
|
||||
pub fn read_clipboard_image() -> Result<Vec<u8>, String> {
|
||||
let mut clipboard =
|
||||
@@ -695,14 +695,14 @@ pub fn read_clipboard_image() -> Result<Vec<u8>, String> {
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(mobile)]
|
||||
#[tauri::command]
|
||||
pub fn read_clipboard_image() -> Result<Vec<u8>, String> {
|
||||
Err("Clipboard image reading not supported on Android".to_string())
|
||||
}
|
||||
|
||||
/// Copy an image file to the system clipboard.
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
#[tauri::command]
|
||||
pub fn copy_image_to_clipboard(path: String) -> Result<(), String> {
|
||||
let data = std::fs::read(&path).map_err(|e| format!("Failed to read image: {}", e))?;
|
||||
@@ -722,14 +722,14 @@ pub fn copy_image_to_clipboard(path: String) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(mobile)]
|
||||
#[tauri::command]
|
||||
pub fn copy_image_to_clipboard(_path: String) -> Result<(), String> {
|
||||
Err("Clipboard image copy not supported on Android".to_string())
|
||||
}
|
||||
|
||||
/// Copy PNG bytes directly to the system clipboard.
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
#[tauri::command]
|
||||
pub fn copy_png_to_clipboard(data: Vec<u8>) -> Result<(), String> {
|
||||
let img = image::load_from_memory(&data)
|
||||
@@ -748,7 +748,7 @@ pub fn copy_png_to_clipboard(data: Vec<u8>) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(mobile)]
|
||||
#[tauri::command]
|
||||
pub fn copy_png_to_clipboard(_data: Vec<u8>) -> Result<(), String> {
|
||||
Err("Clipboard image copy not supported on Android".to_string())
|
||||
@@ -1798,17 +1798,21 @@ pub fn ai_ask(
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
static ANDROID_CONFIG_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
|
||||
// On mobile (Android + iOS) the OS config dir is injected at startup via the Tauri
|
||||
// path resolver, since dirs::config_dir() is not reliable in the app sandbox.
|
||||
static MOBILE_CONFIG_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
|
||||
|
||||
pub fn set_android_config_dir(path: std::path::PathBuf) {
|
||||
let _ = ANDROID_CONFIG_DIR.set(path);
|
||||
pub fn set_mobile_config_dir(path: std::path::PathBuf) {
|
||||
let _ = MOBILE_CONFIG_DIR.set(path);
|
||||
}
|
||||
|
||||
fn app_config_path() -> Result<std::path::PathBuf, String> {
|
||||
let app_dir = if let Some(config_dir) = dirs::config_dir() {
|
||||
// Prefer the injected mobile dir when present (set only on mobile); fall back to
|
||||
// the platform config dir on desktop.
|
||||
let app_dir = if let Some(mobile_dir) = MOBILE_CONFIG_DIR.get() {
|
||||
mobile_dir.join("helixnotes")
|
||||
} else if let Some(config_dir) = dirs::config_dir() {
|
||||
config_dir.join("helixnotes")
|
||||
} else if let Some(android_dir) = ANDROID_CONFIG_DIR.get() {
|
||||
android_dir.join("helixnotes")
|
||||
} else {
|
||||
return Err("Config directory not available yet".to_string());
|
||||
};
|
||||
|
||||
+18
-12
@@ -11,7 +11,7 @@ use state::AppState;
|
||||
#[allow(unused_imports)]
|
||||
use tauri::{Emitter, Manager};
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
use tauri::{
|
||||
image::Image,
|
||||
menu::{MenuBuilder, MenuItemBuilder},
|
||||
@@ -20,14 +20,20 @@ use tauri::{
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
// Work around blank window from WebKitGTK DMABUF/GBM allocation failures on some Linux GPUs.
|
||||
#[cfg(target_os = "linux")]
|
||||
if std::env::var_os("WEBKIT_DISABLE_DMABUF_RENDERER").is_none() {
|
||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
}
|
||||
|
||||
rustls::crypto::ring::default_provider()
|
||||
.install_default()
|
||||
.expect("Failed to install rustls crypto provider");
|
||||
|
||||
let config = commands::load_app_config();
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
let show_tray = config.show_tray_icon;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
let close_to_tray = config.close_to_tray && show_tray;
|
||||
let app_state = AppState::new(config);
|
||||
|
||||
@@ -44,28 +50,28 @@ pub fn run() {
|
||||
)?;
|
||||
}
|
||||
|
||||
// On Android, set config dir from Tauri's path resolver, then reload config
|
||||
#[cfg(target_os = "android")]
|
||||
// On mobile, set config dir from Tauri's path resolver, then reload config
|
||||
#[cfg(mobile)]
|
||||
{
|
||||
if let Ok(config_dir) = app.path().config_dir() {
|
||||
commands::set_android_config_dir(config_dir);
|
||||
commands::set_mobile_config_dir(config_dir);
|
||||
} else if let Ok(data_dir) = app.path().data_dir() {
|
||||
commands::set_android_config_dir(data_dir);
|
||||
commands::set_mobile_config_dir(data_dir);
|
||||
}
|
||||
// Reload config now that the Android config dir is available
|
||||
// Reload config now that the mobile config dir is available
|
||||
let reloaded = commands::load_app_config();
|
||||
let _ = app.state::<AppState>().config.lock().map(|mut cfg| {
|
||||
*cfg = reloaded;
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
if show_tray {
|
||||
setup_tray(app)?;
|
||||
}
|
||||
|
||||
// Check CLI args for a .md file path on initial launch
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
let file_arg = std::env::args().skip(1).find(|a| {
|
||||
let a = a.trim();
|
||||
@@ -229,7 +235,7 @@ pub fn run() {
|
||||
});
|
||||
});
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
builder = builder.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
|
||||
// Always show/focus the main window
|
||||
@@ -290,7 +296,7 @@ pub fn run() {
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
fn setup_tray(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let show = MenuItemBuilder::with_id("show", "Show HelixNotes").build(app)?;
|
||||
let quit = MenuItemBuilder::with_id("quit", "Quit").build(app)?;
|
||||
|
||||
@@ -5,9 +5,9 @@ use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
use tantivy::collector::TopDocs;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
use tantivy::directory::MmapDirectory;
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(mobile)]
|
||||
use tantivy::directory::RamDirectory;
|
||||
use tantivy::query::{BooleanQuery, FuzzyTermQuery, Occur, PhrasePrefixQuery, Query};
|
||||
use tantivy::schema::*;
|
||||
@@ -34,14 +34,14 @@ impl SearchIndex {
|
||||
let tags_field = schema_builder.add_text_field("tags", TEXT | STORED);
|
||||
let schema = schema_builder.build();
|
||||
|
||||
// Android: use in-memory index (flock doesn't work on /storage/emulated FUSE filesystem)
|
||||
// Mobile: use in-memory index (flock is unreliable on the sandboxed/FUSE filesystem)
|
||||
// Desktop: use mmap directory for persistent index on disk
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(mobile)]
|
||||
let index = {
|
||||
let dir = RamDirectory::create();
|
||||
Index::open_or_create(dir, schema.clone()).map_err(|e| e.to_string())?
|
||||
};
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
let index = {
|
||||
let index_dir = helixnotes_dir(vault_path).join("search_index");
|
||||
fs::create_dir_all(&index_dir).map_err(|e| e.to_string())?;
|
||||
@@ -49,9 +49,9 @@ impl SearchIndex {
|
||||
Index::open_or_create(dir, schema.clone()).map_err(|e| e.to_string())?
|
||||
};
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(mobile)]
|
||||
let heap_size = 15_000_000;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
let heap_size = 50_000_000;
|
||||
|
||||
let writer = index.writer(heap_size).map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -194,8 +194,8 @@ pub fn scan_notes(vault_path: &str, notebook_path: Option<&str>) -> Result<Vec<N
|
||||
return Err("Path does not exist".to_string());
|
||||
}
|
||||
|
||||
// On Android, use metadata-only scan (no file reads) for fast listing on FUSE
|
||||
#[cfg(target_os = "android")]
|
||||
// On mobile, use metadata-only scan (no file reads) for fast listing on sandboxed FS
|
||||
#[cfg(mobile)]
|
||||
{
|
||||
let mut notes = Vec::new();
|
||||
// Log what read_dir sees
|
||||
@@ -238,12 +238,12 @@ pub fn scan_notes(vault_path: &str, notebook_path: Option<&str>) -> Result<Vec<N
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("scan_notes: Android scan found {} notes", notes.len());
|
||||
log::info!("scan_notes: mobile scan found {} notes", notes.len());
|
||||
notes.sort_by(|a, b| b.meta.modified.cmp(&a.meta.modified));
|
||||
return Ok(notes);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
let md_files: Vec<PathBuf> = if notebook_path.is_some() {
|
||||
fs::read_dir(root)
|
||||
@@ -279,7 +279,7 @@ fn read_note_entry(path: &Path, vault_root: &Path) -> Result<NoteEntry, String>
|
||||
|
||||
/// Android-only: reads just the frontmatter (first 2KB) for tags/title/pinned,
|
||||
/// uses filesystem timestamps for dates. No preview text.
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(mobile)]
|
||||
fn read_note_entry_metadata_only(path: &Path, vault_root: &Path) -> Result<NoteEntry, String> {
|
||||
// Read first 2KB - enough for frontmatter with tags, title, pinned
|
||||
let mut file = fs::File::open(path).map_err(|e| e.to_string())?;
|
||||
|
||||
Reference in New Issue
Block a user