Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4771d1b39 |
@@ -1,10 +1,5 @@
|
||||
node_modules
|
||||
|
||||
# Foreign lockfiles - this project uses pnpm (pnpm-lock.yaml is the only lockfile)
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
bun.lockb
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
@@ -13,23 +8,9 @@ bun.lockb
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# Local tests
|
||||
/tests/
|
||||
|
||||
# Nix
|
||||
/result
|
||||
|
||||
# Tauri
|
||||
/src-tauri/target
|
||||
|
||||
# Track Android source (MainActivity.kt, manifest, proguard, gradle); Tauri's nested
|
||||
# gen/android/.gitignore + app/.gitignore keep build/, .gradle/, .cxx/, generated/, *.so out.
|
||||
/src-tauri/gen/*
|
||||
!/src-tauri/gen/android/
|
||||
|
||||
# Never commit signing secrets (nested ignore misses keystore.properties)
|
||||
/src-tauri/gen/android/keystore.properties
|
||||
/src-tauri/gen/android/key.properties
|
||||
/src-tauri/gen
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
@@ -1,174 +1,45 @@
|
||||
# HelixNotes
|
||||
|
||||
[](https://gitlab.com/ArkHost/HelixNotes/-/blob/main/LICENSE)
|
||||
[](https://gitlab.com/ArkHost/HelixNotes/-/releases/v1.3.5)
|
||||
[](https://helixnotes.com)
|
||||
[]()
|
||||
A local-first markdown note-taking app built with Tauri, SvelteKit, and Rust.
|
||||
|
||||
A local markdown note-taking app built with Tauri, SvelteKit, and Rust.
|
||||
Your notes are stored as standard Markdown files on your local filesystem. No cloud, no lock-in.
|
||||
|
||||
Your notes are stored as standard Markdown files on your local filesystem.
|
||||
No cloud, no lock-in.
|
||||
## Download
|
||||
|
||||
## Download (v1.3.5)
|
||||
| Platform | Download |
|
||||
|----------|----------|
|
||||
| Linux | [HelixNotes_1.0.1_amd64.AppImage](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.0.1/HelixNotes_1.0.1_amd64.AppImage) |
|
||||
| Windows | [HelixNotes_1.0.1_x64-setup.exe](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.0.1/HelixNotes_1.0.1_x64-setup.exe) |
|
||||
| macOS | Coming soon |
|
||||
|
||||
### Linux
|
||||
|
||||
#### AppImage
|
||||
|
||||
The AppImage works only on Fedora 43+, Arch Linux, and openSUSE Tumbleweed (x86_64).
|
||||
|
||||
[Download AppImage](https://download.helixnotes.com/releases/v1.3.5/HelixNotes_1.3.5_amd64.AppImage)
|
||||
|
||||
#### Distro-specific packages
|
||||
|
||||
##### Fedora 43+ (DNF)
|
||||
|
||||
```bash
|
||||
sudo dnf config-manager addrepo \
|
||||
--from-repofile=https://repo.arkhost.com/helixnotes.repo
|
||||
sudo dnf install helix-notes
|
||||
```
|
||||
|
||||
##### Debian / Ubuntu / Mint (APT)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://repo.arkhost.com/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/arkhost.gpg && echo "deb [signed-by=/usr/share/keyrings/arkhost.gpg arch=amd64] https://repo.arkhost.com stable main" | sudo tee /etc/apt/sources.list.d/helixnotes.list && sudo apt update && sudo apt install helix-notes
|
||||
```
|
||||
|
||||
##### Arch / Manjaro (AUR)
|
||||
|
||||
```bash
|
||||
yay -S helixnotes-appimage-bin
|
||||
```
|
||||
|
||||
##### Solus (EOPKG)
|
||||
|
||||
```bash
|
||||
sudo eopkg it helixnotes
|
||||
```
|
||||
|
||||
##### NixOS
|
||||
|
||||
<details>
|
||||
<summary>flake.nix</summary>
|
||||
|
||||
```nix
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
helix-notes = {
|
||||
url = "git+https://gitlab.com/ArkHost/HelixNotes";
|
||||
# inputs.nixpkgs.follows = "nixpkgs";
|
||||
}
|
||||
};
|
||||
|
||||
outputs = {
|
||||
nixpkgs,
|
||||
helix-notes,
|
||||
...
|
||||
}: let
|
||||
system = "x86_64-linux";
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
nixosConfigurations.default = nixpkgs.lib.nixosSystem {
|
||||
system = system;
|
||||
specialArgs = { inherit helix-notes; };
|
||||
|
||||
modules = [
|
||||
/path/to/configuration.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>configuration.nix</summary>
|
||||
|
||||
```nix
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
helix-notes,
|
||||
...
|
||||
}:
|
||||
{
|
||||
users.users.<USERNAME> = {
|
||||
packages = with pkgs; [
|
||||
(helix-notes.packages.${pkgs.stdenv.hostPlatform.system}.default)
|
||||
];
|
||||
};
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
#### Manual package downloads
|
||||
|
||||
- [.deb](https://download.helixnotes.com/releases/v1.3.5/HelixNotes_1.3.5_amd64.deb) (Ubuntu 22.04+)
|
||||
- [.rpm](https://download.helixnotes.com/releases/v1.3.5/HelixNotes-1.3.5-1.x86_64.rpm)
|
||||
|
||||
### Windows
|
||||
|
||||
[Download Installer](https://download.helixnotes.com/releases/v1.3.5/HelixNotes_1.3.5_x64-setup.exe) (Windows 10/11)
|
||||
|
||||
### macOS
|
||||
|
||||
[Download .dmg (Apple Silicon)](https://download.helixnotes.com/releases/v1.3.5/HelixNotes_1.3.5_aarch64.dmg) (M-series Macs)
|
||||
|
||||
[Download .dmg (Intel)](https://download.helixnotes.com/releases/v1.3.5/HelixNotes_1.3.5_x64.dmg)
|
||||
|
||||
The Apple Silicon build is signed and notarized by Apple. The Intel build may still trigger a Gatekeeper warning. If macOS reports that the Intel build is damaged, run this once after installing or updating it:
|
||||
|
||||
```bash
|
||||
xattr -cr /Applications/HelixNotes.app
|
||||
```
|
||||
|
||||
### Android
|
||||
|
||||
[Download APK](https://download.helixnotes.com/releases/v1.3.5/HelixNotes_1.3.5_android.apk)
|
||||
|
||||
---
|
||||
|
||||
All releases: [gitlab.com/ArkHost/HelixNotes/-/releases](https://gitlab.com/ArkHost/HelixNotes/-/releases)
|
||||
See all releases: [codeberg.org/ArkHost/HelixNotes/releases](https://codeberg.org/ArkHost/HelixNotes/releases)
|
||||
|
||||
## Features
|
||||
|
||||
- Markdown editor with toolbar, slash commands, source mode, code highlighting
|
||||
- **Tasks view**: aggregate `- [ ]` checklists from across all notes, set priority and due dates, work in a list or a calendar (drag a task to reschedule)
|
||||
- `[[Wiki-links]]` and graph view
|
||||
- Full-text search (Tantivy), CJK-aware for Chinese, Japanese, and Korean
|
||||
- Outline panel, daily notes with calendar view, tags with autocomplete, drag-and-drop
|
||||
- Live KaTeX math editor (`/math`, `/imath`) with modal preview, double-click to edit
|
||||
- Mermaid diagrams (opt-in render, copy as PNG, save as PNG/SVG)
|
||||
- Encrypted secret blocks (`/secret`) stored as portable `helix-secret` markdown fences
|
||||
- Insert date/time (`/date`, `/time`, `/now`), color swatches (`/color`), configurable week start
|
||||
- Manual notebook sorting (drag to reorder above, into, or below)
|
||||
- External `.md` viewer mode with import-to-vault flow
|
||||
- PDF preview, Obsidian import, "Show in File Manager"
|
||||
- AI writing tools (Ollama / OpenAI-compatible / Anthropic / OpenAI)
|
||||
- **Optional WebDAV sync** to your own server (Nextcloud, ownCloud, a NAS): manual or automatic, with keep-both conflict copies
|
||||
- Version history with diffs, automatic backups
|
||||
- Multi-window, file associations, focus mode, view mode
|
||||
- Themes (light, dark, and 14 palettes), accent colors, fonts, 80-200% interface scale
|
||||
- Local plain-text files, no company cloud
|
||||
|
||||
Full documentation: [helixnotes.com/docs](https://helixnotes.com/docs.html)
|
||||
- **Markdown editor** with rich formatting toolbar, slash commands, and source mode
|
||||
- **Wiki-links** — link notes with `[[Note Title]]` syntax
|
||||
- **Graph view** — visualize connections between your notes
|
||||
- **Full-text search** powered by Tantivy
|
||||
- **AI writing tools** — improve, summarize, translate, and more (Anthropic / OpenAI)
|
||||
- **Version history** — per-note snapshots with diff view
|
||||
- **Backups** — automatic zip-based vault backups
|
||||
- **PDF preview** — inline rendering of embedded PDFs
|
||||
- **Obsidian import** — convert Obsidian wiki-links to standard markdown
|
||||
- **Themes** — light/dark mode with customizable accent colors and fonts
|
||||
- **Focus mode** — distraction-free writing
|
||||
- **Local-first** — everything stays on your machine
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Frontend**: SvelteKit (Svelte 5) + TailwindCSS v4 + TipTap v3
|
||||
- **Backend**: Rust (Tauri 2.0) + Tantivy (search) + Notify (file watcher)
|
||||
- **Platforms**: Linux (AppImage), Windows, macOS, Android
|
||||
- **Platforms**: Linux (AppImage), Windows, macOS
|
||||
|
||||
## Building from Source
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Rust](https://rustup.rs/) (1.88+)
|
||||
- [Rust](https://rustup.rs/) (1.77+)
|
||||
- [Node.js](https://nodejs.org/) (18+)
|
||||
- [pnpm](https://pnpm.io/)
|
||||
- System dependencies for Tauri: see [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/)
|
||||
@@ -180,28 +51,12 @@ pnpm install
|
||||
pnpm tauri dev
|
||||
```
|
||||
|
||||
### Verification
|
||||
|
||||
Run the frontend checks and tests, Rust tests and lints, and production frontend build with one command:
|
||||
|
||||
```bash
|
||||
pnpm verify
|
||||
```
|
||||
|
||||
### Production Build
|
||||
|
||||
```bash
|
||||
pnpm tauri build
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## License
|
||||
|
||||
[AGPL-3.0](https://gitlab.com/ArkHost/HelixNotes/-/blob/main/LICENSE)
|
||||
[AGPL-3.0](LICENSE)
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1785571196,
|
||||
"narHash": "sha256-KoTsyMQqnXQZq8deCEnu4QkyldkwH/bpMMhUcfMdGIw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "148bab9c1c3c53136ecb44a6ea356a0ed5b39b06",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
{
|
||||
description = "HelixNotes (local Markdown notes app) with Nix flake";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./src-tauri/Cargo.toml);
|
||||
pname = cargoToml.package.name;
|
||||
version = cargoToml.package.version;
|
||||
in
|
||||
{
|
||||
packages.default = pkgs.rustPlatform.buildRustPackage {
|
||||
inherit pname version;
|
||||
src = ./.;
|
||||
|
||||
cargoHash = "sha256-P0iXImWYKYPxVj7q2JSuDmd8JvjcKW4DzFUzXeUQZPk=C";
|
||||
|
||||
pnpmDeps = pkgs.fetchPnpmDeps {
|
||||
inherit pname version;
|
||||
src = ./.;
|
||||
fetcherVersion = 4;
|
||||
hash = "sha256-odUNKO2D50DG+VpuoTEo5FLMYy5pQSHQBtfHJrhJt78=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cargo-tauri.hook
|
||||
|
||||
pnpmConfigHook
|
||||
pnpm
|
||||
nodejs
|
||||
|
||||
pkg-config
|
||||
jq
|
||||
moreutils
|
||||
wrapGAppsHook3
|
||||
];
|
||||
|
||||
buildInputs =
|
||||
with pkgs;
|
||||
lib.optionals stdenv.hostPlatform.isLinux [
|
||||
webkitgtk_4_1
|
||||
libayatana-appindicator
|
||||
];
|
||||
|
||||
cargoRoot = "src-tauri";
|
||||
buildAndTestSubdir = self.packages.${system}.default.cargoRoot;
|
||||
|
||||
# Deactivate the upstream update mechanism
|
||||
postPatch = ''
|
||||
jq '
|
||||
.bundle.createUpdaterArtifacts = false |
|
||||
.plugins.updater = {"active": false, "pubkey": "", "endpoints": []}
|
||||
' \
|
||||
src-tauri/tauri.conf.json | sponge src-tauri/tauri.conf.json
|
||||
'';
|
||||
|
||||
meta = with pkgs.lib; {
|
||||
description = "HelixNotes desktop application";
|
||||
homepage = "https://helixnotes.com/";
|
||||
license = licenses.agpl3Plus;
|
||||
platforms = platforms.linux ++ platforms.darwin;
|
||||
mainProgram = "helixnotes";
|
||||
};
|
||||
};
|
||||
|
||||
formatter = pkgs.nixfmt;
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "helixnotes",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"version": "1.3.5",
|
||||
"version": "1.0.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -11,10 +11,6 @@
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test": "node --test tests/*.test.mjs src/lib/utils/*.test.mjs",
|
||||
"test:rust": "cargo test --manifest-path src-tauri/Cargo.toml",
|
||||
"lint:rust": "cargo fmt --manifest-path src-tauri/Cargo.toml --check && cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings",
|
||||
"verify": "pnpm check && pnpm test && pnpm test:rust && pnpm lint:rust && pnpm build",
|
||||
"tauri": "tauri",
|
||||
"tauri:dev": "tauri dev",
|
||||
"tauri:build": "tauri build"
|
||||
@@ -22,22 +18,22 @@
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.70.2",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@tailwindcss/postcss": "^4.3.3",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tauri-apps/cli": "^2.10.0",
|
||||
"@types/node": "26.2.0",
|
||||
"svelte": "^5.56.9",
|
||||
"svelte": "^5.49.2",
|
||||
"svelte-check": "^4.3.6",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.6"
|
||||
"vite": "^7.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.10.1",
|
||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
"@tauri-apps/plugin-opener": "^2.5.3",
|
||||
"@tauri-apps/plugin-updater": "^2.10.0",
|
||||
"@tiptap/core": "^3.19.0",
|
||||
"@tiptap/extension-code-block-lowlight": "^3.19.0",
|
||||
@@ -61,16 +57,12 @@
|
||||
"@tiptap/extension-underline": "^3.19.0",
|
||||
"@tiptap/pm": "^3.19.0",
|
||||
"@tiptap/starter-kit": "^3.19.0",
|
||||
"@types/katex": "^0.16.8",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"highlight.js": "^11.11.1",
|
||||
"katex": "^0.16.28",
|
||||
"lowlight": "^3.3.0",
|
||||
"markdown-it": "^14.3.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-mark": "^4.0.0",
|
||||
"markdown-it-sub": "^2.0.0",
|
||||
"markdown-it-sup": "^2.0.0",
|
||||
"markdown-it-task-lists": "^2.1.1",
|
||||
"mermaid": "^11.16.1"
|
||||
"markdown-it-task-lists": "^2.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
overrides:
|
||||
cookie: 0.7.2
|
||||
@@ -1,15 +1,11 @@
|
||||
[package]
|
||||
name = "helixnotes"
|
||||
version = "1.3.5"
|
||||
description = "Local markdown note-taking app"
|
||||
version = "1.0.2"
|
||||
description = "Local-first markdown note-taking app"
|
||||
authors = ["HelixNotes"]
|
||||
license = "AGPL-3.0-or-later"
|
||||
edition = "2021"
|
||||
rust-version = "1.88"
|
||||
|
||||
[lints.clippy]
|
||||
# This style lint is version-dependent and produces over 100 legacy-only diagnostics on Rust 1.88.
|
||||
uninlined_format_args = "allow"
|
||||
rust-version = "1.77.2"
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
@@ -18,23 +14,20 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dev-dependencies]
|
||||
tauri = { version = "2", features = ["test"] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = ["protocol-asset", "tray-icon", "image-png"] }
|
||||
tauri-plugin-log = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-opener = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9"
|
||||
log = "0.4"
|
||||
notify = "8"
|
||||
walkdir = "2"
|
||||
chrono = { version = "0.4", features = ["serde", "unstable-locales"] }
|
||||
sys-locale = "0.3"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
tantivy = "0.22"
|
||||
gray_matter = "0.2"
|
||||
@@ -42,23 +35,6 @@ tokio = { version = "1", features = ["full"] }
|
||||
dirs = "6"
|
||||
regex = "1"
|
||||
zip = { version = "2", default-features = false, features = ["deflate"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls", "blocking"] }
|
||||
reqwest = { version = "0.12", features = ["json", "stream"] }
|
||||
futures = "0.3"
|
||||
rayon = "1"
|
||||
quick-xml = "0.41"
|
||||
urlencoding = "2"
|
||||
sha2 = "0.10"
|
||||
rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] }
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2-app-kit = "0.3.2"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
tauri-plugin-ios-vault-access = { path = "plugins/ios-vault-access" }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-updater = "2"
|
||||
tauri-plugin-single-instance = "2"
|
||||
tauri-plugin-window-state = "2"
|
||||
arboard = { version = "3", features = ["image-data", "wayland-data-control"] }
|
||||
png = "0.17"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] }
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?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>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,3 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
@@ -2,28 +2,49 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "HelixNotes default capabilities",
|
||||
"windows": ["main", "note-*"],
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:window:default",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-start-resize-dragging",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-is-maximized",
|
||||
"core:window:allow-set-decorations",
|
||||
"core:window:allow-is-fullscreen",
|
||||
"core:window:allow-set-fullscreen",
|
||||
"core:window:allow-set-title",
|
||||
"core:webview:allow-create-webview-window",
|
||||
"dialog:default",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-save",
|
||||
"dialog:allow-message",
|
||||
"dialog:allow-ask",
|
||||
"dialog:allow-confirm",
|
||||
"fs:allow-read-file"
|
||||
"fs:default",
|
||||
"fs:allow-read",
|
||||
"fs:allow-write",
|
||||
"fs:allow-exists",
|
||||
"fs:allow-mkdir",
|
||||
"fs:allow-remove",
|
||||
"fs:allow-rename",
|
||||
"fs:allow-copy-file",
|
||||
"fs:allow-stat",
|
||||
"fs:allow-read-dir",
|
||||
"fs:allow-read-file",
|
||||
"fs:allow-write-file",
|
||||
"fs:allow-watch",
|
||||
"opener:default",
|
||||
{
|
||||
"identifier": "opener:allow-open-path",
|
||||
"allow": [{ "path": "/**" }]
|
||||
},
|
||||
{
|
||||
"identifier": "opener:allow-open-url",
|
||||
"allow": [
|
||||
{ "url": "https://**" },
|
||||
{ "url": "http://**" },
|
||||
{ "url": "mailto:*" }
|
||||
]
|
||||
},
|
||||
"opener:allow-reveal-item-in-dir",
|
||||
"updater:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "desktop",
|
||||
"description": "Desktop-only capabilities",
|
||||
"platforms": ["linux", "macOS", "windows"],
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"updater:default",
|
||||
"window-state:default",
|
||||
"core:webview:allow-set-webview-zoom"
|
||||
]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?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>
|
||||
<!-- Hardened runtime entitlements for Developer ID distribution (outside Mac App Store).
|
||||
Required for notarization. These do NOT enable App Sandbox.
|
||||
Without these, the WebKit webview and bundled Rust libraries get killed by the
|
||||
hardened runtime's library validation. -->
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<!-- Network access for auto-updater, AI features, WebDAV sync, image proxy -->
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,12 +0,0 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
@@ -1,19 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
key.properties
|
||||
|
||||
/.tauri
|
||||
/tauri.settings.gradle
|
||||
@@ -1,6 +0,0 @@
|
||||
/src/main/java/com/helixnotes/app/generated
|
||||
/src/main/jniLibs/**/*.so
|
||||
/src/main/assets/tauri.conf.json
|
||||
/tauri.build.gradle.kts
|
||||
/proguard-tauri.pro
|
||||
/tauri.properties
|
||||
@@ -1,92 +0,0 @@
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("rust")
|
||||
}
|
||||
|
||||
val tauriProperties = Properties().apply {
|
||||
val propFile = file("tauri.properties")
|
||||
if (propFile.exists()) {
|
||||
propFile.inputStream().use { load(it) }
|
||||
}
|
||||
}
|
||||
|
||||
val keystoreProperties = Properties().apply {
|
||||
val propFile = rootProject.file("keystore.properties")
|
||||
if (propFile.exists()) {
|
||||
propFile.inputStream().use { load(it) }
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 36
|
||||
namespace = "com.helixnotes.app"
|
||||
|
||||
if (keystoreProperties.containsKey("storeFile")) {
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
storeFile = file(keystoreProperties["storeFile"] as String)
|
||||
storePassword = keystoreProperties["storePassword"] as String
|
||||
keyAlias = keystoreProperties["keyAlias"] as String
|
||||
keyPassword = keystoreProperties["keyPassword"] as String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
applicationId = "com.helixnotes.app"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
|
||||
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
|
||||
}
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
|
||||
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
|
||||
jniLibs.keepDebugSymbols.add("*/x86/*.so")
|
||||
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
|
||||
}
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
*fileTree(".") { include("**/*.pro") }
|
||||
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
.toList().toTypedArray()
|
||||
)
|
||||
if (signingConfigs.names.contains("release")) {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
rust {
|
||||
rootDirRel = "../../../"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.webkit:webkit:1.14.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
implementation("androidx.activity:activity-ktx:1.10.1")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
||||
}
|
||||
|
||||
apply(from = "tauri.build.gradle.kts")
|
||||
@@ -1,22 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Preserve JS interface annotations
|
||||
-keepattributes JavascriptInterface
|
||||
|
||||
# Keep our custom WebView JS bridge and all its public methods
|
||||
-keep class com.helixnotes.app.StorageBridge {
|
||||
public *;
|
||||
}
|
||||
|
||||
# Keep MainActivity public methods (called from StorageBridge)
|
||||
-keep class com.helixnotes.app.MainActivity {
|
||||
public *;
|
||||
}
|
||||
|
||||
# Ignore missing java.beans classes (referenced by Jackson but not available on Android)
|
||||
-dontwarn java.beans.**
|
||||
@@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
|
||||
<!-- AndroidTV support -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/Theme.helixnotes"
|
||||
android:usesCleartextTraffic="${usesCleartextTraffic}">
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/main_activity_title"
|
||||
android:name=".MainActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<!-- AndroidTV support -->
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,157 +0,0 @@
|
||||
package com.helixnotes.app
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import java.io.File
|
||||
|
||||
class MainActivity : TauriActivity() {
|
||||
private var webView: WebView? = null
|
||||
|
||||
companion object {
|
||||
private const val STORAGE_PERMISSION_REQUEST_CODE = 1001
|
||||
}
|
||||
|
||||
override fun onWebViewCreate(webView: WebView) {
|
||||
super.onWebViewCreate(webView)
|
||||
this.webView = webView
|
||||
webView.addJavascriptInterface(StorageBridge(this), "Android")
|
||||
// Render text at the size CSS specifies, ignoring the device's system font-size
|
||||
// accessibility setting (textZoom is otherwise tied to the system font scale). (#100)
|
||||
webView.settings.textZoom = 100
|
||||
// The actual fix: disable WebView "text autosizing" / font-boosting, which recomputes
|
||||
// paragraph font-size and line-height from viewport heuristics and ignores our CSS (only
|
||||
// size + line-height were affected; font-family was fine). NORMAL turns it off. (#100)
|
||||
webView.settings.layoutAlgorithm = android.webkit.WebSettings.LayoutAlgorithm.NORMAL
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (hasStoragePermission()) {
|
||||
webView?.evaluateJavascript(
|
||||
"window.__storagePermissionGranted && window.__storagePermissionGranted()",
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasStoragePermission(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// Android 11+ (API 30+): "All files access" special permission.
|
||||
Environment.isExternalStorageManager()
|
||||
} else {
|
||||
// Android 7-10 (API 24-29): legacy runtime storage permission.
|
||||
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
|
||||
fun requestStoragePermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Environment.isExternalStorageManager()) return
|
||||
try {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
intent.data = Uri.parse("package:$packageName")
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
val fallback = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||
startActivity(fallback)
|
||||
} catch (_: Exception) {
|
||||
val details = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
details.data = Uri.parse("package:$packageName")
|
||||
startActivity(details)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Android 7-10: request the legacy runtime permission directly.
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
),
|
||||
STORAGE_PERMISSION_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == STORAGE_PERMISSION_REQUEST_CODE && hasStoragePermission()) {
|
||||
webView?.evaluateJavascript(
|
||||
"window.__storagePermissionGranted && window.__storagePermissionGranted()",
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareVaultDir(path: String): Boolean {
|
||||
return try {
|
||||
val dir = File(path)
|
||||
dir.mkdirs()
|
||||
File(dir, ".helixnotes/trash").mkdirs()
|
||||
File(dir, ".helixnotes/attachments").mkdirs()
|
||||
dir.exists() && dir.canWrite()
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun openFile(path: String) {
|
||||
try {
|
||||
val file = File(path)
|
||||
if (!file.exists()) return
|
||||
val uri = FileProvider.getUriForFile(this, "${packageName}.fileprovider", file)
|
||||
val ext = MimeTypeMap.getFileExtensionFromUrl(path)
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) ?: "*/*"
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uri, mime)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("HelixNotes", "Failed to open file: $path", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StorageBridge(private val activity: MainActivity) {
|
||||
@JavascriptInterface
|
||||
fun hasStoragePermission(): Boolean {
|
||||
return activity.hasStoragePermission()
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun requestStoragePermission() {
|
||||
activity.runOnUiThread {
|
||||
activity.requestStoragePermission()
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun prepareVaultDir(path: String): Boolean {
|
||||
return activity.prepareVaultDir(path)
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun openFile(path: String) {
|
||||
activity.runOnUiThread {
|
||||
activity.openFile(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#5B6ABF"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
</vector>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
@@ -1,8 +0,0 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme (dark mode). -->
|
||||
<style name="Theme.helixnotes" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<item name="android:windowBackground">#1a1b26</item>
|
||||
<item name="android:navigationBarColor">#1a1b26</item>
|
||||
<item name="android:statusBarColor">#1a1b26</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#626ED4</color>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">HelixNotes</string>
|
||||
<string name="main_activity_title">HelixNotes</string>
|
||||
</resources>
|
||||
@@ -1,9 +0,0 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme (light mode). -->
|
||||
<style name="Theme.helixnotes" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<item name="android:windowBackground">#ffffff</item>
|
||||
<item name="android:navigationBarColor">#ffffff</item>
|
||||
<item name="android:statusBarColor">#ffffff</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
@@ -1,22 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.11.0")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("clean").configure {
|
||||
delete("build")
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("pluginsForCoolKids") {
|
||||
id = "rust"
|
||||
implementationClass = "RustPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(gradleApi())
|
||||
implementation("com.android.tools.build:gradle:8.11.0")
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import java.io.File
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
open class BuildTask : DefaultTask() {
|
||||
@Input
|
||||
var rootDirRel: String? = null
|
||||
@Input
|
||||
var target: String? = null
|
||||
@Input
|
||||
var release: Boolean? = null
|
||||
|
||||
@TaskAction
|
||||
fun assemble() {
|
||||
val executable = """pnpm""";
|
||||
try {
|
||||
runTauriCli(executable)
|
||||
} catch (e: Exception) {
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
// Try different Windows-specific extensions
|
||||
val fallbacks = listOf(
|
||||
"$executable.exe",
|
||||
"$executable.cmd",
|
||||
"$executable.bat",
|
||||
)
|
||||
|
||||
var lastException: Exception = e
|
||||
for (fallback in fallbacks) {
|
||||
try {
|
||||
runTauriCli(fallback)
|
||||
return
|
||||
} catch (fallbackException: Exception) {
|
||||
lastException = fallbackException
|
||||
}
|
||||
}
|
||||
throw lastException
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun runTauriCli(executable: String) {
|
||||
val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null")
|
||||
val target = target ?: throw GradleException("target cannot be null")
|
||||
val release = release ?: throw GradleException("release cannot be null")
|
||||
val args = listOf("tauri", "android", "android-studio-script");
|
||||
|
||||
project.exec {
|
||||
workingDir(File(project.projectDir, rootDirRel))
|
||||
executable(executable)
|
||||
args(args)
|
||||
if (project.logger.isEnabled(LogLevel.DEBUG)) {
|
||||
args("-vv")
|
||||
} else if (project.logger.isEnabled(LogLevel.INFO)) {
|
||||
args("-v")
|
||||
}
|
||||
if (release) {
|
||||
args("--release")
|
||||
}
|
||||
args(listOf("--target", target))
|
||||
}.assertNormalExitValue()
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.configure
|
||||
import org.gradle.kotlin.dsl.get
|
||||
|
||||
const val TASK_GROUP = "rust"
|
||||
|
||||
open class Config {
|
||||
lateinit var rootDirRel: String
|
||||
}
|
||||
|
||||
open class RustPlugin : Plugin<Project> {
|
||||
private lateinit var config: Config
|
||||
|
||||
override fun apply(project: Project) = with(project) {
|
||||
config = extensions.create("rust", Config::class.java)
|
||||
|
||||
val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64");
|
||||
val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList
|
||||
|
||||
val defaultArchList = listOf("arm64", "arm", "x86", "x86_64");
|
||||
val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList
|
||||
|
||||
val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64")
|
||||
|
||||
extensions.configure<ApplicationExtension> {
|
||||
@Suppress("UnstableApiUsage")
|
||||
flavorDimensions.add("abi")
|
||||
productFlavors {
|
||||
create("universal") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += abiList
|
||||
}
|
||||
}
|
||||
defaultArchList.forEachIndexed { index, arch ->
|
||||
create(arch) {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters.add(defaultAbiList[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
for (profile in listOf("debug", "release")) {
|
||||
val profileCapitalized = profile.replaceFirstChar { it.uppercase() }
|
||||
val buildTask = tasks.maybeCreate(
|
||||
"rustBuildUniversal$profileCapitalized",
|
||||
DefaultTask::class.java
|
||||
).apply {
|
||||
group = TASK_GROUP
|
||||
description = "Build dynamic library in $profile mode for all targets"
|
||||
}
|
||||
|
||||
tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask)
|
||||
|
||||
for (targetPair in targetsList.withIndex()) {
|
||||
val targetName = targetPair.value
|
||||
val targetArch = archList[targetPair.index]
|
||||
val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() }
|
||||
val targetBuildTask = project.tasks.maybeCreate(
|
||||
"rustBuild$targetArchCapitalized$profileCapitalized",
|
||||
BuildTask::class.java
|
||||
).apply {
|
||||
group = TASK_GROUP
|
||||
description = "Build dynamic library in $profile mode for $targetArch"
|
||||
rootDirRel = config.rootDirRel
|
||||
target = targetName
|
||||
release = profile == "release"
|
||||
}
|
||||
|
||||
buildTask.dependsOn(targetBuildTask)
|
||||
tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn(
|
||||
targetBuildTask
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonFinalResIds=false
|
||||
@@ -1,6 +0,0 @@
|
||||
#Tue May 10 19:22:52 CST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
@@ -1,185 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -1,89 +0,0 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,3 +0,0 @@
|
||||
include ':app'
|
||||
|
||||
apply from: 'tauri.settings.gradle'
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 709 B After Width: | Height: | Size: 953 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 556 B After Width: | Height: | Size: 793 B |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 961 B After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 984 B After Width: | Height: | Size: 1.6 KiB |
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(85.333 85.333) scale(0.666667)">
|
||||
<line x1="178.45584" y1="178.45584" x2="333.54416" y2="333.54416" stroke="#ffffff" stroke-width="25" stroke-opacity="0.5" stroke-linecap="round"/>
|
||||
<line x1="337.53708" y1="174.46292" x2="174.46294" y2="337.53708" stroke="#ffffff" stroke-width="25" stroke-opacity="0.5" stroke-linecap="round"/>
|
||||
<circle cx="153" cy="153" r="36" fill="#ffffff"/>
|
||||
<circle cx="359" cy="153" r="36" fill="#ffffff"/>
|
||||
<circle cx="153" cy="359" r="36" fill="#ffffff"/>
|
||||
<circle cx="359" cy="359" r="36" fill="#ffffff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 706 B |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 17 KiB |
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xmlns="http://www.w3.org/2000/svg"><rect
|
||||
width="512"
|
||||
height="512"
|
||||
rx="112"
|
||||
ry="112"
|
||||
fill="#626ED4"
|
||||
id="rect1" /><line
|
||||
x1="178.45584"
|
||||
y1="178.45584"
|
||||
x2="333.54416"
|
||||
y2="333.54416"
|
||||
stroke="#ffffff"
|
||||
stroke-width="25"
|
||||
stroke-opacity="0.5"
|
||||
stroke-linecap="round"
|
||||
id="line1" /><line
|
||||
x1="337.53708"
|
||||
y1="174.46292"
|
||||
x2="174.46294"
|
||||
y2="337.53708"
|
||||
stroke="#ffffff"
|
||||
stroke-width="25"
|
||||
stroke-opacity="0.5"
|
||||
stroke-linecap="round"
|
||||
id="line2" /><circle
|
||||
cx="153"
|
||||
cy="153"
|
||||
r="36"
|
||||
fill="#ffffff"
|
||||
id="circle2" /><circle
|
||||
cx="359"
|
||||
cy="153"
|
||||
r="36"
|
||||
fill="#ffffff"
|
||||
id="circle3" /><circle
|
||||
cx="153"
|
||||
cy="359"
|
||||
r="36"
|
||||
fill="#ffffff"
|
||||
id="circle4" /><circle
|
||||
cx="359"
|
||||
cy="359"
|
||||
r="36"
|
||||
fill="#ffffff"
|
||||
id="circle5" /></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 475 B |
|
Before Width: | Height: | Size: 788 B |
|
Before Width: | Height: | Size: 788 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 788 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"themes": [
|
||||
{
|
||||
"colors": {
|
||||
"bg_active": "#373a52",
|
||||
"bg_editor": "#1a1b26",
|
||||
"bg_hover": "#2f3145",
|
||||
"bg_primary": "#1a1b26",
|
||||
"bg_secondary": "#1f2029",
|
||||
"bg_tertiary": "#2a2b3d",
|
||||
"border_color": "#2f3145",
|
||||
"text_primary": "#c8cde4",
|
||||
"text_secondary": "#6b7094"
|
||||
},
|
||||
"id": "custom-1782146306429",
|
||||
"is_dark": true,
|
||||
"name": "My theme"
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "tauri-plugin-ios-vault-access"
|
||||
version = "0.1.0"
|
||||
authors = ["HelixNotes"]
|
||||
description = "iOS Files folder access for HelixNotes"
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
links = "tauri-plugin-ios-vault-access"
|
||||
|
||||
[dependencies]
|
||||
tauri = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2", features = ["build"] }
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(&[]).ios_path("ios").build();
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "tauri-plugin-ios-vault-access",
|
||||
platforms: [.iOS(.v14)],
|
||||
products: [
|
||||
.library(
|
||||
name: "tauri-plugin-ios-vault-access",
|
||||
type: .static,
|
||||
targets: ["tauri-plugin-ios-vault-access"]
|
||||
)
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "Tauri", path: "../.tauri/tauri-api")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "tauri-plugin-ios-vault-access",
|
||||
dependencies: [.byName(name: "Tauri")],
|
||||
path: "Sources"
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -1,312 +0,0 @@
|
||||
import Foundation
|
||||
import SwiftRs
|
||||
import Tauri
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
private struct BookmarkRequest: Decodable {
|
||||
let bookmarkId: String
|
||||
}
|
||||
|
||||
final class IosVaultAccessPlugin: Plugin, UIDocumentPickerDelegate {
|
||||
private let fileManager = FileManager.default
|
||||
private let lock = NSLock()
|
||||
private var pickerInvoke: Invoke?
|
||||
private var activeBookmarkId: String?
|
||||
private var activeURL: URL?
|
||||
private var stagedBookmarkId: String?
|
||||
private var stagedURL: URL?
|
||||
|
||||
deinit {
|
||||
rollbackStagedScope()
|
||||
releaseCurrentScope()
|
||||
}
|
||||
|
||||
@objc func chooseFolder(_ invoke: Invoke) {
|
||||
DispatchQueue.main.async {
|
||||
guard self.pickerInvoke == nil else {
|
||||
invoke.reject("A folder picker is already open.")
|
||||
return
|
||||
}
|
||||
guard let viewController = self.manager.viewController else {
|
||||
self.pickerInvoke = nil
|
||||
invoke.reject("Files is unavailable right now. Try again.")
|
||||
return
|
||||
}
|
||||
self.pickerInvoke = invoke
|
||||
let picker = UIDocumentPickerViewController(
|
||||
forOpeningContentTypes: [.folder],
|
||||
asCopy: false
|
||||
)
|
||||
picker.delegate = self
|
||||
picker.allowsMultipleSelection = false
|
||||
viewController.present(picker, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func resolveFolder(_ invoke: Invoke) throws {
|
||||
let request = try invoke.parseArgs(BookmarkRequest.self)
|
||||
do {
|
||||
invoke.resolve(try resolveBookmark(request.bookmarkId))
|
||||
} catch {
|
||||
invoke.reject(message(for: error))
|
||||
}
|
||||
}
|
||||
|
||||
@objc func releaseActive(_ invoke: Invoke) {
|
||||
rollbackStagedScope()
|
||||
releaseCurrentScope()
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@objc func commitStaged(_ invoke: Invoke) {
|
||||
commitStagedScope()
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@objc func rollbackStaged(_ invoke: Invoke) {
|
||||
rollbackStagedScope()
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@objc func forgetBookmark(_ invoke: Invoke) throws {
|
||||
let request = try invoke.parseArgs(BookmarkRequest.self)
|
||||
do {
|
||||
let file = try bookmarkFile(for: request.bookmarkId, createDirectory: false)
|
||||
lock.lock()
|
||||
let isActive = activeBookmarkId == request.bookmarkId
|
||||
let isStaged = stagedBookmarkId == request.bookmarkId
|
||||
lock.unlock()
|
||||
if fileManager.fileExists(atPath: file.path) {
|
||||
try fileManager.removeItem(at: file)
|
||||
}
|
||||
if isActive {
|
||||
releaseCurrentScope()
|
||||
}
|
||||
if isStaged {
|
||||
rollbackStagedScope()
|
||||
}
|
||||
invoke.resolve()
|
||||
} catch {
|
||||
invoke.reject(message(for: error))
|
||||
}
|
||||
}
|
||||
|
||||
func documentPicker(
|
||||
_ controller: UIDocumentPickerViewController,
|
||||
didPickDocumentsAt urls: [URL]
|
||||
) {
|
||||
guard let invoke = takePickerInvoke() else { return }
|
||||
guard let url = urls.first else {
|
||||
invoke.resolve(cancelledResult())
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
guard url.startAccessingSecurityScopedResource() else {
|
||||
throw AccessError.denied
|
||||
}
|
||||
var adopted = false
|
||||
defer {
|
||||
if !adopted { url.stopAccessingSecurityScopedResource() }
|
||||
}
|
||||
|
||||
let name = try validateFolder(url)
|
||||
let bookmarkId = existingBookmarkId(for: url) ?? UUID().uuidString.lowercased()
|
||||
let bookmark = try url.bookmarkData(
|
||||
options: .minimalBookmark,
|
||||
includingResourceValuesForKeys: nil,
|
||||
relativeTo: nil
|
||||
)
|
||||
try bookmark.write(to: bookmarkFile(for: bookmarkId, createDirectory: true), options: .atomic)
|
||||
stageScope(url: url, bookmarkId: bookmarkId)
|
||||
adopted = true
|
||||
invoke.resolve(result(bookmarkId: bookmarkId, url: url, name: name))
|
||||
} catch {
|
||||
invoke.reject(message(for: error))
|
||||
}
|
||||
}
|
||||
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
||||
takePickerInvoke()?.resolve(cancelledResult())
|
||||
}
|
||||
|
||||
private func resolveBookmark(_ bookmarkId: String) throws -> [String: Any] {
|
||||
guard UUID(uuidString: bookmarkId) != nil else { throw AccessError.missing }
|
||||
|
||||
lock.lock()
|
||||
let currentURL = activeBookmarkId == bookmarkId ? activeURL : nil
|
||||
lock.unlock()
|
||||
if let currentURL {
|
||||
return result(bookmarkId: bookmarkId, url: currentURL, name: try validateFolder(currentURL))
|
||||
}
|
||||
|
||||
let data = try Data(contentsOf: bookmarkFile(for: bookmarkId, createDirectory: false))
|
||||
var stale = false
|
||||
let url = try URL(
|
||||
resolvingBookmarkData: data,
|
||||
options: [],
|
||||
relativeTo: nil,
|
||||
bookmarkDataIsStale: &stale
|
||||
)
|
||||
guard url.startAccessingSecurityScopedResource() else { throw AccessError.denied }
|
||||
var adopted = false
|
||||
defer {
|
||||
if !adopted { url.stopAccessingSecurityScopedResource() }
|
||||
}
|
||||
|
||||
let name = try validateFolder(url)
|
||||
if stale {
|
||||
let refreshed = try url.bookmarkData(
|
||||
options: .minimalBookmark,
|
||||
includingResourceValuesForKeys: nil,
|
||||
relativeTo: nil
|
||||
)
|
||||
try refreshed.write(to: bookmarkFile(for: bookmarkId, createDirectory: true), options: .atomic)
|
||||
}
|
||||
stageScope(url: url, bookmarkId: bookmarkId)
|
||||
adopted = true
|
||||
return result(bookmarkId: bookmarkId, url: url, name: name)
|
||||
}
|
||||
|
||||
private func validateFolder(_ url: URL) throws -> String {
|
||||
let values = try url.resourceValues(forKeys: [.isDirectoryKey, .nameKey])
|
||||
guard values.isDirectory == true else { throw AccessError.notFolder }
|
||||
guard try url.checkResourceIsReachable() else { throw AccessError.unavailable }
|
||||
return values.name ?? url.lastPathComponent
|
||||
}
|
||||
|
||||
private func stageScope(url: URL, bookmarkId: String) {
|
||||
lock.lock()
|
||||
let previous = stagedURL
|
||||
stagedURL = url
|
||||
stagedBookmarkId = bookmarkId
|
||||
lock.unlock()
|
||||
previous?.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
|
||||
private func commitStagedScope() {
|
||||
lock.lock()
|
||||
guard let next = stagedURL else {
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
let previous = activeURL
|
||||
activeURL = next
|
||||
activeBookmarkId = stagedBookmarkId
|
||||
stagedURL = nil
|
||||
stagedBookmarkId = nil
|
||||
lock.unlock()
|
||||
previous?.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
|
||||
private func rollbackStagedScope() {
|
||||
lock.lock()
|
||||
let staged = stagedURL
|
||||
stagedURL = nil
|
||||
stagedBookmarkId = nil
|
||||
lock.unlock()
|
||||
staged?.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
|
||||
private func releaseCurrentScope() {
|
||||
lock.lock()
|
||||
let previous = activeURL
|
||||
activeURL = nil
|
||||
activeBookmarkId = nil
|
||||
lock.unlock()
|
||||
previous?.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
|
||||
private func takePickerInvoke() -> Invoke? {
|
||||
let invoke = pickerInvoke
|
||||
pickerInvoke = nil
|
||||
return invoke
|
||||
}
|
||||
|
||||
private func bookmarkDirectory(create: Bool) throws -> URL {
|
||||
let base = try fileManager.url(
|
||||
for: .applicationSupportDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: nil,
|
||||
create: true
|
||||
)
|
||||
let directory = base.appendingPathComponent("VaultBookmarks", isDirectory: true)
|
||||
if create {
|
||||
try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
}
|
||||
return directory
|
||||
}
|
||||
|
||||
private func bookmarkFile(for id: String, createDirectory: Bool) throws -> URL {
|
||||
guard UUID(uuidString: id) != nil else { throw AccessError.missing }
|
||||
let directory = try bookmarkDirectory(create: createDirectory)
|
||||
return directory.appendingPathComponent("\(id).bookmark")
|
||||
}
|
||||
|
||||
private func existingBookmarkId(for selectedURL: URL) -> String? {
|
||||
guard
|
||||
let directory = try? bookmarkDirectory(create: true),
|
||||
let files = try? fileManager.contentsOfDirectory(
|
||||
at: directory,
|
||||
includingPropertiesForKeys: nil
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
let selectedPath = selectedURL.standardizedFileURL.path
|
||||
for file in files where file.pathExtension == "bookmark" {
|
||||
guard let data = try? Data(contentsOf: file) else { continue }
|
||||
var stale = false
|
||||
guard
|
||||
let resolved = try? URL(
|
||||
resolvingBookmarkData: data,
|
||||
options: [],
|
||||
relativeTo: nil,
|
||||
bookmarkDataIsStale: &stale
|
||||
),
|
||||
resolved.standardizedFileURL.path == selectedPath
|
||||
else { continue }
|
||||
let id = file.deletingPathExtension().lastPathComponent
|
||||
if UUID(uuidString: id) != nil { return id }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func result(bookmarkId: String, url: URL, name: String) -> [String: Any] {
|
||||
["bookmarkId": bookmarkId, "path": url.path, "name": name, "cancelled": false]
|
||||
}
|
||||
|
||||
private func cancelledResult() -> [String: Any] {
|
||||
["bookmarkId": "", "path": "", "name": "", "cancelled": true]
|
||||
}
|
||||
|
||||
private func message(for error: Error) -> String {
|
||||
if let accessError = error as? AccessError { return accessError.localizedDescription }
|
||||
return "The selected folder is unavailable. Open Files and try again."
|
||||
}
|
||||
}
|
||||
|
||||
private enum AccessError: LocalizedError {
|
||||
case denied
|
||||
case missing
|
||||
case notFolder
|
||||
case unavailable
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .denied:
|
||||
return "HelixNotes no longer has access to this folder. Choose it again in Files."
|
||||
case .missing:
|
||||
return "Saved access to this folder is missing. Choose it again in Files."
|
||||
case .notFolder:
|
||||
return "The selected item is not a folder."
|
||||
case .unavailable:
|
||||
return "The selected folder is unavailable. Open Files and try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_ios_vault_access")
|
||||
func initPlugin() -> Plugin {
|
||||
IosVaultAccessPlugin()
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{
|
||||
plugin::{Builder, PluginHandle, TauriPlugin},
|
||||
Manager, Runtime,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FolderSelection {
|
||||
pub bookmark_id: String,
|
||||
pub path: String,
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub cancelled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct BookmarkRequest<'a> {
|
||||
bookmark_id: &'a str,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
tauri::ios_plugin_binding!(init_plugin_ios_vault_access);
|
||||
|
||||
pub struct IosVaultAccess<R: Runtime>(PluginHandle<R>);
|
||||
|
||||
impl<R: Runtime> IosVaultAccess<R> {
|
||||
pub fn choose_folder(&self) -> Result<FolderSelection, String> {
|
||||
self.0
|
||||
.run_mobile_plugin("chooseFolder", ())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn resolve_folder(&self, bookmark_id: &str) -> Result<FolderSelection, String> {
|
||||
self.0
|
||||
.run_mobile_plugin("resolveFolder", BookmarkRequest { bookmark_id })
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn release_active(&self) -> Result<(), String> {
|
||||
self.0
|
||||
.run_mobile_plugin("releaseActive", ())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn commit_staged(&self) -> Result<(), String> {
|
||||
self.0
|
||||
.run_mobile_plugin("commitStaged", ())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn rollback_staged(&self) -> Result<(), String> {
|
||||
self.0
|
||||
.run_mobile_plugin("rollbackStaged", ())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn forget_bookmark(&self, bookmark_id: &str) -> Result<(), String> {
|
||||
self.0
|
||||
.run_mobile_plugin("forgetBookmark", BookmarkRequest { bookmark_id })
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IosVaultAccessExt<R: Runtime> {
|
||||
fn ios_vault_access(&self) -> &IosVaultAccess<R>;
|
||||
}
|
||||
|
||||
impl<R: Runtime, T: Manager<R>> IosVaultAccessExt<R> for T {
|
||||
fn ios_vault_access(&self) -> &IosVaultAccess<R> {
|
||||
self.state::<IosVaultAccess<R>>().inner()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("ios-vault-access")
|
||||
.setup(|app, api| {
|
||||
let handle = api.register_ios_plugin(init_plugin_ios_vault_access)?;
|
||||
app.manage(IosVaultAccess(handle));
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||