A Capacitor-based Android wrapper around the TurboWarp / scratch-gui
web client, with native Bluetooth bridges so JavaScript extensions can talk to
real LEGO hardware:
- LEGO® EV3 — Bluetooth Classic (RFCOMM SPP)
- LEGO® Spike Prime / Robot Inventor — BLE (FW 3.x) and BTC (FW 2.x)
- Standard TurboWarp features: 60 FPS, compiler, dark mode, custom extensions.
The Android shell injects a JavaScript bridge that intercepts Scratch Link WebSocket URLs and routes them to native Bluetooth via Capacitor plugins — so extensions written for ScratchLink desktop work unchanged on the device.
| Repo | Role |
|---|---|
brickwright-android (this) |
Android wrapper (Capacitor + native BT bridges). |
CrispStrobe/brickwright |
The editor UI bundled inside the app. |
CrispStrobe/extensions |
Extensions copied into the app's web assets. |
CrispStrobe/brickwright-ios |
iOS counterpart (WKWebView + native bridges). |
CrispStrobe/brickwright-desktop |
Electron desktop counterpart. |
CrispStrobe/brickwright-bridges |
Working sandbox + Python bridges (used by the bridge-mode extensions). |
This repo cannot be built standalone — it expects sibling clones of
scratch-gui and extensions. Lay them out like this:
/workspace/
├── extensions/ # clone: https://github.com/CrispStrobe/extensions
├── scratch-gui/ # clone: https://github.com/CrispStrobe/brickwright
└── brickwright-android/ # this repo
├── android/ # Android Studio project
├── capacitor.config.json
└── package.json
- Node.js (v18 or newer)
- Android Studio (Koala or newer recommended)
- Java JDK 17 (Required for Gradle 8.x)
- Xcode (macOS only, for iOS builds)
All build commands are available as npm scripts. Run them from the brickwright-android/ directory.
# Build web assets + sync to Android
npm run build:android
# Build web assets + sync to iOS
npm run build:ios
# Build web assets + sync all platforms
npm run build:all# Build scratch-gui web assets only
npm run build:web
# Sync to platforms (without rebuilding web)
npm run sync # All platforms
npm run sync:android # Android only
npm run sync:ios # iOS only
# Copy extension files into Android assets
npm run copy:extensions
# Open in IDE
npm run open:android # Opens Android Studio
npm run open:ios # Opens Xcode
# Run on connected device/emulator
npm run run:android
npm run run:ios# 1. Install dependencies (both projects)
cd ../scratch-gui && npm install && cd ../brickwright-android && npm install
# 2. Build web + sync + copy extensions
npm run build:android
npm run copy:extensions
# 3. Open in Android Studio and build APK
npm run open:android# 1. Add iOS platform (first time only)
npx cap add ios
# 2. Install dependencies
cd ../scratch-gui && npm install && cd ../brickwright-android && npm install
# 3. Build web + sync
npm run build:ios
# 4. Open in Xcode and build
npm run open:iosBefore building the Android app, you must patch the web client to trust the local Android environment and build the web assets.
Open ../scratch-gui/src/containers/tw-security-manager.jsx.
Add http://localhost/ and http://localhost:8000/ to the trusted extension list. This allows the Android WebView to load your custom extensions.
const isTrustedExtension = url => (
url.startsWith('https://extensions.turbowarp.org/') ||
url.startsWith('http://localhost/') || // Trust Capacitor Android
url.startsWith('http://localhost:8000/') || // Trust Dev Server
extensionsTrustedByUser.has(url)
);Compile the Scratch GUI into a static web folder.
cd ../scratch-gui
npm install
npm run build
# Result: A populated 'build/' folder containing index.html and static assets.Or use the shortcut from brickwright-android/:
npm run build:webRun these commands from the brickwright-android root to pull in the latest web assets and plugins.
npm install
npm run sync:androidCopy the raw extension files and metadata into the Android assets.
npm run copy:extensionsOr manually:
mkdir -p android/app/src/main/assets/public/extensions
mkdir -p android/app/src/main/assets/public/generated-metadata
cp ../extensions/extensions/*.js android/app/src/main/assets/public/extensions/
cp ../extensions/extensions/extensions.json android/app/src/main/assets/public/generated-metadata/extensions-v0.jsonWe need to inject a script that translates Scratch Link WebSocket calls into Android native Bluetooth commands.
- Create the Bridge Script:
Ensure
android/app/src/main/assets/public/inject-android-bridge.jsexists. (Seeinject-android-bridge.jsin this repo for content). - Link it in HTML:
Open
android/app/src/main/assets/public/index.html. Add this line just before the closing</body>tag:
<script src="inject-android-bridge.js"></script>
</body>To support SDK 36 (Android 16 Preview) while maintaining compatibility with standard Android Studio versions, use this exact configuration.
android/variables.gradle
ext {
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
// ... other versions ...
cordovaAndroidVersion = '14.0.1'
}android/build.gradle(Project Level)
dependencies {
// AGP 8.12.3 is the stable max for current IDEs
classpath 'com.android.tools.build:gradle:8.12.0'
classpath 'com.google.gms:google-services:4.4.4'
}android/gradle/wrapper/gradle-wrapper.properties
# Gradle 8.13 is required to build SDK 36
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zipOpen android/app/src/main/AndroidManifest.xml. Ensure these permissions are present for LEGO Bluetooth support and force Landscape Mode.
<manifest ...>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application ...>
<activity
android:name=".MainActivity"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
...>
</activity>
</application>
</manifest>A custom Capacitor plugin registered in MainActivity.java that provides native Android file picker integration via Storage Access Framework (SAF):
saveFile()- Opens Android's native "Save As" dialog (ACTION_CREATE_DOCUMENT). Accepts base64-encoded file data, filename, and MIME type. Writes to the user-selected location.openFile()- Opens Android's native file browser (ACTION_OPEN_DOCUMENT). Returns the selected file as base64-encoded data along with filename and URI.
These are called from the web layer via src/lib/tw-capacitor-file-bridge.js and replace the browser's showSaveFilePicker/showOpenFilePicker APIs which are unavailable in Android WebView.
The @capacitor/app plugin provides hardware back button events. Modals listen for backButton events and close themselves, matching native Android navigation behavior.
Open the project in Android Studio:
npm run open:android- Click Sync Project with Gradle Files (Elephant icon).
- Verify: Ensure the "Build" tab shows green checks.
- Right-click
appfolder in the project tree. - Select New > Image Asset.
- Select your
Turbowarp_icon.png. - Resize to fit the Safe Zone and click Finish.
- Go to Build > Generate Signed Bundle / APK.
- Select APK.
- Create a new KeyStore (
release.jks) if you don't have one. - Select Release build variant.
- Click Create.
The final APK will be located at:
android/app/release/app-release.apk
cd android
./gradlew assembleDebug # Debug APK
./gradlew assembleRelease # Release APK (requires signing config)| Script | Description |
|---|---|
npm run build:web |
Build scratch-gui web assets |
npm run build:android |
Build web + sync Android |
npm run build:ios |
Build web + sync iOS |
npm run build:all |
Build web + sync all platforms |
npm run sync |
Sync all platforms (no web rebuild) |
npm run sync:android |
Sync Android only |
npm run sync:ios |
Sync iOS only |
npm run open:android |
Open Android Studio |
npm run open:ios |
Open Xcode |
npm run run:android |
Run on Android device/emulator |
npm run run:ios |
Run on iOS device/simulator |
npm run copy:extensions |
Copy extensions into Android assets |
Based on TurboWarp and Scratch.
- This project and TurboWarp: GPLv3
- Scratch: BSD-3-Clause
Disclaimer: This project is not affiliated with TurboWarp, the Scratch Team, or the LEGO Group.