diff --git a/README.md b/README.md index 192c2a65..cd31a8d8 100644 --- a/README.md +++ b/README.md @@ -73,34 +73,34 @@ Run in dev mode: npm run tauri:dev ``` -## iOS Support (WIP) +## Mobile Support (WIP) -iOS support is currently in progress. +Mobile support is currently in progress for iOS and Android. -- Current status: mobile layout runs, remote backend flow is wired, and iOS defaults to remote backend mode. +- Current status: mobile layout runs, remote backend flow is wired, and mobile builds default to remote backend mode. - Current limits: terminal and dictation remain unavailable on mobile builds. - Desktop behavior is unchanged: macOS/Linux/Windows remain local-first unless remote mode is explicitly selected. -### iOS + Tailscale Setup (TCP) +### Mobile + Tailscale Setup (TCP) -Use this when connecting the iOS app to a desktop-hosted daemon over your Tailscale tailnet. +Use this when connecting the mobile app (iOS/Android) to a desktop-hosted daemon over your Tailscale tailnet. -1. Install and sign in to Tailscale on both desktop and iPhone (same tailnet). +1. Install and sign in to Tailscale on desktop and your mobile device (same tailnet). 2. On desktop CodexMonitor, open `Settings > Server`. 3. Keep `Remote provider` set to `TCP (wip)`. 4. Set a `Remote backend token`. 5. Start the desktop daemon with `Start daemon` (in `Mobile access daemon`). 6. In `Tailscale helper`, use `Detect Tailscale` and note the suggested host (for example `your-mac.your-tailnet.ts.net:4732`). -7. On iOS CodexMonitor, open `Settings > Server`. +7. On mobile CodexMonitor, open `Settings > Server`. 8. Set `Connection type` to `TCP`. 9. Enter the desktop Tailscale host and the same token. 10. Tap `Connect & test` and confirm it succeeds. Notes: -- The desktop daemon must stay running while iOS is connected. +- The desktop daemon must stay running while mobile clients are connected. - If the test fails, confirm both devices are online in Tailscale and that host/token match desktop settings. -- If you want to use Orbit instead of Tailscale TCP, switch `Connection type` to `Orbit` on iOS and use your desktop Orbit websocket URL/token. +- If you want to use Orbit instead of Tailscale TCP, switch `Connection type` to `Orbit` on mobile and use your desktop Orbit websocket URL/token. ### iOS Prerequisites @@ -130,7 +130,7 @@ Options: - `--skip-build` to reuse the current app bundle. - `--no-clean` to preserve `src-tauri/gen/apple/build` between builds. -### Run on USB Device +### Run on iOS USB Device List discoverable devices: @@ -162,6 +162,105 @@ If signing is not ready yet, open Xcode from the script flow: ./scripts/build_run_ios_device.sh --open-xcode ``` +### Android Prerequisites + +- Android Studio installed with Android SDK + SDK Platform + SDK Platform Tools. +- Android command line tools installed and `adb` available in `PATH`. +- JDK 17 configured for Gradle/Android builds. +- Rust Android targets installed: + +```bash +rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android +``` + +- Android environment variables configured (typical setup): + +```bash +export ANDROID_HOME="$HOME/Library/Android/sdk" +export ANDROID_SDK_ROOT="$ANDROID_HOME" +export NDK_HOME="$ANDROID_HOME/ndk/" +``` + +Android scripts auto-detect common SDK paths and the latest installed NDK, but explicit env vars are recommended for reproducible CI/dev setups. + +### Initialize Android Project Files + +```bash +./scripts/init_android.sh +``` + +### Run on Android (Dev) + +List connected Android devices/emulators: + +```bash +./scripts/build_run_android.sh --list-devices +``` + +Run dev mode on default/first available device: + +```bash +./scripts/build_run_android.sh +``` + +Run dev mode on a specific device: + +```bash +./scripts/build_run_android.sh --device "" +``` + +Optional: + +- Use `--host ` for physical-device testing when the dev server is on your local network. +- Use `--open` to open Android Studio. +- Use `--release` for release-mode run. + +### Build Android Artifacts (APK/AAB) + +```bash +./scripts/build_android.sh +``` + +Common options: + +- `--target aarch64 --target x86_64` for specific ABIs. +- `--split-per-abi` for split outputs. +- `--apk-only` or `--aab-only` to limit artifact types. +- `--debug` for debug build artifacts. + +Build outputs are generated under `src-tauri/gen/android/app/build/outputs/`. + +### Install on Android Phone + +Prerequisites on phone: + +1. Enable `Developer options`. +2. Enable `USB debugging`. +3. Connect phone via USB and authorize this computer on the phone prompt. + +Verify the device is visible: + +```bash +./scripts/build_run_android.sh --list-devices +``` + +Build and install debug APK: + +```bash +./scripts/build_android.sh --apk-only --debug +./scripts/install_android_apk.sh --device "" +``` + +The installer script also launches the app after install. + +Optional wireless adb (after first USB authorization): + +```bash +adb -s "" tcpip 5555 +adb connect ":5555" +./scripts/install_android_apk.sh --device ":5555" +``` + ### iOS TestFlight Release (Scripted) Use the end-to-end script to archive, upload, configure compliance, assign beta group, and submit for beta review. diff --git a/docs/android-support-plan.md b/docs/android-support-plan.md new file mode 100644 index 00000000..cadbdac4 --- /dev/null +++ b/docs/android-support-plan.md @@ -0,0 +1,12 @@ +# Android Support Implementation Plan + +- [ ] Confirm Android toolchain prerequisites and environment variables (`ANDROID_HOME`, `ANDROID_SDK_ROOT`, `JAVA_HOME`, `adb`, `emulator`). +- [ ] Add Android-specific Tauri config override (`src-tauri/tauri.android.conf.json`) with Android bundle identifier. +- [ ] Align backend mobile defaults so Android uses the same remote-first behavior as iOS. +- [ ] Add Android bootstrap script to initialize Tauri Android project files when missing. +- [ ] Add Android simulator/device run script for development (`tauri android dev`) with device listing support. +- [ ] Add Android release build script (`tauri android build`) with ABI and artifact options (APK/AAB, split per ABI). +- [ ] Update server/mobile UX copy that is iOS-only so Android users receive correct guidance. +- [ ] Document Android prerequisites and run/build workflows in `README.md`. +- [ ] Add or update frontend/backend tests covering Android/mobile detection and defaults. +- [ ] Run validation (`npm run lint`, `npm run test`, `npm run typecheck`, `cd src-tauri && cargo check`) and resolve integration issues. diff --git a/package.json b/package.json index 455e2f3a..ede47c20 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,15 @@ "pretauri:dev:win": "npm run sync:material-icons", "tauri:dev:win": "npm run doctor:win && tauri dev --config src-tauri/tauri.windows.conf.json", "pretauri:build:win": "npm run sync:material-icons", - "tauri:build:win": "npm run doctor:win && tauri build --config src-tauri/tauri.windows.conf.json" + "tauri:build:win": "npm run doctor:win && tauri build --config src-tauri/tauri.windows.conf.json", + "pretauri:android:init": "npm run sync:material-icons", + "tauri:android:init": "tauri android init", + "pretauri:android:dev": "npm run sync:material-icons", + "tauri:android:dev": "tauri android dev", + "pretauri:android:build": "npm run sync:material-icons", + "tauri:android:build": "tauri android build", + "pretauri:android:run": "npm run sync:material-icons", + "tauri:android:run": "tauri android run" }, "dependencies": { "@pierre/diffs": "^1.0.6", diff --git a/scripts/android_env.sh b/scripts/android_env.sh new file mode 100644 index 00000000..02ee36b6 --- /dev/null +++ b/scripts/android_env.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +ensure_android_env() { + local sdk_home="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}" + if [[ -z "$sdk_home" ]]; then + for candidate in "$HOME/Library/Android/sdk" "$HOME/Android/Sdk"; do + if [[ -d "$candidate" ]]; then + sdk_home="$candidate" + break + fi + done + fi + + if [[ -z "$sdk_home" ]]; then + return + fi + + export ANDROID_HOME="$sdk_home" + export ANDROID_SDK_ROOT="$sdk_home" + + local platform_tools_dir="$sdk_home/platform-tools" + if [[ -d "$platform_tools_dir" && ":$PATH:" != *":$platform_tools_dir:"* ]]; then + export PATH="$platform_tools_dir:$PATH" + fi + + local cmdline_tools_latest="$sdk_home/cmdline-tools/latest/bin" + if [[ -d "$cmdline_tools_latest" && ":$PATH:" != *":$cmdline_tools_latest:"* ]]; then + export PATH="$cmdline_tools_latest:$PATH" + fi + + if [[ -z "${NDK_HOME:-}" ]]; then + local ndk_dir="$sdk_home/ndk" + if [[ -d "$ndk_dir" ]]; then + local latest_ndk="" + latest_ndk="$(ls -1 "$ndk_dir" 2>/dev/null | sort | tail -n 1)" + if [[ -n "$latest_ndk" && -d "$ndk_dir/$latest_ndk" ]]; then + export NDK_HOME="$ndk_dir/$latest_ndk" + fi + fi + fi +} diff --git a/scripts/build_android.sh b/scripts/build_android.sh new file mode 100755 index 00000000..1ceb9504 --- /dev/null +++ b/scripts/build_android.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" +source "$ROOT_DIR/scripts/android_env.sh" + +declare -a TARGETS=() +DEBUG_MODE=0 +SPLIT_PER_ABI=0 +APK_ONLY=0 +AAB_ONLY=0 +OPEN_ANDROID_STUDIO=0 +CI_MODE=1 +SKIP_INIT=0 + +usage() { + cat <<'EOF' +Usage: scripts/build_android.sh [options] + +Builds Android artifacts (APK/AAB) using Tauri. + +Options: + --target Add target ABI (aarch64, armv7, i686, x86_64), repeatable + --debug Build debug artifacts + --split-per-abi Build split APK/AAB per ABI + --apk-only Build APKs only + --aab-only Build AABs only + --open Open Android Studio + --no-ci Allow interactive prompts + --skip-init Skip Android project initialization check + -h, --help Show this help +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --target) + TARGETS+=("${2:-}") + shift 2 + ;; + --debug) + DEBUG_MODE=1 + shift + ;; + --split-per-abi) + SPLIT_PER_ABI=1 + shift + ;; + --apk-only) + APK_ONLY=1 + shift + ;; + --aab-only) + AAB_ONLY=1 + shift + ;; + --open) + OPEN_ANDROID_STUDIO=1 + shift + ;; + --no-ci) + CI_MODE=0 + shift + ;; + --skip-init) + SKIP_INIT=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ "$APK_ONLY" -eq 1 && "$AAB_ONLY" -eq 1 ]]; then + echo "--apk-only and --aab-only cannot be used together." >&2 + exit 1 +fi + +resolve_npm() { + if command -v npm >/dev/null 2>&1; then + command -v npm + return + fi + + for candidate in /opt/homebrew/bin/npm /usr/local/bin/npm; do + if [[ -x "$candidate" ]]; then + echo "$candidate" + return + fi + done + + if [[ -n "${NVM_DIR:-}" && -s "${NVM_DIR}/nvm.sh" ]]; then + # shellcheck source=/dev/null + . "${NVM_DIR}/nvm.sh" + if command -v npm >/dev/null 2>&1; then + command -v npm + return + fi + fi + + return 1 +} + +if [[ "$SKIP_INIT" -eq 0 ]]; then + init_cmd=("$ROOT_DIR/scripts/init_android.sh") + if [[ "$CI_MODE" -eq 0 ]]; then + init_cmd+=(--no-ci) + fi + "${init_cmd[@]}" +fi + +ensure_android_env + +NPM_BIN="$(resolve_npm || true)" +if [[ -z "$NPM_BIN" ]]; then + echo "Unable to find npm in PATH or common install locations." >&2 + echo "Install Node/npm, or run from a shell where npm is available." >&2 + exit 1 +fi + +cmd=("$NPM_BIN" run tauri -- android build) +if [[ "$DEBUG_MODE" -eq 1 ]]; then + cmd+=(--debug) +fi +if [[ "${#TARGETS[@]}" -gt 0 ]]; then + cmd+=(--target "${TARGETS[@]}") +fi +if [[ "$SPLIT_PER_ABI" -eq 1 ]]; then + cmd+=(--split-per-abi) +fi +if [[ "$APK_ONLY" -eq 1 ]]; then + cmd+=(--apk true --aab false) +fi +if [[ "$AAB_ONLY" -eq 1 ]]; then + cmd+=(--apk false --aab true) +fi +if [[ "$OPEN_ANDROID_STUDIO" -eq 1 ]]; then + cmd+=(--open) +fi +if [[ "$CI_MODE" -eq 1 ]]; then + cmd+=(--ci) +fi + +"${cmd[@]}" + +if [[ -d "src-tauri/gen/android/app/build/outputs" ]]; then + echo + echo "Android artifacts:" + find src-tauri/gen/android/app/build/outputs -type f \ + \( -name '*.apk' -o -name '*.aab' \) | sort +fi diff --git a/scripts/build_run_android.sh b/scripts/build_run_android.sh new file mode 100755 index 00000000..c3a4ddc9 --- /dev/null +++ b/scripts/build_run_android.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" +source "$ROOT_DIR/scripts/android_env.sh" + +DEVICE="" +HOST="" +OPEN_ANDROID_STUDIO=0 +RELEASE_MODE=0 +NO_WATCH=0 +LIST_DEVICES=0 +SKIP_INIT=0 + +usage() { + cat <<'EOF' +Usage: scripts/build_run_android.sh [options] + +Builds and runs CodexMonitor in Android development mode. + +Options: + --device Run on a specific connected/emulated device + --host
Public dev-server host for physical device testing + --open Open Android Studio instead of auto-running on device + --release Run in release mode + --no-watch Disable file watching + --list-devices List devices via adb and exit + --skip-init Skip Android project initialization check + -h, --help Show this help +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --device) + DEVICE="${2:-}" + shift 2 + ;; + --host) + HOST="${2:-}" + shift 2 + ;; + --open) + OPEN_ANDROID_STUDIO=1 + shift + ;; + --release) + RELEASE_MODE=1 + shift + ;; + --no-watch) + NO_WATCH=1 + shift + ;; + --list-devices) + LIST_DEVICES=1 + shift + ;; + --skip-init) + SKIP_INIT=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +resolve_npm() { + if command -v npm >/dev/null 2>&1; then + command -v npm + return + fi + + for candidate in /opt/homebrew/bin/npm /usr/local/bin/npm; do + if [[ -x "$candidate" ]]; then + echo "$candidate" + return + fi + done + + if [[ -n "${NVM_DIR:-}" && -s "${NVM_DIR}/nvm.sh" ]]; then + # shellcheck source=/dev/null + . "${NVM_DIR}/nvm.sh" + if command -v npm >/dev/null 2>&1; then + command -v npm + return + fi + fi + + return 1 +} + +if [[ "$LIST_DEVICES" -eq 1 ]]; then + ensure_android_env + if ! command -v adb >/dev/null 2>&1; then + echo "adb is not available in PATH." >&2 + echo "Install Android Platform Tools and ensure adb is available." >&2 + exit 1 + fi + adb devices -l + exit 0 +fi + +if [[ "$SKIP_INIT" -eq 0 ]]; then + "$ROOT_DIR/scripts/init_android.sh" +fi + +ensure_android_env + +NPM_BIN="$(resolve_npm || true)" +if [[ -z "$NPM_BIN" ]]; then + echo "Unable to find npm in PATH or common install locations." >&2 + echo "Install Node/npm, or run from a shell where npm is available." >&2 + exit 1 +fi + +cmd=("$NPM_BIN" run tauri -- android dev) +if [[ "$OPEN_ANDROID_STUDIO" -eq 1 ]]; then + cmd+=(--open) +fi +if [[ "$RELEASE_MODE" -eq 1 ]]; then + cmd+=(--release) +fi +if [[ "$NO_WATCH" -eq 1 ]]; then + cmd+=(--no-watch) +fi +if [[ -n "$HOST" ]]; then + cmd+=(--host "$HOST") +fi +if [[ -n "$DEVICE" ]]; then + cmd+=("$DEVICE") +fi + +"${cmd[@]}" diff --git a/scripts/init_android.sh b/scripts/init_android.sh new file mode 100755 index 00000000..2bfc8240 --- /dev/null +++ b/scripts/init_android.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" +source "$ROOT_DIR/scripts/android_env.sh" + +ANDROID_GEN_DIR="src-tauri/gen/android" +FORCE_INIT=0 +SKIP_TARGETS_INSTALL=0 +CI_MODE=1 + +usage() { + cat <<'EOF' +Usage: scripts/init_android.sh [options] + +Initializes Tauri Android project files under src-tauri/gen/android. + +Options: + --force Re-run init even if src-tauri/gen/android exists + --skip-targets-install Do not install missing Rust Android targets + --no-ci Allow interactive prompts + -h, --help Show this help +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --force) + FORCE_INIT=1 + shift + ;; + --skip-targets-install) + SKIP_TARGETS_INSTALL=1 + shift + ;; + --no-ci) + CI_MODE=0 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +resolve_npm() { + if command -v npm >/dev/null 2>&1; then + command -v npm + return + fi + + for candidate in /opt/homebrew/bin/npm /usr/local/bin/npm; do + if [[ -x "$candidate" ]]; then + echo "$candidate" + return + fi + done + + if [[ -n "${NVM_DIR:-}" && -s "${NVM_DIR}/nvm.sh" ]]; then + # shellcheck source=/dev/null + . "${NVM_DIR}/nvm.sh" + if command -v npm >/dev/null 2>&1; then + command -v npm + return + fi + fi + + return 1 +} + +if [[ "$FORCE_INIT" -eq 0 && -d "$ANDROID_GEN_DIR" ]]; then + echo "Android project already initialized at ${ANDROID_GEN_DIR}." + exit 0 +fi + +ensure_android_env + +NPM_BIN="$(resolve_npm || true)" +if [[ -z "$NPM_BIN" ]]; then + echo "Unable to find npm in PATH or common install locations." >&2 + echo "Install Node/npm, or run from a shell where npm is available." >&2 + exit 1 +fi + +cmd=("$NPM_BIN" run tauri -- android init) +if [[ "$CI_MODE" -eq 1 ]]; then + cmd+=(--ci) +fi +if [[ "$SKIP_TARGETS_INSTALL" -eq 1 ]]; then + cmd+=(--skip-targets-install) +fi + +"${cmd[@]}" +echo "Android project initialized." diff --git a/scripts/install_android_apk.sh b/scripts/install_android_apk.sh new file mode 100755 index 00000000..a1ddc99d --- /dev/null +++ b/scripts/install_android_apk.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +PACKAGE_ID="com.dimillian.codexmonitor.android" +ACTIVITY_NAME=".MainActivity" +DEVICE_ID="" +APK_PATH="src-tauri/gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk" +LIST_DEVICES=0 +SKIP_LAUNCH=0 + +usage() { + cat <<'EOF' +Usage: scripts/install_android_apk.sh [options] + +Installs a built CodexMonitor APK to an Android device (phone or emulator) via adb. + +Options: + --device adb device id (default: first connected device) + --apk APK path (default: debug universal APK) + --list-devices List connected devices and exit + --skip-launch Install only (do not launch app) + -h, --help Show this help +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --device) + DEVICE_ID="${2:-}" + shift 2 + ;; + --apk) + APK_PATH="${2:-}" + shift 2 + ;; + --list-devices) + LIST_DEVICES=1 + shift + ;; + --skip-launch) + SKIP_LAUNCH=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if ! command -v adb >/dev/null 2>&1; then + echo "adb is not available in PATH." >&2 + exit 1 +fi + +if [[ "$LIST_DEVICES" -eq 1 ]]; then + adb devices -l + exit 0 +fi + +if [[ ! -f "$APK_PATH" ]]; then + echo "APK not found: $APK_PATH" >&2 + echo "Build one first with: ./scripts/build_android.sh --apk-only --debug" >&2 + exit 1 +fi + +if [[ -z "$DEVICE_ID" ]]; then + DEVICE_ID="$( + adb devices | awk 'NR>1 && $2=="device" {print $1; exit}' + )" +fi + +if [[ -z "$DEVICE_ID" ]]; then + echo "No connected Android device found." >&2 + echo "Run with --list-devices after connecting a phone/emulator." >&2 + exit 1 +fi + +echo "Installing $APK_PATH to $DEVICE_ID..." +adb -s "$DEVICE_ID" install -r -d "$APK_PATH" + +if [[ "$SKIP_LAUNCH" -eq 0 ]]; then + echo "Launching ${PACKAGE_ID}/${ACTIVITY_NAME} on $DEVICE_ID..." + adb -s "$DEVICE_ID" shell am start -n "${PACKAGE_ID}/${ACTIVITY_NAME}" +fi + +echo "Done." diff --git a/src-tauri/gen/android/.editorconfig b/src-tauri/gen/android/.editorconfig new file mode 100644 index 00000000..ebe51d3b --- /dev/null +++ b/src-tauri/gen/android/.editorconfig @@ -0,0 +1,12 @@ +# 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 \ No newline at end of file diff --git a/src-tauri/gen/android/.gitignore b/src-tauri/gen/android/.gitignore new file mode 100644 index 00000000..b2482031 --- /dev/null +++ b/src-tauri/gen/android/.gitignore @@ -0,0 +1,19 @@ +*.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 \ No newline at end of file diff --git a/src-tauri/gen/android/app/.gitignore b/src-tauri/gen/android/app/.gitignore new file mode 100644 index 00000000..ad42db2b --- /dev/null +++ b/src-tauri/gen/android/app/.gitignore @@ -0,0 +1,6 @@ +/src/main/java/com/dimillian/codexmonitor/android/generated +/src/main/jniLibs/**/*.so +/src/main/assets/tauri.conf.json +/tauri.build.gradle.kts +/proguard-tauri.pro +/tauri.properties \ No newline at end of file diff --git a/src-tauri/gen/android/app/build.gradle.kts b/src-tauri/gen/android/app/build.gradle.kts new file mode 100644 index 00000000..82959362 --- /dev/null +++ b/src-tauri/gen/android/app/build.gradle.kts @@ -0,0 +1,70 @@ +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) } + } +} + +android { + compileSdk = 36 + namespace = "com.dimillian.codexmonitor.android" + defaultConfig { + manifestPlaceholders["usesCleartextTraffic"] = "false" + applicationId = "com.dimillian.codexmonitor.android" + 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() + ) + } + } + 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") \ No newline at end of file diff --git a/src-tauri/gen/android/app/proguard-rules.pro b/src-tauri/gen/android/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/src-tauri/gen/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# 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 + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/src-tauri/gen/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..08a4b76d --- /dev/null +++ b/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-tauri/gen/android/app/src/main/java/com/dimillian/codexmonitor/android/MainActivity.kt b/src-tauri/gen/android/app/src/main/java/com/dimillian/codexmonitor/android/MainActivity.kt new file mode 100644 index 00000000..2bccb025 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/java/com/dimillian/codexmonitor/android/MainActivity.kt @@ -0,0 +1,11 @@ +package com.dimillian.codexmonitor.android + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge + +class MainActivity : TauriActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + } +} diff --git a/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml b/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml b/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..4fc24441 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..28f1aa11 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..85d0c88a Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..28f1aa11 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..73e48dbf Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..13dd2147 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..73e48dbf Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..1d98044f Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..a888b336 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..1d98044f Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..08183246 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..a2a838e7 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..08183246 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..b18bceb6 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..3f8a57f3 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..b18bceb6 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/values-night/themes.xml b/src-tauri/gen/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..b1b903e6 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/src-tauri/gen/android/app/src/main/res/values/colors.xml b/src-tauri/gen/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/values/strings.xml b/src-tauri/gen/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..74990fed --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + Codex Monitor + Codex Monitor + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/values/themes.xml b/src-tauri/gen/android/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..b1b903e6 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml b/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 00000000..782d63b9 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src-tauri/gen/android/build.gradle.kts b/src-tauri/gen/android/build.gradle.kts new file mode 100644 index 00000000..607240bc --- /dev/null +++ b/src-tauri/gen/android/build.gradle.kts @@ -0,0 +1,22 @@ +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") +} + diff --git a/src-tauri/gen/android/buildSrc/build.gradle.kts b/src-tauri/gen/android/buildSrc/build.gradle.kts new file mode 100644 index 00000000..5c55bba7 --- /dev/null +++ b/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -0,0 +1,23 @@ +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") +} + diff --git a/src-tauri/gen/android/buildSrc/src/main/java/com/dimillian/codexmonitor/android/kotlin/BuildTask.kt b/src-tauri/gen/android/buildSrc/src/main/java/com/dimillian/codexmonitor/android/kotlin/BuildTask.kt new file mode 100644 index 00000000..a3de1256 --- /dev/null +++ b/src-tauri/gen/android/buildSrc/src/main/java/com/dimillian/codexmonitor/android/kotlin/BuildTask.kt @@ -0,0 +1,68 @@ +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 = """npm"""; + 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("run", "--", "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() + } +} \ No newline at end of file diff --git a/src-tauri/gen/android/buildSrc/src/main/java/com/dimillian/codexmonitor/android/kotlin/RustPlugin.kt b/src-tauri/gen/android/buildSrc/src/main/java/com/dimillian/codexmonitor/android/kotlin/RustPlugin.kt new file mode 100644 index 00000000..4aa7fcaf --- /dev/null +++ b/src-tauri/gen/android/buildSrc/src/main/java/com/dimillian/codexmonitor/android/kotlin/RustPlugin.kt @@ -0,0 +1,85 @@ +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 { + 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 { + @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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/src-tauri/gen/android/gradle.properties b/src-tauri/gen/android/gradle.properties new file mode 100644 index 00000000..2a7ec695 --- /dev/null +++ b/src-tauri/gen/android/gradle.properties @@ -0,0 +1,24 @@ +# 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 \ No newline at end of file diff --git a/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..c5f9a53c --- /dev/null +++ b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#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 diff --git a/src-tauri/gen/android/gradlew b/src-tauri/gen/android/gradlew new file mode 100755 index 00000000..4f906e0c --- /dev/null +++ b/src-tauri/gen/android/gradlew @@ -0,0 +1,185 @@ +#!/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" "$@" diff --git a/src-tauri/gen/android/gradlew.bat b/src-tauri/gen/android/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/src-tauri/gen/android/gradlew.bat @@ -0,0 +1,89 @@ +@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 diff --git a/src-tauri/gen/android/settings.gradle b/src-tauri/gen/android/settings.gradle new file mode 100644 index 00000000..39391166 --- /dev/null +++ b/src-tauri/gen/android/settings.gradle @@ -0,0 +1,3 @@ +include ':app' + +apply from: 'tauri.settings.gradle' diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index 016e63c4..bc499ab5 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -728,7 +728,7 @@ fn default_review_delivery_mode() -> String { } fn default_backend_mode() -> BackendMode { - if cfg!(target_os = "ios") { + if cfg!(any(target_os = "ios", target_os = "android")) { BackendMode::Remote } else { BackendMode::Local @@ -1226,7 +1226,7 @@ mod tests { fn app_settings_defaults_from_empty_json() { let settings: AppSettings = serde_json::from_str("{}").expect("settings deserialize"); assert!(settings.codex_bin.is_none()); - let expected_backend_mode = if cfg!(target_os = "ios") { + let expected_backend_mode = if cfg!(any(target_os = "ios", target_os = "android")) { BackendMode::Remote } else { BackendMode::Local diff --git a/src-tauri/tauri.android.conf.json b/src-tauri/tauri.android.conf.json new file mode 100644 index 00000000..cd35204b --- /dev/null +++ b/src-tauri/tauri.android.conf.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "identifier": "com.dimillian.codexmonitor.android" +} diff --git a/src/features/settings/components/sections/SettingsServerSection.tsx b/src/features/settings/components/sections/SettingsServerSection.tsx index 7c8c93d5..fdecd21a 100644 --- a/src/features/settings/components/sections/SettingsServerSection.tsx +++ b/src/features/settings/components/sections/SettingsServerSection.tsx @@ -333,8 +333,9 @@ export function SettingsServerSection({ )}
- Start this daemon before connecting from iOS. It uses your current token and - listens on 0.0.0.0:<port>, matching your configured host port. + Start this daemon before connecting from a mobile device. It uses your current + token and listens on 0.0.0.0:<port>, matching your configured + host port.
)} @@ -379,7 +380,7 @@ export function SettingsServerSection({
{tailscaleStatus.installed ? `Version: ${tailscaleStatus.version ?? "unknown"}` - : "Install Tailscale on both desktop and iOS to continue."} + : "Install Tailscale on both desktop and your mobile device to continue."}
{tailscaleStatus.suggestedRemoteHost && (
@@ -713,8 +714,8 @@ export function SettingsServerSection({
{isMobileSimplified ? appSettings.remoteBackendProvider === "tcp" - ? "Use your own infrastructure only. On iOS, get the Tailscale hostname and token from your desktop CodexMonitor setup." - : "Use your own infrastructure only. On iOS, use the Orbit websocket URL and token configured on your desktop CodexMonitor setup." + ? "Use your own infrastructure only. On mobile, get the Tailscale hostname and token from your desktop CodexMonitor setup." + : "Use your own infrastructure only. On mobile, use the Orbit websocket URL and token configured on your desktop CodexMonitor setup." : "Mobile access should stay scoped to your own infrastructure (tailnet or self-hosted Orbit). CodexMonitor does not provide hosted backend services."}
diff --git a/src/utils/platformPaths.test.ts b/src/utils/platformPaths.test.ts index 92b88846..50bf7aaf 100644 --- a/src/utils/platformPaths.test.ts +++ b/src/utils/platformPaths.test.ts @@ -73,6 +73,19 @@ describe("isMobilePlatform", () => { ); }); + it("returns true for Android-like user agents", () => { + withNavigatorValues( + { + platform: "Linux armv8l", + userAgent: + "Mozilla/5.0 (Linux; Android 15; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36", + }, + () => { + expect(isMobilePlatform()).toBe(true); + }, + ); + }); + it("returns false for desktop platforms", () => { withNavigatorValues( {