diff --git a/.gitignore b/.gitignore index 3e311c2..baf0a69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,5 @@ -# nix .direnv -result - -# system .DS_Store - -# wails -.task -bin -build/linux/appimage/build -build/windows/nsis/MicrosoftEdgeWebview2Setup.exe +dist +node_modules +result diff --git a/README.md b/README.md index 4ef34de..ec650ff 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ _tinysub_ is a simple but full-featured web player for _OpenSubsonic compatible music servers_, such as [Navidrome](https://github.com/navidrome/navidrome) and [Gonic](https://github.com/sentriz/gonic). i don't really like Navidrome's web client for various reasons, and i wanted something akin to [Strawberry Music Player](https://www.strawberrymusicplayer.org) that i could run anywhere. -it's written in Svelte, and aims to be extremely performant and lightweight (as much as a web app can be), while also having all of the typical features you'd expect. it's also meant to look and feel similar to a desktop application. +it's written in Svelte, and aims to be extremely performant and lightweight (as much as a web app can be), while also having all of the typical features you'd expect. for more information, see https://devins.page/tinysub. @@ -16,8 +16,6 @@ this project is still a **work-in-progress** and i am working on it in my free t if you are trying to connect with `http` on local network/Tailscale rather than `https`, it may not work out of the box on browsers other than Chromium-based browsers, such as Firefox and Safari. you likely need to serve your Subsonic server on `https`. -a solution to this is using the desktop version of tinysub built with [Wails v3](https://v3.wails.io) - currently these builds are not automated, but if you have `go` and `nodejs` installed you can manually compile it yourself after [installing Wails v3](https://v3.wails.io/quick-start/installation). just run `wails3 package`. these will be automated through CI in the future! - ### mobile support? sorta, but it's not a priority. you may experience some weird mobile UX issues. diff --git a/Taskfile.yml b/Taskfile.yml deleted file mode 100644 index f2041d8..0000000 --- a/Taskfile.yml +++ /dev/null @@ -1,58 +0,0 @@ -version: "3" - -vars: - APP_NAME: "tinysub" - BIN_DIR: "bin" - VITE_PORT: "{{.WAILS_VITE_PORT | default 9245}}" - -includes: - common: ./build/Taskfile.yml - windows: ./build/windows/Taskfile.yml - darwin: ./build/darwin/Taskfile.yml - linux: ./build/linux/Taskfile.yml - -tasks: - build: - summary: Builds the application - cmds: - - task: "{{OS}}:build" - - package: - summary: Packages a production build of the application - cmds: - - task: "{{OS}}:package" - - run: - summary: Runs the application - cmds: - - task: "{{OS}}:run" - - dev: - summary: Runs the application in development mode - cmds: - - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} - - setup:docker: - summary: Builds Docker image for cross-compilation (~800MB download) - cmds: - - task: common:setup:docker - - build:server: - summary: Builds the application in server mode (no GUI, HTTP server only) - cmds: - - task: common:build:server - - run:server: - summary: Runs the application in server mode - cmds: - - task: common:run:server - - build:docker: - summary: Builds a Docker image for server mode deployment - cmds: - - task: common:build:docker - - run:docker: - summary: Builds and runs the Docker image - cmds: - - task: common:run:docker diff --git a/frontend/biome.json b/biome.json similarity index 91% rename from frontend/biome.json rename to biome.json index 3b68026..7ec4861 100644 --- a/frontend/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.0/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.2/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -11,7 +11,6 @@ "!!**/dist", "!!**/result", "!src/assets/famfamfam-silk-svg", - "!bindings", "!public" ] }, diff --git a/build/Taskfile.yml b/build/Taskfile.yml deleted file mode 100644 index 00a49e3..0000000 --- a/build/Taskfile.yml +++ /dev/null @@ -1,369 +0,0 @@ -version: '3' - -vars: - PACKAGE_MANAGER: npm - -tasks: - go:mod:tidy: - summary: Runs `go mod tidy` - internal: true - # Universal/multi-arch builds invoke this concurrently from parallel deps; - # two `go mod tidy` processes racing on go.mod can corrupt it (#4637). - run: once - cmds: - - go mod tidy - - install:frontend:deps: - summary: Install frontend dependencies - run: once - cmds: - - task: install:frontend:deps:{{.PACKAGE_MANAGER}} - - install:frontend:deps:npm: - dir: frontend - sources: - - package.json - - package-lock.json - generates: - - node_modules - preconditions: - - sh: npm version - msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" - cmds: - - npm install - - install:frontend:deps:bun: - dir: frontend - sources: - - package.json - - bun.lock - - bun.lockb - generates: - - node_modules - preconditions: - - sh: bun --version - msg: "bun not found" - cmds: - - bun install - - install:frontend:deps:pnpm: - dir: frontend - sources: - - package.json - - pnpm-lock.yaml - generates: - - node_modules - preconditions: - - sh: pnpm --version - msg: "pnpm not found" - cmds: - - pnpm install - - install:frontend:deps:yarn: - dir: frontend - sources: - - package.json - - yarn.lock - status: - - test -d node_modules || test -f .pnp.cjs - preconditions: - - sh: yarn --version - msg: "yarn not found" - cmds: - - yarn install - - build:frontend: - label: build:frontend (DEV={{.DEV}} RUNNER={{.PACKAGE_MANAGER}}) - summary: Build the frontend project - # darwin:build:universal runs its per-arch builds as parallel deps, each of - # which depends on this task. Without run:once the two executions race: - # one regenerates frontend/bindings (-clean deletes it first) while the - # other's bundler is reading it, failing intermittently with - # 'Could not resolve "./bindings/"' (#4637). - run: once - dir: frontend - sources: - - "**/*" - - exclude: node_modules/**/* - generates: - - dist/**/* - deps: - - task: install:frontend:deps - - task: generate:bindings - vars: - BUILD_FLAGS: - ref: .BUILD_FLAGS - OBFUSCATED: - ref: .OBFUSCATED - cmds: - - task: frontend:run - vars: - SCRIPT: '{{if eq .DEV "true"}}build:dev{{else}}build{{end}}' - env: - PRODUCTION: '{{if eq .DEV "true"}}false{{else}}true{{end}}' - - frontend:run: - summary: Run a frontend script with selected runner - cmds: - - task: frontend:run:{{.PACKAGE_MANAGER}} - vars: - SCRIPT: "{{.SCRIPT}}" - vars: - SCRIPT: "{{.SCRIPT}}" - - frontend:run:npm: - dir: frontend - cmds: - - npm run {{.SCRIPT}} -q - vars: - SCRIPT: "{{.SCRIPT}}" - - frontend:run:yarn: - dir: frontend - cmds: - - yarn {{.SCRIPT}} - vars: - SCRIPT: "{{.SCRIPT}}" - - frontend:run:pnpm: - dir: frontend - cmds: - - pnpm run {{.SCRIPT}} - vars: - SCRIPT: "{{.SCRIPT}}" - - frontend:run:bun: - dir: frontend - cmds: - - bun run {{.SCRIPT}} - vars: - SCRIPT: "{{.SCRIPT}}" - - frontend:vendor:puppertino: - summary: Fetches Puppertino CSS into frontend/public for consistent mobile styling - sources: - - frontend/public/puppertino/puppertino.css - generates: - - frontend/public/puppertino/puppertino.css - cmds: - - | - set -euo pipefail - mkdir -p frontend/public/puppertino - # If bundled Puppertino exists, prefer it. Otherwise, try to fetch, but don't fail build on error. - if [ ! -f frontend/public/puppertino/puppertino.css ]; then - echo "No bundled Puppertino found. Attempting to fetch from GitHub..." - if curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/dist/css/full.css -o frontend/public/puppertino/puppertino.css; then - curl -fsSL https://raw.githubusercontent.com/codedgar/Puppertino/main/LICENSE -o frontend/public/puppertino/LICENSE || true - echo "Puppertino CSS downloaded to frontend/public/puppertino/puppertino.css" - else - echo "Warning: Could not fetch Puppertino CSS. Proceeding without download since template may bundle it." - fi - else - echo "Using bundled Puppertino at frontend/public/puppertino/puppertino.css" - fi - # Ensure index.html includes Puppertino CSS and button classes - INDEX_HTML=frontend/index.html - if [ -f "$INDEX_HTML" ]; then - if ! grep -q 'href="/puppertino/puppertino.css"' "$INDEX_HTML"; then - # Insert Puppertino link tag after style.css link - awk ' - /href="\/style.css"\/?/ && !x { print; print " "; x=1; next }1 - ' "$INDEX_HTML" > "$INDEX_HTML.tmp" && mv "$INDEX_HTML.tmp" "$INDEX_HTML" - fi - # Replace default .btn with Puppertino primary button classes if present - sed -E -i'' 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" || true - fi - - - - generate:bindings: - summary: Generates bindings for the frontend - run: once - deps: - - task: go:mod:tidy - sources: - - "**/*.[jt]s" - - exclude: frontend/**/* - - frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output - - "**/*.go" - - go.mod - - go.sum - generates: - - frontend/bindings/**/* - cmds: - - wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true{{if eq .OBFUSCATED "true"}} -obfuscated{{end}} - - generate:icons: - summary: Generates Windows `.ico` and Mac `.icns` from an image; on macOS, `-iconcomposerinput appicon.icon -macassetdir darwin` also produces `Assets.car` from a `.icon` file (skipped on other platforms). - run: once - dir: build - sources: - - "appicon.png" - - "appicon.icon" - generates: - - "darwin/icons.icns" - - "windows/icon.ico" - cmds: - - wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico -iconcomposerinput appicon.icon -macassetdir darwin - - dev:frontend: - summary: Runs the frontend in development mode - deps: - - task: install:frontend:deps - cmds: - - task: frontend:dev:{{.PACKAGE_MANAGER}} - - frontend:dev:npm: - dir: frontend - cmds: - - npm run dev -- --port {{.VITE_PORT}} --strictPort - - frontend:dev:yarn: - dir: frontend - cmds: - - yarn dev --port {{.VITE_PORT}} --strictPort - - frontend:dev:pnpm: - dir: frontend - cmds: - - pnpm dev --port {{.VITE_PORT}} --strictPort - - frontend:dev:bun: - dir: frontend - cmds: - - bun run dev --port {{.VITE_PORT}} --strictPort - - update:build-assets: - summary: Updates the build assets - dir: build - cmds: - - wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir . - - build:server: - summary: Builds the application in server mode (no GUI, HTTP server only) - desc: | - Builds the application with the server build tag enabled. - Server mode runs as a pure HTTP server without native GUI dependencies. - Usage: task build:server - deps: - - task: build:frontend - vars: - BUILD_FLAGS: - ref: .BUILD_FLAGS - cmds: - - go build -tags server {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}} - vars: - BUILD_FLAGS: "{{.BUILD_FLAGS}}" - - run:server: - summary: Builds and runs the application in server mode - deps: - - task: build:server - cmds: - - ./{{.BIN_DIR}}/{{.APP_NAME}}-server{{exeExt}} - - build:docker: - summary: Builds a Docker image for server mode deployment - desc: | - Creates a minimal Docker image containing the server mode binary. - The image is based on distroless for security and small size. - Usage: task build:docker [TAG=myapp:latest] - cmds: - - docker build -t {{.TAG | default (printf "%s:latest" .APP_NAME)}} -f build/docker/Dockerfile.server . - vars: - TAG: "{{.TAG}}" - preconditions: - - sh: docker info > /dev/null 2>&1 - msg: "Docker is required. Please install Docker first." - - sh: test -f build/docker/Dockerfile.server - msg: "Dockerfile.server not found. Run 'wails3 update build-assets' to generate it." - - run:docker: - summary: Builds and runs the Docker image - desc: | - Builds the Docker image and runs it, exposing port 8080. - Usage: task run:docker [TAG=myapp:latest] [PORT=8080] - Note: The internal container port is always 8080. The PORT variable - only changes the host port mapping. Ensure your app uses port 8080 - or modify the Dockerfile to match your ServerOptions.Port setting. - deps: - - task: build:docker - vars: - TAG: - ref: .TAG - cmds: - - docker run --rm -p {{.PORT | default "8080"}}:8080 {{.TAG | default (printf "%s:latest" .APP_NAME)}} - vars: - TAG: "{{.TAG}}" - PORT: "{{.PORT}}" - - setup:docker: - summary: Builds Docker image for cross-compilation (~800MB download) - desc: | - Builds the Docker image needed for cross-compiling to any platform. - Run this once to enable cross-platform builds from any OS. - cmds: - - docker build -t wails-cross -f build/docker/Dockerfile.cross build/docker/ - preconditions: - - sh: docker info > /dev/null 2>&1 - msg: "Docker is required. Please install Docker first." - - ios:device:list: - summary: Lists connected iOS devices (UDIDs) - cmds: - - xcrun xcdevice list - - ios:run:device: - summary: Build, install, and launch on a physical iPhone using Apple tools (xcodebuild/devicectl) - vars: - PROJECT: '{{.PROJECT}}' # e.g., build/ios/xcode/.xcodeproj - SCHEME: '{{.SCHEME}}' # e.g., ios.dev - CONFIG: '{{.CONFIG | default "Debug"}}' - DERIVED: '{{.DERIVED | default "build/ios/DerivedData"}}' - UDID: '{{.UDID}}' # from `task ios:device:list` - BUNDLE_ID: '{{.BUNDLE_ID}}' # e.g., com.yourco.wails.ios.dev - TEAM_ID: '{{.TEAM_ID}}' # optional, if your project is not already set up for signing - preconditions: - - sh: xcrun -f xcodebuild - msg: "xcodebuild not found. Please install Xcode." - - sh: xcrun -f devicectl - msg: "devicectl not found. Please update to Xcode 15+ (which includes devicectl)." - - sh: test -n '{{.PROJECT}}' - msg: "Set PROJECT to your .xcodeproj path (e.g., PROJECT=build/ios/xcode/App.xcodeproj)." - - sh: test -n '{{.SCHEME}}' - msg: "Set SCHEME to your app scheme (e.g., SCHEME=ios.dev)." - - sh: test -n '{{.UDID}}' - msg: "Set UDID to your device UDID (see: task ios:device:list)." - - sh: test -n '{{.BUNDLE_ID}}' - msg: "Set BUNDLE_ID to your app's bundle identifier (e.g., com.yourco.wails.ios.dev)." - cmds: - - | - set -euo pipefail - echo "Building for device: UDID={{.UDID}} SCHEME={{.SCHEME}} PROJECT={{.PROJECT}}" - XCB_ARGS=( - -project "{{.PROJECT}}" - -scheme "{{.SCHEME}}" - -configuration "{{.CONFIG}}" - -destination "id={{.UDID}}" - -derivedDataPath "{{.DERIVED}}" - -allowProvisioningUpdates - -allowProvisioningDeviceRegistration - ) - # Optionally inject signing identifiers if provided - if [ -n '{{.TEAM_ID}}' ]; then XCB_ARGS+=(DEVELOPMENT_TEAM={{.TEAM_ID}}); fi - if [ -n '{{.BUNDLE_ID}}' ]; then XCB_ARGS+=(PRODUCT_BUNDLE_IDENTIFIER={{.BUNDLE_ID}}); fi - xcodebuild "${XCB_ARGS[@]}" build | xcpretty || true - # If xcpretty isn't installed, run without it - if [ "${PIPESTATUS[0]}" -ne 0 ]; then - xcodebuild "${XCB_ARGS[@]}" build - fi - # Find built .app - APP_PATH=$(find "{{.DERIVED}}/Build/Products" -type d -name "*.app" -maxdepth 3 | head -n 1) - if [ -z "$APP_PATH" ]; then - echo "Could not locate built .app under {{.DERIVED}}/Build/Products" >&2 - exit 1 - fi - echo "Installing: $APP_PATH" - xcrun devicectl device install app --device "{{.UDID}}" "$APP_PATH" - echo "Launching: {{.BUNDLE_ID}}" - xcrun devicectl device process launch --device "{{.UDID}}" --stderr console --stdout console "{{.BUNDLE_ID}}" diff --git a/build/android/Taskfile.yml b/build/android/Taskfile.yml deleted file mode 100644 index 800acd8..0000000 --- a/build/android/Taskfile.yml +++ /dev/null @@ -1,290 +0,0 @@ -version: '3' - -includes: - common: ../Taskfile.yml - -vars: - APP_ID: '{{.APP_ID | default "com.wails.app"}}' - MIN_SDK: '21' - TARGET_SDK: '34' - # The emulator runs the host architecture; physical devices are arm64 - HOST_ARCH: - sh: '[ "$(uname -m)" = "x86_64" ] && echo "amd64" || echo "arm64"' - # System-image ABI for the host, used in the "create an AVD" hint below. - ANDROID_ABI: - sh: '[ "$(uname -m)" = "arm64" ] && echo "arm64-v8a" || echo "x86_64"' - # SDK location: $ANDROID_HOME / $ANDROID_SDK_ROOT, else the per-OS default - # (macOS: ~/Library/Android/sdk, Linux/other: ~/Android/Sdk) - SDK_ROOT: - sh: 'echo "${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$([ -d "$HOME/Library/Android/sdk" ] && echo "$HOME/Library/Android/sdk" || echo "$HOME/Android/Sdk")}}"' - ADB: - sh: 'command -v adb || echo "${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$([ -d "$HOME/Library/Android/sdk" ] && echo "$HOME/Library/Android/sdk" || echo "$HOME/Android/Sdk")}}/platform-tools/adb"' - EMULATOR: - sh: 'command -v emulator || echo "${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$([ -d "$HOME/Library/Android/sdk" ] && echo "$HOME/Library/Android/sdk" || echo "$HOME/Android/Sdk")}}/emulator/emulator"' - -tasks: - install:deps: - summary: Check and install Android development dependencies - cmds: - - go run build/android/scripts/deps/install_deps.go - env: - TASK_FORCE_YES: '{{if .YES}}true{{else}}false{{end}}' - prompt: This will check and install Android development dependencies. Continue? - - build: - summary: Creates a debug build of the application for Android - deps: - - task: common:go:mod:tidy - - task: generate:android:overlay - - task: common:build:frontend - vars: - BUILD_FLAGS: - ref: .BUILD_FLAGS - PRODUCTION: - ref: .PRODUCTION - cmds: - - echo "Building Android app {{.APP_NAME}}..." - - task: compile:go:shared - vars: - ARCH: '{{.ARCH | default .HOST_ARCH}}' - vars: - BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,android -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags android,debug -buildvcs=false -gcflags=all="-l"{{end}}' - env: - PRODUCTION: '{{.PRODUCTION | default "false"}}' - - compile:go:shared: - summary: Compile Go code to shared library (.so) - cmds: - - | - # Locate the NDK: $ANDROID_NDK_HOME, or the newest installed NDK - NDK_ROOT="$ANDROID_NDK_HOME" - if [ -z "$NDK_ROOT" ]; then - SDK_ROOT="{{.SDK_ROOT}}" - NDK_ROOT=$(ls -d "$SDK_ROOT"/ndk/* 2>/dev/null | sort -V | tail -1) - fi - if [ -z "$NDK_ROOT" ] || [ ! -d "$NDK_ROOT" ]; then - echo "Error: Android NDK not found" - echo "Install one with: sdkmanager 'ndk;26.3.11579264' (or set ANDROID_NDK_HOME)" - exit 1 - fi - - # Determine toolchain based on host OS - case "$(uname -s)" in - Darwin) HOST_TAG="darwin-x86_64" ;; - Linux) HOST_TAG="linux-x86_64" ;; - *) echo "Unsupported host OS"; exit 1 ;; - esac - - TOOLCHAIN="$NDK_ROOT/toolchains/llvm/prebuilt/$HOST_TAG" - - # Set compiler based on architecture - case "{{.ARCH}}" in - arm64) - export CC="$TOOLCHAIN/bin/aarch64-linux-android{{.MIN_SDK}}-clang" - export CXX="$TOOLCHAIN/bin/aarch64-linux-android{{.MIN_SDK}}-clang++" - export GOARCH=arm64 - JNI_DIR="arm64-v8a" - ;; - amd64|x86_64) - export CC="$TOOLCHAIN/bin/x86_64-linux-android{{.MIN_SDK}}-clang" - export CXX="$TOOLCHAIN/bin/x86_64-linux-android{{.MIN_SDK}}-clang++" - export GOARCH=amd64 - JNI_DIR="x86_64" - ;; - *) - echo "Unsupported architecture: {{.ARCH}}" - exit 1 - ;; - esac - - export CGO_ENABLED=1 - export GOOS=android - - mkdir -p {{.BIN_DIR}} - mkdir -p build/android/app/src/main/jniLibs/$JNI_DIR - - go build -buildmode=c-shared -overlay build/android/overlay.json {{.BUILD_FLAGS}} \ - -o build/android/app/src/main/jniLibs/$JNI_DIR/libwails.so - vars: - BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,android -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags android,debug -buildvcs=false -gcflags=all="-l"{{end}}' - - compile:go:all-archs: - summary: Compile Go code for all Android architectures (fat APK) - cmds: - - task: compile:go:shared - vars: - ARCH: arm64 - - task: compile:go:shared - vars: - ARCH: amd64 - - package: - summary: Packages a production build of the application into a signed release APK - deps: - - task: build - vars: - PRODUCTION: "true" - ARCH: '{{.ARCH | default .HOST_ARCH}}' - cmds: - - task: assemble:apk:release - - package:fat: - summary: Packages a production build for all architectures (fat APK) - deps: - - task: build - vars: - PRODUCTION: "true" - ARCH: arm64 - cmds: - - task: compile:go:shared - vars: - ARCH: amd64 - PRODUCTION: "true" - - task: assemble:apk:release - - assemble:apk: - summary: Assembles a debug APK using Gradle - preconditions: - - sh: 'command -v java >/dev/null || [ -n "$JAVA_HOME" ]' - msg: "Java not found. Install a JDK (e.g. brew install openjdk@21) and/or set JAVA_HOME" - cmds: - - | - cd build/android - # The exec bit is lost when gradlew is extracted from the embedded - # build assets, so restore it before invoking the wrapper. - chmod +x ./gradlew - ./gradlew assembleDebug - cp app/build/outputs/apk/debug/app-debug.apk "../../{{.BIN_DIR}}/{{.APP_NAME}}.apk" - echo "APK created: {{.BIN_DIR}}/{{.APP_NAME}}.apk" - - assemble:apk:release: - summary: Assembles a release APK using Gradle (signed with the debug keystore unless ANDROID_KEYSTORE_FILE is set) - preconditions: - - sh: 'command -v java >/dev/null || [ -n "$JAVA_HOME" ]' - msg: "Java not found. Install a JDK (e.g. brew install openjdk@21) and/or set JAVA_HOME" - cmds: - - | - cd build/android - # The exec bit is lost when gradlew is extracted from the embedded - # build assets, so restore it before invoking the wrapper. - chmod +x ./gradlew - ./gradlew assembleRelease - cp app/build/outputs/apk/release/app-release.apk "../../{{.BIN_DIR}}/{{.APP_NAME}}.apk" - echo "Release APK created: {{.BIN_DIR}}/{{.APP_NAME}}.apk" - - generate:android:overlay: - internal: true - summary: Generate Go build overlay that registers the Android main - sources: - - build/config.yml - generates: - - build/android/overlay.json - - build/android/gen/main_android.gen.go - cmds: - - wails3 android overlay:gen -out build/android/overlay.json -config build/config.yml - - generate:android:bindings: - internal: true - summary: Generates bindings for Android - sources: - - "**/*.go" - - go.mod - - go.sum - generates: - - frontend/bindings/**/* - cmds: - # Bindings are generated from the Go AST; CGO is disabled so the NDK - # is not required for this step - - wails3 generate bindings -f '-tags android' -clean=true - env: - GOOS: android - CGO_ENABLED: 0 - - ensure-emulator: - internal: true - summary: Ensure Android Emulator is running - silent: true - cmds: - - | - # Check if an emulator is already running - if "{{.ADB}}" devices | grep -q "emulator"; then - echo "Emulator already running" - exit 0 - fi - - # Get first available AVD - AVD_NAME=$("{{.EMULATOR}}" -list-avds | tail -1) - if [ -z "$AVD_NAME" ]; then - echo "No Android Virtual Devices found. Create one with:" - echo " avdmanager create avd --name wails --package 'system-images;android-34;google_apis;{{.ANDROID_ABI}}' --device pixel_7" - exit 1 - fi - - echo "Starting emulator: $AVD_NAME" - # Start the emulator daemonized so it outlives this task step. go-task's - # shell tracks background jobs by PID and reaps them when the command's - # interpreter finishes (which nohup/setsid alone don't prevent — the kill - # is direct), so a bare `emulator &` is gone before the later - # install/launch steps run. Launch it from a short-lived child shell that - # backgrounds the emulator and exits immediately: the emulator is then - # reparented to init/launchd and go-task's shell has no handle to reap it. - nohup sh -c "'{{.EMULATOR}}' -avd '$AVD_NAME' -no-snapshot-load /dev/null 2>&1 &" >/dev/null 2>&1 - - # Wait for emulator to boot (max 120 seconds) - echo "Waiting for emulator to boot..." - "{{.ADB}}" wait-for-device - - for i in $(seq 1 120); do - BOOT_COMPLETED=$("{{.ADB}}" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') - if [ "$BOOT_COMPLETED" = "1" ]; then - echo "Emulator booted successfully" - exit 0 - fi - sleep 1 - done - - echo "Emulator boot timeout" - exit 1 - preconditions: - - sh: '[ -x "{{.ADB}}" ] || command -v adb' - msg: "adb not found. Install the Android SDK platform-tools (or set ANDROID_HOME)" - - sh: '[ -x "{{.EMULATOR}}" ] || command -v emulator' - msg: "emulator not found. Install the Android SDK emulator package (or set ANDROID_HOME)" - - deploy-emulator: - summary: Deploy the packaged release APK to the Android Emulator - deps: [package] - cmds: - - task: ensure-emulator - - '"{{.ADB}}" uninstall {{.APP_ID}} 2>/dev/null || true' - - '"{{.ADB}}" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"' - - '"{{.ADB}}" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity' - - run: - summary: Build, install and launch a debug build in the Android Emulator - deps: - - task: ensure-emulator - - task: build - cmds: - - task: assemble:apk - - '"{{.ADB}}" uninstall {{.APP_ID}} 2>/dev/null || true' - - '"{{.ADB}}" install "{{.BIN_DIR}}/{{.APP_NAME}}.apk"' - - '"{{.ADB}}" shell am start -n {{.APP_ID}}/com.wails.app.MainActivity' - - logs: - summary: Stream Android logcat filtered to this app - cmds: - - '"{{.ADB}}" logcat -v time | grep -E "(Wails|{{.APP_NAME}})" || true' - - logs:all: - summary: Stream all Android logcat (verbose) - cmds: - - '"{{.ADB}}" logcat -v time' - - clean: - summary: Clean build artifacts - cmds: - - rm -rf {{.BIN_DIR}} - - rm -rf build/android/app/build - - rm -rf build/android/app/src/main/jniLibs/*/libwails.so - - rm -rf build/android/.gradle diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle deleted file mode 100644 index 630f03e..0000000 --- a/build/android/app/build.gradle +++ /dev/null @@ -1,83 +0,0 @@ -plugins { - id 'com.android.application' -} - -android { - namespace 'com.wails.app' - compileSdk 34 - - buildFeatures { - buildConfig = true - } - - defaultConfig { - applicationId "com.wails.app" - minSdk 21 - targetSdk 34 - versionCode 1 - versionName "1.0" - - // Configure supported ABIs - ndk { - abiFilters 'arm64-v8a', 'x86_64' - } - } - - def keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE") - def hasKeystore = keystoreFile != null && !keystoreFile.trim().isEmpty() - - signingConfigs { - // A real keystore can be provided via environment variables; without - // one, release builds are signed with the debug keystore so they can - // be installed for testing (not suitable for Play Store uploads). - release { - if (hasKeystore) { - storeFile file(keystoreFile) - storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD") - keyAlias System.getenv("ANDROID_KEY_ALIAS") - keyPassword System.getenv("ANDROID_KEY_PASSWORD") - } - } - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - signingConfig hasKeystore ? signingConfigs.release : signingConfigs.debug - } - debug { - debuggable true - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - - // Source sets configuration - sourceSets { - main { - // JNI libraries are in jniLibs folder - jniLibs.srcDirs = ['src/main/jniLibs'] - // Assets for the WebView - assets.srcDirs = ['src/main/assets'] - } - } - - // Packaging options - packagingOptions { - // Don't strip Go symbols in debug builds - doNotStrip '*/arm64-v8a/libwails.so' - doNotStrip '*/x86_64/libwails.so' - } -} - -dependencies { - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'androidx.webkit:webkit:1.9.0' - implementation 'com.google.android.material:material:1.11.0' - implementation 'androidx.biometric:biometric:1.1.0' - implementation 'androidx.security:security-crypto:1.1.0-alpha06' -} diff --git a/build/android/app/proguard-rules.pro b/build/android/app/proguard-rules.pro deleted file mode 100644 index 8b88c3d..0000000 --- a/build/android/app/proguard-rules.pro +++ /dev/null @@ -1,12 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. - -# Keep native methods --keepclasseswithmembernames class * { - native ; -} - -# Keep Wails bridge classes --keep class com.wails.app.WailsBridge { *; } --keep class com.wails.app.WailsJSBridge { *; } diff --git a/build/android/app/src/main/AndroidManifest.xml b/build/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 03d343a..0000000 --- a/build/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build/android/app/src/main/java/com/wails/app/MainActivity.java b/build/android/app/src/main/java/com/wails/app/MainActivity.java deleted file mode 100644 index 4d7b204..0000000 --- a/build/android/app/src/main/java/com/wails/app/MainActivity.java +++ /dev/null @@ -1,806 +0,0 @@ -package com.wails.app; - -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Configuration; -import android.database.Cursor; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.Uri; -import android.os.BatteryManager; -import android.os.Build; -import android.os.Bundle; -import android.os.PowerManager; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.provider.MediaStore; -import android.provider.OpenableColumns; -import android.util.Base64; -import android.util.Log; -import android.webkit.WebResourceRequest; -import android.webkit.WebResourceResponse; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.FileProvider; -import androidx.webkit.WebViewAssetLoader; - -import org.json.JSONObject; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * MainActivity hosts the WebView and manages the Wails application lifecycle. - * It uses WebViewAssetLoader to serve assets from the Go library without - * requiring a network server. - */ -public class MainActivity extends AppCompatActivity { - private static final String TAG = "WailsActivity"; - private static final boolean DEBUG = BuildConfig.DEBUG; - private static final String WAILS_SCHEME = "https"; - private static final String WAILS_HOST = "wails.localhost"; - private static final int FILE_PICKER_REQUEST = 7001; - - private WebView webView; - private WailsBridge bridge; - // Battery: system-event receivers are registered only while the activity is - // in the foreground (onStart) and torn down in onStop, so background battery/ - // network/screen broadcasts don't wake the app. - private boolean systemReceiversRegistered = false; - private WebViewAssetLoader assetLoader; - - // The Go-side dialog ID of the in-flight file picker (-1 when idle) - private int pendingFilePickerCallbackID = -1; - private static final int PHOTO_CAPTURE_REQUEST = 7002; - private static final int VIDEO_CAPTURE_REQUEST = 7003; - private static final int CAMERA_PERMISSION_REQUEST = 7010; - private File pendingCaptureFile; - private boolean pendingCaptureIsVideo; - - // System-event sources (battery/power, screen lock, network). Registered in - // onCreate, torn down in onDestroy. Each forwards a "system:*" event to JS - // via the bridge. - private BroadcastReceiver batteryReceiver; - private BroadcastReceiver screenReceiver; - private BroadcastReceiver powerSaveReceiver; - private ConnectivityManager connectivityManager; - private ConnectivityManager.NetworkCallback networkCallback; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - // Initialize the native Go library - bridge = new WailsBridge(this); - bridge.initialize(); - - // Set up WebView - setupWebView(); - - // Load the application - loadApplication(); - } - - @SuppressLint("SetJavaScriptEnabled") - private void setupWebView() { - webView = findViewById(R.id.webview); - bridge.setWebView(webView); - - // Configure WebView settings - WebSettings settings = webView.getSettings(); - settings.setJavaScriptEnabled(true); - settings.setDomStorageEnabled(true); - settings.setDatabaseEnabled(true); - settings.setAllowFileAccess(false); - settings.setAllowContentAccess(false); - settings.setMediaPlaybackRequiresUserGesture(false); - settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); - - // Enable debugging in debug builds - if (DEBUG) { - WebView.setWebContentsDebuggingEnabled(true); - } - - // Set up asset loader for serving local assets - assetLoader = new WebViewAssetLoader.Builder() - .setDomain(WAILS_HOST) - .addPathHandler("/", new WailsPathHandler(bridge)) - .build(); - - // Set up WebView client to intercept requests - webView.setWebViewClient(new WebViewClient() { - @Nullable - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { - // Handle wails.localhost requests - if (request.getUrl().getHost() != null && - request.getUrl().getHost().equals(WAILS_HOST)) { - - // For wails API calls (runtime, capabilities, etc.) pass the - // full URL including the query string, because - // WebViewAssetLoader.PathHandler strips query params - String path = request.getUrl().getPath(); - if (path != null && path.startsWith("/wails/")) { - String fullPath = path; - String query = request.getUrl().getQuery(); - if (query != null && !query.isEmpty()) { - fullPath = path + "?" + query; - } - if (DEBUG) Log.d(TAG, "Wails API call: " + fullPath); - - byte[] data = bridge.serveAsset(fullPath, request.getMethod(), "{}"); - if (data != null && data.length > 0) { - java.io.InputStream inputStream = new java.io.ByteArrayInputStream(data); - java.util.Map headers = new java.util.HashMap<>(); - headers.put("Access-Control-Allow-Origin", "*"); - headers.put("Cache-Control", "no-cache"); - headers.put("Content-Type", "application/json"); - - return new WebResourceResponse( - "application/json", - "UTF-8", - 200, - "OK", - headers, - inputStream - ); - } - // Return error response if data is null - return new WebResourceResponse( - "application/json", - "UTF-8", - 500, - "Internal Error", - new java.util.HashMap<>(), - new java.io.ByteArrayInputStream("{}".getBytes()) - ); - } - - // Stream captured photos/videos from the cache with HTTP Range - // support so