diff --git a/.github/workflows/update-libs.yml b/.github/workflows/update-libs.yml index bd29b2da..530c1a14 100644 --- a/.github/workflows/update-libs.yml +++ b/.github/workflows/update-libs.yml @@ -74,6 +74,7 @@ jobs: ["icons-repository"]="icons-repository.cgp" ["rainbow-on-the-go"]="rainbow-on-the-go.cgp" ["ai-literacy-course"]="ai-literacy-course.cgp" + ["flutter-template"]="flutter-template.cgp" ) for module in "${!MAP[@]}"; do src=$(ls "${module}/build/plugin/"*.cgp 2>/dev/null | head -n1) diff --git a/CLAUDE.md b/CLAUDE.md index e6e5af8c..776ac2b5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,7 +42,9 @@ Every plugin depends on two jars in the repo-root `libs/`: - **`plugin-api.jar`** — the IDE-side API surface (`IPlugin`, `PluginContext`, `BuildStatusListener`, `IdeBuildService`, etc.). Each plugin uses it as `compileOnly` (provided by the IDE at runtime) AND as `buildscript classpath` so the Gradle plugin can resolve symbols at configuration time. - **`gradle-plugin.jar`** — the Gradle plugin with id `com.itsaky.androidide.plugins.build`, applied by every plugin. It's the output of CoGo's `plugin-api/plugin-builder/` module (separate from CoGo's `gradle-plugin/` module, which is unrelated despite the name). It packages the compiled Android library into a `.cgp`. -Both jars are referenced via `../libs/*.jar`. **A plugin folder is not standalone in isolation** — copy `libs/` along if you move one elsewhere. When CoGo's API changes, refresh via the script above or the **Update libs from CodeOnTheGo** GitHub Action (which also commits the refreshed jars, cuts a release, and deploys `.cgp` files to the website). +There is also **one shared Gradle wrapper at the repo root** (`gradlew` + `gradle/wrapper/`). New plugins should use it — build them with `cd && ../gradlew assemblePlugin` rather than bundling a per-plugin `gradlew`/`gradle/wrapper/` copy. (`flutter-template` follows this; most older plugins still carry their own local wrapper and can be migrated opportunistically.) + +Both jars are referenced via `../libs/*.jar`. **Always use the repo-root `libs/` jars and the repo-root Gradle wrapper — never bundle per-plugin copies.** A plugin that ships its own `libs/plugin-api.jar` / `libs/gradle-plugin.jar` (e.g. copied from another plugin) can drift out of sync with the rest of the repo; point `build.gradle.kts` (`compileOnly`) and `settings.gradle.kts` (buildscript `classpath`) at `../libs/*.jar` and delete any local `libs/`. The root `plugin-api.jar` already carries the full API surface (including `IdeTemplateService`/`CgtTemplateBuilder`), so newer sub-APIs do not justify a local copy. **A plugin folder is not standalone in isolation** — copy the root `libs/` along if you move one elsewhere. When CoGo's API changes, refresh via the script above or the **Update libs from CodeOnTheGo** GitHub Action (which also commits the refreshed jars, cuts a release, and deploys `.cgp` files to the website). ### Plugin shape @@ -80,12 +82,20 @@ Plugins that extract bundled assets on-device once (currently `ai-literacy-cours **Any change to extraction OR post-extraction generation logic must bump `INSTALL_VERSION`.** Otherwise the change compiles and packages cleanly but has zero effect on existing installs — they keep the stale extracted tree, and it looks like "my fix didn't work" (costing a device round-trip). Bumping the constant forces a clean re-extract. On a device with a prior install, confirm the marker version changed (or wipe plugin data) before concluding a fix works — see Verification below. +### Template-installer plugins (Pebble `.cgt`) + +Some plugins are headless template installers (`flutter-template`, `pebble-custom-function-template-installer`): on `activate()` they register project templates with `IdeTemplateService` (building each `.cgt` from Pebble `.peb` skeletons under `src/main/assets/templates//`), and unregister on `deactivate()`. The templates then appear on the New Project screen beside the core ones. The source-of-truth skeletons follow the pattern in `~/src/dev-assets/templates`. + +**Pebble gotcha:** a bare `${{TAG}}` at end-of-line loses its trailing newline to Pebble's newline-trimming and merges with the next line (this silently produced invalid YAML by collapsing `name:` and `description:` into one line). Ensure a non-newline character follows `}}` — the convention is to quote the value, e.g. `name: "${{APP_NAME | lower}}"`. + ## Verification **`./gradlew assemblePlugin` succeeding is not verification** — it only proves the plugin compiles and packages. Real verification for these plugins is device-level: push the built `.cgp` to a connected emulator/device, install through CoGo's Plugin Manager, exercise the feature end-to-end, and observe the expected behavior (UI element appears, file written, build hook fires, DB row replaced, etc.). If device verification isn't possible in-session, say so explicitly rather than calling the change verified. Build success is necessary but never sufficient — this applies especially to plugins that mutate IDE state (`documentation.db`, settings, filesystem, project structure). +**Launching CoGo via adb.** Do **not** launch with `monkey` or a bare LAUNCHER intent (`adb shell monkey -p com.itsaky.androidide …`) — debug builds bundle **LeakCanary**, which registers its own launcher activity, so the intent can open LeakCanary's "Leaks" screen or a disambiguation chooser instead of the IDE. Start the explicit component: `adb shell am start -n com.itsaky.androidide/.activities.SplashActivity`. When re-verifying a plugin **icon** change under the same plugin id, note that the Plugin Manager caches icons via Glide (`cache/image_manager_disk_cache`) keyed by path without mtime invalidation — the old icon persists until that cache is cleared (`adb root`, delete the dir, restart) or you install on a clean device. + ## Adding a new plugin 1. Copy `random-xkcd/` — it's the canonical starting template (small but complete, includes the in-IDE help HTML pattern that submissions are expected to follow). diff --git a/README.md b/README.md index ce14550d..8904aa01 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ See the official [plugin documentation](https://www.appdevforall.org/codeonthego | [`compose-preview/`](compose-preview/) | Renders Jetpack Compose `@Preview` functions on-device — no full app build or run. | | [`ai-literacy-course/`](ai-literacy-course/) | Bundles Learn AI Anywhere's offline "Introduction to AI" course (26 videos + interactive activities) and plays it full-screen, fully offline. | | [`layout-editor/`](layout-editor/) | Visual drag-and-drop editor for Android XML layouts. | +| [`flutter-template/`](flutter-template/) | Adds Flutter starter project templates (Basic, BLoC, Provider, GetX, Riverpod) to the New Project screen. | ## Building a plugin diff --git a/docs/process/learnings.md b/docs/process/learnings.md index 10dee5bb..fecaf693 100644 --- a/docs/process/learnings.md +++ b/docs/process/learnings.md @@ -10,13 +10,20 @@ Cross-session gotchas, discoveries, and patterns worth not re-deriving. - Pulling SQLite databases out of an emulator-installed app: - First try `adb -s shell run-as cat databases/ > local.db`. This only works if the app's APK has `android:debuggable="true"`. Code On The Go's release builds do not, so `run-as` returns "package not debuggable" and writes that error string to your output file. - - On an emulator (root-capable), the fallback is `adb root` (one-time per boot) followed by `adb pull /data/data//databases/ .`. This bypasses the debuggable check entirely. + - On an emulator (root-capable), the fallback is `adb root` (one-time per boot) followed by `adb pull /data/data//databases/ .`. This bypasses the debuggable check entirely. Reach for `adb root` early when inspecting/clearing on-device app data — it's far more reliable than fighting `run-as`'s working-directory quirks. +- **Launching Code On The Go via adb — don't use `monkey`.** CoGo *debug* builds bundle **LeakCanary**, which registers its own launcher activity, so `adb shell monkey -p com.itsaky.androidide -c android.intent.category.LAUNCHER 1` (or any bare LAUNCHER intent) may open LeakCanary's "Leaks" screen or a disambiguation chooser instead of the IDE, and the IDE's own activities are not exported (direct `am start` on them is denied). Launch the explicit launcher component: `adb shell am start -n com.itsaky.androidide/.activities.SplashActivity`. +- **Plugin Manager icons are Glide-cached.** CoGo loads plugin icons through Glide's disk cache (`/data/data/com.itsaky.androidide/cache/image_manager_disk_cache`), keyed by path without mtime invalidation. Reinstalling a plugin under the same `plugin.id` with changed `icon_day/night.png` keeps showing the **old** icon (the on-disk extracted icon under `app_plugin_icons//` updates correctly, but the rendered bitmap is stale). Clear that cache dir (via `adb root`) and restart, or install on a clean device, to confirm an icon change. Template *thumbnails* are not affected — they're re-read from the `.cgt` each time. ## Plugin build & install gotchas - **On-device install markers hide code changes.** `ai-literacy-course`'s `CourseInstaller` extracts its bundle once and gates it behind `.installed-v`; if the marker exists, extraction *and* `CourseShell.generate()` are skipped. A logic fix (e.g. lesson-item ordering) has zero on-device effect until `INSTALL_VERSION` is bumped — it looks like "the fix didn't work" and costs a device round-trip. Bump the version constant as part of any extraction/generation change. - **`assemblePlugin` silently ships broken `.cgp`s when downloaded assets are missing.** Plugins with a `downloadAssets` task (`ai-literacy-course` → course ZIP + `pdfjs.zip`; `ndk-installer-plugin`) don't fetch those assets during a plain `assemblePlugin`, and there's no build-time warning — the missing asset only surfaces as a runtime failure on device (`Bundled asset not found: pdfjs.zip` → "Could not prepare the course"). Run `./gradlew downloadAssets assemblePlugin` (or `scripts/update-libs.sh`) and `unzip -l` the `.cgp` to confirm assets are present before handing it over. +## CoGo project templates (Pebble `.cgt`) + +- **A bare `${{TAG}}` at end-of-line loses its trailing newline.** CoGo's Pebble renderer trims the newline after a standalone substitution tag, so a `.peb` line like `name: ${{APP_NAME | lower}}` renders as `name: myapp` immediately followed by the *next* line with no break — e.g. `name: myappdescription: ...`, which is invalid YAML and silently corrupts the generated `pubspec.yaml`. `assemblePlugin` and `unzip -l` show nothing wrong; it only surfaces when you generate a project on device. Fix: put the tag inside quotes or otherwise make it not the last token on the line — `name: "${{APP_NAME | lower}}"`. Applies to any generated file where a value must stay on its own line (YAML keys, etc.). Dart's own `$var`/`${expr}` are *not* Pebble tokens (`${` + letter ≠ `${{`), so Dart interpolation passes through untouched. +- **Register templates headlessly via `IdeTemplateService`, not by writing to `/sdcard`.** The correct way to add project templates is a UI-less `IPlugin` that, on `activate()`, builds a `.cgt` from bundled Pebble assets (`createTemplateBuilder(...).addStaticFromAssets(...).build(getPluginDirectory())`) and calls `registerTemplate(file)`; on `deactivate()` it `unregisterTemplate(name)` + deletes the staged file. Model: `pebble-custom-function-template-installer` / `flutter-template`. Writing generated files directly to `Environment.getExternalStorageDirectory()` breaks under scoped storage (`targetSdk` ≥ 30) — that was the defect in the original Flutter Template submission. + ## PDF debugging without pdftotext - When `pdftotext`/`mutool`/`qpdf` aren't installed and you need to read a PDF's /Title or /Author, the Info dictionary is often inside a flate-encoded object stream. Walk every `stream ... endstream` block, `zlib.decompress` each, and regex for `/(Title|Author|Subject|Creator|Producer|Keywords)\s*\(...\)` in the decoded bytes — and for hex-encoded `` UTF-16BE strings. This recovered the Morelli & Walde authorship of `JavaJavaJava.pdf` in the bookshelf session. diff --git a/docs/process/retrospective.md b/docs/process/retrospective.md index edf5866d..5a01e036 100644 --- a/docs/process/retrospective.md +++ b/docs/process/retrospective.md @@ -1,5 +1,46 @@ # Retrospectives +## 2026-07-09 — Flutter Template plugin: review + rebuild on the CoGo template system (ADFA-3857, PR #44) + +### Time Breakdown +| Started | Phase | 👤 Hands-On Time | 🤖 Agent Time | Problems | +|---------|-------|-----------------|---------------|----------| +| Jul 9 10:16pm | PR #43 review — `/plugin-review` on `pair`, post inline PR comments | █ 5m | ██ 12m | | +| Jul 10 1:35am | Scope + plan — Jira triage, dev-assets/Pebble study (3 Explore agents), AskUserQuestion, approved plan | ████ 20m | ████ 22m | | +| Jul 10 1:49am | Build — rebuild submission as headless `IdeTemplateService` installer, author 5 Pebble `.cgt` templates | ██ 12m | █████ 50m | ⚠ `pubspec name:` newline-trim bug (caught on device) | +| Jul 10 2:30am | Icons — Flutter day/night plugin icons + 5 template thumbnails (ImageMagick) | █ 6m | ███ 25m | ⚠ Glide plugin-icon disk cache showed stale PCF icon | +| Jul 10 2:44am | Verify + ship — device install/generate/substitute, commit, push, PR #44 | ██ 10m | ████ 22m | ⚠ LeakCanary hijacked `monkey` launch; emulator UI flakiness | +| Jul 10 4:15am | Retro + root consolidation — shared repo-root `libs/`+wrapper, docs | █ 5m | ██ 15m | | + +### Metrics +| Metric | Duration | +|--------|----------| +| Total wall-clock (active, excl. two long idle gaps) | ~3h | +| Hands-on | ~1h (rough; the analyzer over-counts AskUserQuestion/plan text as typing) | +| Automated agent time | ~2h 20m | +| Idle/away (overnight + compaction gap) | ~16h | +| Retro analysis time | ~10 min | + +### Key Observations +- **Rebuild-not-patch was the right call, and verifying the API first avoided PR #43's failure mode.** Ali's submission wrote hardcoded Dart to `/sdcard` (broken under scoped storage) because his bundled `plugin-api.jar` lacked `IdeTemplateService`. PR #43 had just failed by calling an *unreleased* API; here I confirmed `IdeTemplateService`/`CgtTemplateBuilder` exist in the **repo-root** `libs/` before building. Trusting the root jar (not a per-plugin copy) is what made the installer approach safe. +- **Device verification earned its keep — again.** `assemblePlugin` + `unzip -l` looked clean, but installing on the emulator and actually generating a project surfaced the `pubspec.yaml` `name:` corruption (Pebble trimmed the newline after a bare `${{APP_NAME | lower}}`, merging it into `description:`). This is the second consecutive session where build-success masked a real defect. +- **Two IDE-side quirks cost real time:** LeakCanary intercepting the `monkey` LAUNCHER intent (opened its Leaks screen instead of the IDE), and Glide's path-keyed plugin-icon disk cache showing the stale PCF icon after reinstall. Both are now documented so they're one-line fixes next time. +- **Explore agents front-loaded the design well.** Three parallel Explore agents (dev-assets pattern, Pebble mechanics, PCFInstaller shape) delivered the whole recipe before any code was written — the build phase had almost no false starts. + +### Feedback +**What worked:** The plan-first approach (Explore agents → AskUserQuestion on the 5-variant/SDK scope → approved plan) meant the rebuild went cleanly. Device verification caught the pubspec bug. +**What didn't:** "i feel like leak canary slowed us down" — the `monkey`-launch detour into LeakCanary's UI was avoidable. Emulator UI-automation flakiness (ANRs, empty bounds) made the final device re-verify not worth it. + +### Actions Taken +| Issue | Action Type | Change | +|-------|-------------|--------| +| LeakCanary hijacked `monkey` app-launch | CLAUDE.md + learnings.md | Documented launching via `am start -n com.itsaky.androidide/.activities.SplashActivity` (Verification §; learnings "Android / adb") | +| Pebble bare-tag newline-trim silently corrupts generated files | CLAUDE.md + learnings.md | Documented quoting values that must survive on their own line: `name: "${{APP_NAME \| lower}}"` (template-installer subsection; new learnings section) | +| Glide plugin-icon disk cache shows stale icon after reinstall | learnings.md (bug already tracked) | Documented cache-clear (`adb root` + rm `image_manager_disk_cache`). Underlying CoGo bug is already filed as **ADFA-4446** (Glide load in `PluginListAdapter.kt` lacks a content signature) — no new ticket needed | +| Per-plugin `libs/` and gradle-wrapper copies drift from repo | CLAUDE.md + code | Mandated repo-root shared `libs/` **and** a single repo-root Gradle wrapper; promoted the wrapper to root and pointed `flutter-template` at `../gradlew` / `../libs/*.jar`; deleted its local copies | + +**Glide icon-cache bug — already tracked as [ADFA-4446](https://appdevforall.atlassian.net/browse/ADFA-4446)** (filed 2026-06-25). Root cause: `PluginListAdapter.kt` (~line 71) loads the extracted icon `File` with Glide and no cache signature, so `ObjectKey(File)` hashes the stable path and serves the old bitmap when content changes in place. Fix on file: `.signature(ObjectKey(iconFile.lastModified()))`. No new ticket was created; my session draft turned out to duplicate it. + ## 2026-07-01 — Code review (PR #31) + AI Literacy Course ordering fix (PR #36) ### Time Breakdown diff --git a/flutter-template/.gitignore b/flutter-template/.gitignore new file mode 100644 index 00000000..24227e33 --- /dev/null +++ b/flutter-template/.gitignore @@ -0,0 +1,35 @@ +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Log/OS Files +*.log +.DS_Store + +# Android Studio generated files and folders +captures/ +.externalNativeBuild/ +.cxx/ +*.aab +*.apk +output-metadata.json + +# IntelliJ +*.iml +.idea/ + +# Keystore / signing material — never commit +*.jks +*.keystore +release.properties +signing.properties +keystore.properties + +# Google Services +google-services.json + +# Android Profiling +*.hprof diff --git a/flutter-template/README.md b/flutter-template/README.md new file mode 100644 index 00000000..e73e90f8 --- /dev/null +++ b/flutter-template/README.md @@ -0,0 +1,52 @@ +# Flutter Template + +A [Code on the Go](https://github.com/appdevforall/CodeOnTheGo) plugin that adds **Flutter starter +project templates** to the IDE's New Project screen, alongside the built-in core templates. + +It contributes five templates, one per state-management approach: + +| Template | State management | +|---|---| +| Flutter Basic | none (plain `setState`) | +| Flutter BLoC | `flutter_bloc` | +| Flutter Provider | `provider` | +| Flutter GetX | `get` | +| Flutter Riverpod | `flutter_riverpod` | + +Each generates a small, idiomatic Flutter project (a counter app) with `pubspec.yaml`, `lib/`, +and `analysis_options.yaml`, substituting the app name and package name you enter in the New +Project dialog. + +## How it works + +The plugin is a headless installer. On `activate()` it registers each template with the IDE via +`IdeTemplateService`, building a `.cgt` from the Pebble skeletons bundled under +`src/main/assets/templates//`; on `deactivate()` it unregisters them and deletes the +staged files. There is no UI — the templates simply appear in **New Project** after the plugin is +enabled. This mirrors the `pebble-custom-function-template-installer` example. + +## Building + +This plugin uses the repo-root Gradle wrapper and shared `../libs/` jars — it has no local copies. +Build it from its own directory via the parent wrapper: + +```sh +cd flutter-template +../gradlew clean assemblePlugin +``` + +The `.cgp` lands in `build/plugin/fluttertemplate.cgp`. Install it from inside Code on the Go via +the Plugin Manager, then open **New Project** to see the Flutter templates. + +## Note on the Flutter SDK + +This plugin **scaffolds project files only** — it does not install the Flutter/Dart SDK. Code on +the Go does not yet ship an on-device Flutter toolchain, so building or running a generated project +requires Flutter on a separate machine (or a future on-device SDK installer). Enter an app name +that is a valid Dart package identifier (lowercase, no spaces — underscores are fine); it is +lower-cased for `pubspec.yaml`'s `name:` field. + +## Credit + +Contributed by **Ali** via the Code on the Go community submission process, rebuilt on the IDE's +template system. diff --git a/flutter-template/build.gradle.kts b/flutter-template/build.gradle.kts new file mode 100644 index 00000000..a2a2692a --- /dev/null +++ b/flutter-template/build.gradle.kts @@ -0,0 +1,73 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.itsaky.androidide.plugins.build") +} + +pluginBuilder { + pluginName = "fluttertemplate" +} + +android { + namespace = "com.ali.fluttertemplate" + compileSdk = 36 + + defaultConfig { + applicationId = "com.ali.fluttertemplate" + minSdk = 21 + targetSdk = 36 + versionCode = 2 + versionName = "2.0.0" + } + + buildTypes { + release { + isMinifyEnabled = false + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + packaging { + resources { + excludes += setOf( + "META-INF/versions/9/OSGI-INF/MANIFEST.MF", + "META-INF/DEPENDENCIES", + "META-INF/LICENSE", + "META-INF/LICENSE.txt", + "META-INF/NOTICE", + "META-INF/NOTICE.txt" + ) + } + } +} + +dependencies { + compileOnly(files("../libs/plugin-api.jar")) + + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.10.0") + implementation("org.jetbrains.kotlin:kotlin-stdlib:2.1.0") +} + +tasks.withType().configureEach { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + freeCompilerArgs.add("-Xskip-metadata-version-check") + } +} + +tasks.wrapper { + gradleVersion = "8.14.3" + distributionType = Wrapper.DistributionType.BIN +} + +tasks.matching { + it.name.contains("checkDebugAarMetadata") || + it.name.contains("checkReleaseAarMetadata") +}.configureEach { + enabled = false +} diff --git a/flutter-template/fluttertemplate.html b/flutter-template/fluttertemplate.html new file mode 100644 index 00000000..336f0218 --- /dev/null +++ b/flutter-template/fluttertemplate.html @@ -0,0 +1,289 @@ + + + + + +Flutter Template Plugin Documentation + + + + +
+

Flutter Template

+

Adds Flutter starter project templates to Code on the Go's New Project screen

+
Version 2.0.0 · Author: Ali · Package: com.ali.fluttertemplate
+
+ + + +
+

1. Executive Overview

+

+ Flutter Template is a Code on the Go plugin that adds ready-to-use Flutter starter + project templates to the IDE's New Project screen, sitting alongside the built-in + core (Android) templates. Instead of starting from an empty folder, a developer picks a + Flutter template, enters an app name and package name, and the IDE generates a complete, + idiomatic Flutter project skeleton. +

+

+ The plugin contributes five templates — one for a plain Flutter app and four wired to + popular state-management libraries — so a project starts with the chosen architecture + already in place. +

+

+ It is a headless installer: it has no screens of its own. When enabled, it + registers its templates with the IDE through the public plugin-api template + service; when disabled, it removes them cleanly. +

+
+ +
+

2. Core Functionality

+ +

The five templates

+ + + + + + + + + + + +
TemplateState managementKey dependency
Flutter BasicNone (plain setState)
Flutter BLoCBLoC patternflutter_bloc
Flutter ProviderProviderprovider
Flutter GetXGetXget
Flutter RiverpodRiverpodflutter_riverpod
+ +

What each template generates

+

+ Every template produces a small counter app so the chosen pattern is demonstrated end to end: +

+
    +
  • pubspec.yaml — project name (lower-cased from the app name), the SDK + constraint, and the state-management dependency.
  • +
  • lib/main.dart — the app entry point and home screen.
  • +
  • A pattern-specific source file where applicable — e.g. + lib/blocs/counter_bloc.dart, lib/providers/counter_provider.dart, + or lib/controllers/counter_controller.dart.
  • +
  • analysis_options.yaml — a lint baseline.
  • +
  • README.md — getting-started notes for the generated project.
  • +
+ +

Parameter substitution

+

+ The app name and package name entered in the New Project dialog are substituted into the + generated files. The app name also drives pubspec.yaml's name: + field, lower-cased to satisfy Dart's package-name rules. +

+
+ +
+

3. Technical Architecture

+ +

Template system integration

+

+ Code on the Go discovers project templates by scanning a templates directory for .cgt + archives. A plugin adds its own by calling IdeTemplateService.registerTemplate(), + which places the archive where the IDE will find it and reloads the template list — so the + plugin's templates appear next to the core ones with no host changes. +

+ +

Authoring with Pebble

+

+ Each template is authored as a project skeleton bundled in the plugin's assets. Files ending in + .peb are processed by the Pebble template engine at project-creation time + (${{APP_NAME}}, ${{PACKAGE_NAME}} and friends are substituted); other + files are copied verbatim. This is the same authoring format the core templates use. Dart's own + $variable string interpolation passes through untouched because it does not match + Pebble's ${{ }} delimiter. +

+ +

Lifecycle

+
+ initialize(context) + | acquire IdeTemplateService + IdeFileService + v + activate() + | for each of the 5 variants: + | createTemplateBuilder(name) + | .description(...).showPackageNameOption() + | .thumbnailFromAssets(...) + | .addStaticFromAssets(each bundled file) + | build() -> .cgt -> registerTemplate(.cgt) + v + (templates now visible on the New Project screen) + | + v + deactivate() + | unregisterTemplate(name) + delete staged .cgt for each + v + dispose() -> release service references
+ +

Extension interfaces & services

+ + + + + + + + + +
TypeRole
IPluginLifecycle: initialize / activate / deactivate / dispose.
IdeTemplateServiceBuild and register/unregister the .cgt templates.
IdeFileServiceDelete staged template files on deactivation.
+ +

Permissions

+

+ The plugin requests only filesystem.read and filesystem.write — + what the template service needs to stage and register archives. No network, no native code, no + system commands. +

+
+ +
+

4. Usage

+ +

Install

+
    +
  1. Build the plugin: cd flutter-template && ./gradlew clean assemblePlugin + (produces build/plugin/fluttertemplate.cgp).
  2. +
  3. In Code on the Go, open Preferences → Plugin Manager → + and select the + fluttertemplate.cgp file.
  4. +
  5. Enable the plugin. Its templates register automatically.
  6. +
+ +

Create a project

+
    +
  1. Open New Project.
  2. +
  3. Pick one of the Flutter templates (Basic, BLoC, Provider, GetX, Riverpod).
  4. +
  5. Enter an app name (a valid Dart package identifier — lowercase, no + spaces; underscores are fine) and a package name.
  6. +
  7. Finish the wizard. The generated project appears in your workspace.
  8. +
+ +
+ Building the generated project requires the Flutter SDK, which Code on the Go + does not yet provide on-device. Run flutter pub get / flutter run on a + machine with Flutter installed, or once an on-device Flutter SDK becomes available. +
+
+ +
+

5. Key Benefits

+
    +
  • First-class integration. Templates appear on the standard New Project + screen next to the core templates — no separate UI to learn.
  • +
  • Architecture on day one. Pick your state-management approach up front and + start from working, idiomatic boilerplate.
  • +
  • Parameterized. App name and package name flow into the generated files, + including a Dart-valid pubspec.yaml name.
  • +
  • Clean and reversible. A headless installer that registers on enable and + fully unregisters on disable — no leftover state.
  • +
  • Plugin-API only. Built entirely on the stable plugin-api + contract, using the same Pebble template format as the built-in templates.
  • +
+
+ +
+

6. Limitations & Scope

+
    +
  • The plugin scaffolds project files only; it does not install the + Flutter/Dart SDK or build/run Flutter projects on-device.
  • +
  • Code on the Go's editor does not currently provide Dart syntax highlighting, completion, or + analysis — these templates create the files, but rich Dart editing is outside the IDE's + current language support.
  • +
  • On-device Flutter toolchain support is a separate, larger effort tracked independently.
  • +
+
+ +
+ Flutter Template Plugin Documentation · Version 2.0.0 · com.ali.fluttertemplate +
+ + + diff --git a/flutter-template/gradle.properties b/flutter-template/gradle.properties new file mode 100644 index 00000000..21a36623 --- /dev/null +++ b/flutter-template/gradle.properties @@ -0,0 +1,5 @@ +android.useAndroidX=true +android.nonTransitiveRClass=true +kotlin.code.style=official +org.gradle.jvmargs=-Xmx2560m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 +org.gradle.caching=true diff --git a/flutter-template/settings.gradle.kts b/flutter-template/settings.gradle.kts new file mode 100644 index 00000000..0342a1b2 --- /dev/null +++ b/flutter-template/settings.gradle.kts @@ -0,0 +1,30 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath(files("../libs/plugin-api.jar")) + classpath(files("../libs/gradle-plugin.jar")) + classpath("com.android.tools.build:gradle:8.11.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.0") + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "fluttertemplate" diff --git a/flutter-template/src/main/AndroidManifest.xml b/flutter-template/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c68f0397 --- /dev/null +++ b/flutter-template/src/main/AndroidManifest.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter-template/src/main/assets/icon_day.png b/flutter-template/src/main/assets/icon_day.png new file mode 100644 index 00000000..ab92f399 Binary files /dev/null and b/flutter-template/src/main/assets/icon_day.png differ diff --git a/flutter-template/src/main/assets/icon_night.png b/flutter-template/src/main/assets/icon_night.png new file mode 100644 index 00000000..d9be4054 Binary files /dev/null and b/flutter-template/src/main/assets/icon_night.png differ diff --git a/flutter-template/src/main/assets/templates/FlutterBasic/README.md.peb b/flutter-template/src/main/assets/templates/FlutterBasic/README.md.peb new file mode 100644 index 00000000..faacc132 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterBasic/README.md.peb @@ -0,0 +1,27 @@ +# ${{APP_NAME}} + +Generated by the Flutter Template plugin for Code on the Go. + +- Package: `${{PACKAGE_NAME}}` +- Template: Basic +- Flutter SDK: >=3.0.0 + +## Getting started + +> **Note:** Building a Flutter project requires the Flutter SDK, which is **not** +> installed on-device by Code on the Go. Run these on a machine with Flutter, or +> once an on-device Flutter SDK becomes available. + +```sh +flutter pub get +flutter run +``` + +## Project structure + +``` +lib/ + main.dart +pubspec.yaml +analysis_options.yaml +``` diff --git a/flutter-template/src/main/assets/templates/FlutterBasic/analysis_options.yaml b/flutter-template/src/main/assets/templates/FlutterBasic/analysis_options.yaml new file mode 100644 index 00000000..b54efe91 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterBasic/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - prefer_const_constructors + - prefer_const_literals_to_create_immutables + - avoid_print diff --git a/flutter-template/src/main/assets/templates/FlutterBasic/lib/main.dart.peb b/flutter-template/src/main/assets/templates/FlutterBasic/lib/main.dart.peb new file mode 100644 index 00000000..0f5d211f --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterBasic/lib/main.dart.peb @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: '${{APP_NAME}}', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _counter = 0; + + void _increment() { + setState(() { + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('${{APP_NAME}}'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('You have pushed the button this many times:'), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _increment, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/flutter-template/src/main/assets/templates/FlutterBasic/pubspec.yaml.peb b/flutter-template/src/main/assets/templates/FlutterBasic/pubspec.yaml.peb new file mode 100644 index 00000000..98209d96 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterBasic/pubspec.yaml.peb @@ -0,0 +1,20 @@ +name: "${{APP_NAME | lower}}" +description: Flutter project created by the Flutter Template plugin +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.8 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + +flutter: + uses-material-design: true diff --git a/flutter-template/src/main/assets/templates/FlutterBasic/template/thumb.png b/flutter-template/src/main/assets/templates/FlutterBasic/template/thumb.png new file mode 100644 index 00000000..49f0a3bd Binary files /dev/null and b/flutter-template/src/main/assets/templates/FlutterBasic/template/thumb.png differ diff --git a/flutter-template/src/main/assets/templates/FlutterBloc/README.md.peb b/flutter-template/src/main/assets/templates/FlutterBloc/README.md.peb new file mode 100644 index 00000000..dd2e5ce3 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterBloc/README.md.peb @@ -0,0 +1,29 @@ +# ${{APP_NAME}} + +Generated by the Flutter Template plugin for Code on the Go. + +- Package: `${{PACKAGE_NAME}}` +- Template: BLoC +- Flutter SDK: >=3.0.0 + +## Getting started + +> **Note:** Building a Flutter project requires the Flutter SDK, which is **not** +> installed on-device by Code on the Go. Run these on a machine with Flutter, or +> once an on-device Flutter SDK becomes available. + +```sh +flutter pub get +flutter run +``` + +## Project structure + +``` +lib/ + main.dart + blocs/ + counter_bloc.dart +pubspec.yaml +analysis_options.yaml +``` diff --git a/flutter-template/src/main/assets/templates/FlutterBloc/analysis_options.yaml b/flutter-template/src/main/assets/templates/FlutterBloc/analysis_options.yaml new file mode 100644 index 00000000..b54efe91 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterBloc/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - prefer_const_constructors + - prefer_const_literals_to_create_immutables + - avoid_print diff --git a/flutter-template/src/main/assets/templates/FlutterBloc/lib/blocs/counter_bloc.dart.peb b/flutter-template/src/main/assets/templates/FlutterBloc/lib/blocs/counter_bloc.dart.peb new file mode 100644 index 00000000..b4377c82 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterBloc/lib/blocs/counter_bloc.dart.peb @@ -0,0 +1,14 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +abstract class CounterEvent {} + +class IncrementEvent extends CounterEvent {} + +class DecrementEvent extends CounterEvent {} + +class CounterBloc extends Bloc { + CounterBloc() : super(0) { + on((event, emit) => emit(state + 1)); + on((event, emit) => emit(state - 1)); + } +} diff --git a/flutter-template/src/main/assets/templates/FlutterBloc/lib/main.dart.peb b/flutter-template/src/main/assets/templates/FlutterBloc/lib/main.dart.peb new file mode 100644 index 00000000..a856e4d9 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterBloc/lib/main.dart.peb @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'blocs/counter_bloc.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: '${{APP_NAME}}', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: BlocProvider( + create: (_) => CounterBloc(), + child: const HomePage(), + ), + ); + } +} + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('${{APP_NAME}} - BLoC'), + ), + body: Center( + child: BlocBuilder( + builder: (context, count) { + return Text( + '$count', + style: Theme.of(context).textTheme.headlineMedium, + ); + }, + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => context.read().add(IncrementEvent()), + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/flutter-template/src/main/assets/templates/FlutterBloc/pubspec.yaml.peb b/flutter-template/src/main/assets/templates/FlutterBloc/pubspec.yaml.peb new file mode 100644 index 00000000..b9f5d849 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterBloc/pubspec.yaml.peb @@ -0,0 +1,22 @@ +name: "${{APP_NAME | lower}}" +description: Flutter BLoC project created by the Flutter Template plugin +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_bloc: ^8.1.4 + bloc: ^8.1.4 + equatable: ^2.0.5 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + +flutter: + uses-material-design: true diff --git a/flutter-template/src/main/assets/templates/FlutterBloc/template/thumb.png b/flutter-template/src/main/assets/templates/FlutterBloc/template/thumb.png new file mode 100644 index 00000000..4e0ec88c Binary files /dev/null and b/flutter-template/src/main/assets/templates/FlutterBloc/template/thumb.png differ diff --git a/flutter-template/src/main/assets/templates/FlutterGetx/README.md.peb b/flutter-template/src/main/assets/templates/FlutterGetx/README.md.peb new file mode 100644 index 00000000..8f6ae015 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterGetx/README.md.peb @@ -0,0 +1,29 @@ +# ${{APP_NAME}} + +Generated by the Flutter Template plugin for Code on the Go. + +- Package: `${{PACKAGE_NAME}}` +- Template: GetX +- Flutter SDK: >=3.0.0 + +## Getting started + +> **Note:** Building a Flutter project requires the Flutter SDK, which is **not** +> installed on-device by Code on the Go. Run these on a machine with Flutter, or +> once an on-device Flutter SDK becomes available. + +```sh +flutter pub get +flutter run +``` + +## Project structure + +``` +lib/ + main.dart + controllers/ + counter_controller.dart +pubspec.yaml +analysis_options.yaml +``` diff --git a/flutter-template/src/main/assets/templates/FlutterGetx/analysis_options.yaml b/flutter-template/src/main/assets/templates/FlutterGetx/analysis_options.yaml new file mode 100644 index 00000000..b54efe91 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterGetx/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - prefer_const_constructors + - prefer_const_literals_to_create_immutables + - avoid_print diff --git a/flutter-template/src/main/assets/templates/FlutterGetx/lib/controllers/counter_controller.dart.peb b/flutter-template/src/main/assets/templates/FlutterGetx/lib/controllers/counter_controller.dart.peb new file mode 100644 index 00000000..be749984 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterGetx/lib/controllers/counter_controller.dart.peb @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; + +class CounterController extends GetxController { + final _count = 0.obs; + + int get count => _count.value; + + void increment() => _count.value++; + void decrement() => _count.value--; + void reset() => _count.value = 0; +} diff --git a/flutter-template/src/main/assets/templates/FlutterGetx/lib/main.dart.peb b/flutter-template/src/main/assets/templates/FlutterGetx/lib/main.dart.peb new file mode 100644 index 00000000..b24a6585 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterGetx/lib/main.dart.peb @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'controllers/counter_controller.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return GetMaterialApp( + debugShowCheckedModeBanner: false, + title: '${{APP_NAME}}', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange), + useMaterial3: true, + ), + home: const HomePage(), + ); + } +} + +class HomePage extends StatelessWidget { + HomePage({super.key}); + + final CounterController controller = Get.put(CounterController()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('${{APP_NAME}} - GetX'), + ), + body: Center( + child: Obx(() => Text( + '${controller.count}', + style: Theme.of(context).textTheme.headlineMedium, + )), + ), + floatingActionButton: FloatingActionButton( + onPressed: controller.increment, + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/flutter-template/src/main/assets/templates/FlutterGetx/pubspec.yaml.peb b/flutter-template/src/main/assets/templates/FlutterGetx/pubspec.yaml.peb new file mode 100644 index 00000000..9f9351e5 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterGetx/pubspec.yaml.peb @@ -0,0 +1,20 @@ +name: "${{APP_NAME | lower}}" +description: Flutter GetX project created by the Flutter Template plugin +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + get: ^4.6.6 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + +flutter: + uses-material-design: true diff --git a/flutter-template/src/main/assets/templates/FlutterGetx/template/thumb.png b/flutter-template/src/main/assets/templates/FlutterGetx/template/thumb.png new file mode 100644 index 00000000..70874937 Binary files /dev/null and b/flutter-template/src/main/assets/templates/FlutterGetx/template/thumb.png differ diff --git a/flutter-template/src/main/assets/templates/FlutterProvider/README.md.peb b/flutter-template/src/main/assets/templates/FlutterProvider/README.md.peb new file mode 100644 index 00000000..e6d0e236 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterProvider/README.md.peb @@ -0,0 +1,29 @@ +# ${{APP_NAME}} + +Generated by the Flutter Template plugin for Code on the Go. + +- Package: `${{PACKAGE_NAME}}` +- Template: Provider +- Flutter SDK: >=3.0.0 + +## Getting started + +> **Note:** Building a Flutter project requires the Flutter SDK, which is **not** +> installed on-device by Code on the Go. Run these on a machine with Flutter, or +> once an on-device Flutter SDK becomes available. + +```sh +flutter pub get +flutter run +``` + +## Project structure + +``` +lib/ + main.dart + providers/ + counter_provider.dart +pubspec.yaml +analysis_options.yaml +``` diff --git a/flutter-template/src/main/assets/templates/FlutterProvider/analysis_options.yaml b/flutter-template/src/main/assets/templates/FlutterProvider/analysis_options.yaml new file mode 100644 index 00000000..b54efe91 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterProvider/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - prefer_const_constructors + - prefer_const_literals_to_create_immutables + - avoid_print diff --git a/flutter-template/src/main/assets/templates/FlutterProvider/lib/main.dart.peb b/flutter-template/src/main/assets/templates/FlutterProvider/lib/main.dart.peb new file mode 100644 index 00000000..18bcaba8 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterProvider/lib/main.dart.peb @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'providers/counter_provider.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (_) => CounterProvider(), + child: MaterialApp( + debugShowCheckedModeBanner: false, + title: '${{APP_NAME}}', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal), + useMaterial3: true, + ), + home: const HomePage(), + ), + ); + } +} + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('${{APP_NAME}} - Provider'), + ), + body: Center( + child: Consumer( + builder: (context, counter, _) { + return Text( + '${counter.count}', + style: Theme.of(context).textTheme.headlineMedium, + ); + }, + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => context.read().increment(), + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/flutter-template/src/main/assets/templates/FlutterProvider/lib/providers/counter_provider.dart.peb b/flutter-template/src/main/assets/templates/FlutterProvider/lib/providers/counter_provider.dart.peb new file mode 100644 index 00000000..52483599 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterProvider/lib/providers/counter_provider.dart.peb @@ -0,0 +1,22 @@ +import 'package:flutter/foundation.dart'; + +class CounterProvider extends ChangeNotifier { + int _count = 0; + + int get count => _count; + + void increment() { + _count++; + notifyListeners(); + } + + void decrement() { + _count--; + notifyListeners(); + } + + void reset() { + _count = 0; + notifyListeners(); + } +} diff --git a/flutter-template/src/main/assets/templates/FlutterProvider/pubspec.yaml.peb b/flutter-template/src/main/assets/templates/FlutterProvider/pubspec.yaml.peb new file mode 100644 index 00000000..e82f841d --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterProvider/pubspec.yaml.peb @@ -0,0 +1,20 @@ +name: "${{APP_NAME | lower}}" +description: Flutter Provider project created by the Flutter Template plugin +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + provider: ^6.1.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + +flutter: + uses-material-design: true diff --git a/flutter-template/src/main/assets/templates/FlutterProvider/template/thumb.png b/flutter-template/src/main/assets/templates/FlutterProvider/template/thumb.png new file mode 100644 index 00000000..6d53ec22 Binary files /dev/null and b/flutter-template/src/main/assets/templates/FlutterProvider/template/thumb.png differ diff --git a/flutter-template/src/main/assets/templates/FlutterRiverpod/README.md.peb b/flutter-template/src/main/assets/templates/FlutterRiverpod/README.md.peb new file mode 100644 index 00000000..54f3b1be --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterRiverpod/README.md.peb @@ -0,0 +1,29 @@ +# ${{APP_NAME}} + +Generated by the Flutter Template plugin for Code on the Go. + +- Package: `${{PACKAGE_NAME}}` +- Template: Riverpod +- Flutter SDK: >=3.0.0 + +## Getting started + +> **Note:** Building a Flutter project requires the Flutter SDK, which is **not** +> installed on-device by Code on the Go. Run these on a machine with Flutter, or +> once an on-device Flutter SDK becomes available. + +```sh +flutter pub get +flutter run +``` + +## Project structure + +``` +lib/ + main.dart + providers/ + counter_provider.dart +pubspec.yaml +analysis_options.yaml +``` diff --git a/flutter-template/src/main/assets/templates/FlutterRiverpod/analysis_options.yaml b/flutter-template/src/main/assets/templates/FlutterRiverpod/analysis_options.yaml new file mode 100644 index 00000000..b54efe91 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterRiverpod/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - prefer_const_constructors + - prefer_const_literals_to_create_immutables + - avoid_print diff --git a/flutter-template/src/main/assets/templates/FlutterRiverpod/lib/main.dart.peb b/flutter-template/src/main/assets/templates/FlutterRiverpod/lib/main.dart.peb new file mode 100644 index 00000000..c9c7df9e --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterRiverpod/lib/main.dart.peb @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'providers/counter_provider.dart'; + +void main() { + runApp(const ProviderScope(child: MyApp())); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: '${{APP_NAME}}', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo), + useMaterial3: true, + ), + home: const HomePage(), + ); + } +} + +class HomePage extends ConsumerWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(counterProvider); + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('${{APP_NAME}} - Riverpod'), + ), + body: Center( + child: Text( + '$count', + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => ref.read(counterProvider.notifier).increment(), + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/flutter-template/src/main/assets/templates/FlutterRiverpod/lib/providers/counter_provider.dart.peb b/flutter-template/src/main/assets/templates/FlutterRiverpod/lib/providers/counter_provider.dart.peb new file mode 100644 index 00000000..7eb0cf31 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterRiverpod/lib/providers/counter_provider.dart.peb @@ -0,0 +1,13 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class CounterNotifier extends StateNotifier { + CounterNotifier() : super(0); + + void increment() => state++; + void decrement() => state--; + void reset() => state = 0; +} + +final counterProvider = StateNotifierProvider( + (ref) => CounterNotifier(), +); diff --git a/flutter-template/src/main/assets/templates/FlutterRiverpod/pubspec.yaml.peb b/flutter-template/src/main/assets/templates/FlutterRiverpod/pubspec.yaml.peb new file mode 100644 index 00000000..c89c3751 --- /dev/null +++ b/flutter-template/src/main/assets/templates/FlutterRiverpod/pubspec.yaml.peb @@ -0,0 +1,21 @@ +name: "${{APP_NAME | lower}}" +description: Flutter Riverpod project created by the Flutter Template plugin +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_riverpod: ^2.5.1 + riverpod: ^2.5.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + +flutter: + uses-material-design: true diff --git a/flutter-template/src/main/assets/templates/FlutterRiverpod/template/thumb.png b/flutter-template/src/main/assets/templates/FlutterRiverpod/template/thumb.png new file mode 100644 index 00000000..c7d53173 Binary files /dev/null and b/flutter-template/src/main/assets/templates/FlutterRiverpod/template/thumb.png differ diff --git a/flutter-template/src/main/kotlin/com/ali/fluttertemplate/FlutterTemplate.kt b/flutter-template/src/main/kotlin/com/ali/fluttertemplate/FlutterTemplate.kt new file mode 100644 index 00000000..3566a2a9 --- /dev/null +++ b/flutter-template/src/main/kotlin/com/ali/fluttertemplate/FlutterTemplate.kt @@ -0,0 +1,137 @@ +package com.ali.fluttertemplate + +import android.content.res.AssetManager +import android.util.Log +import com.itsaky.androidide.plugins.IPlugin +import com.itsaky.androidide.plugins.PluginContext +import com.itsaky.androidide.plugins.services.IdeFileService +import com.itsaky.androidide.plugins.services.IdeTemplateService +import java.io.File + +/** + * Headless installer plugin. On [activate] it registers five Flutter starter + * templates with the IDE so they appear on the New Project screen alongside the + * built-in core templates; on [deactivate] it unregisters them and deletes the + * staged `.cgt` files. + * + * Each template is authored as a Pebble skeleton bundled under + * `assets/templates//`. The template metadata (name, description, + * package-name option, parameters) is generated by [IdeTemplateService]'s + * builder — the bundled `template/` folder only supplies the thumbnail. + * + * This replaces the original submission's approach of writing hardcoded Dart + * files to external storage, which fails under scoped storage on modern devices. + */ +class FlutterTemplate : IPlugin { + + private lateinit var context: PluginContext + private var templateService: IdeTemplateService? = null + private var fileService: IdeFileService? = null + + /** Registered template name -> staged .cgt file, for cleanup on deactivate. */ + private val registered = mutableMapOf() + + override fun initialize(context: PluginContext): Boolean { + return try { + this.context = context + templateService = context.services.get(IdeTemplateService::class.java) ?: return false + fileService = context.services.get(IdeFileService::class.java) ?: return false + context.logger.info("FlutterTemplate initialized successfully") + true + } catch (e: Exception) { + context.logger.error("FlutterTemplate initialization failed", e) + false + } + } + + override fun activate(): Boolean { + for (variant in VARIANTS) { + registerTemplate(variant)?.let { registered[variant.name] = it } + } + context.logger.info("FlutterTemplate: registered ${registered.size}/${VARIANTS.size} templates") + return true + } + + override fun deactivate(): Boolean { + val ts = templateService + for ((name, file) in registered) { + runCatching { + val unregistered = ts?.unregisterTemplate(name) ?: false + context.logger.info("$name unregistered: $unregistered") + if (file.exists()) { + val deleted = fileService?.delete(file) ?: false + context.logger.info("$name staged file removed: $deleted") + } + }.onFailure { context.logger.error("FlutterTemplate: cleanup failed for $name", it) } + } + registered.clear() + return true + } + + override fun dispose() { + templateService = null + fileService = null + registered.clear() + context.logger.info("FlutterTemplate: Disposing plugin") + } + + private fun registerTemplate(variant: Variant): File? { + val assetRoot = "templates/${variant.assetDir}" + return runCatching { + var builder = templateService!!.createTemplateBuilder(variant.name) + .description(variant.description) + .showPackageNameOption() + .thumbnailFromAssets("$assetRoot/template/thumb.png", context) + + for (assetFile in listBundledAssetFiles(assetRoot)) { + if (assetFile.startsWith("template/")) continue + builder = builder.addStaticFromAssets(assetFile, "$assetRoot/$assetFile", context) + } + + val cgtFile = builder.build(context.resources.getPluginDirectory()) + templateService!!.registerTemplate(cgtFile) + Log.i(TAG, "${variant.name} template registered") + cgtFile + }.onFailure { + Log.e(TAG, "Failed to register ${variant.name} template", it) + }.getOrNull() + } + + private fun listBundledAssetFiles(rootAssetPath: String): List { + val results = mutableListOf() + walkAssets(context.androidContext.assets, rootAssetPath, "", results) + return results + } + + private fun walkAssets(assets: AssetManager, rootPath: String, relativePath: String, out: MutableList) { + val absolute = if (relativePath.isEmpty()) rootPath else "$rootPath/$relativePath" + val children = assets.list(absolute) ?: return + if (children.isEmpty()) { + out.add(relativePath) + return + } + for (child in children) { + val nextRelative = if (relativePath.isEmpty()) child else "$relativePath/$child" + walkAssets(assets, rootPath, nextRelative, out) + } + } + + private data class Variant(val name: String, val assetDir: String, val description: String) + + private companion object { + const val TAG = "FlutterTemplate" + + val VARIANTS = listOf( + Variant("Flutter Basic", "FlutterBasic", + "A minimal Flutter starter app with a counter screen"), + Variant("Flutter BLoC", "FlutterBloc", + "A Flutter app wired with the BLoC state-management pattern"), + Variant("Flutter Provider", "FlutterProvider", + "A Flutter app wired with the Provider state-management pattern"), + Variant("Flutter GetX", "FlutterGetx", + "A Flutter app wired with the GetX state-management pattern"), + Variant("Flutter Riverpod", "FlutterRiverpod", + "A Flutter app wired with the Riverpod state-management pattern"), + ) + } +} diff --git a/flutter-template/src/main/res/drawable/ic_plugin.xml b/flutter-template/src/main/res/drawable/ic_plugin.xml new file mode 100644 index 00000000..70f6741f --- /dev/null +++ b/flutter-template/src/main/res/drawable/ic_plugin.xml @@ -0,0 +1,10 @@ + + + diff --git a/flutter-template/src/main/res/values-night/colors.xml b/flutter-template/src/main/res/values-night/colors.xml new file mode 100644 index 00000000..35516a83 --- /dev/null +++ b/flutter-template/src/main/res/values-night/colors.xml @@ -0,0 +1,20 @@ + + + #88D6B8 + #00382A + #00513D + #A4F2D3 + #B3CCBF + #1F352C + #191C1A + #E1E3DF + #BFC9C1 + #89938C + #404943 + #E1E3DF + #2E3230 + #88D6B8 + #0D2E22 + #FFB4AB + #690005 + diff --git a/flutter-template/src/main/res/values/colors.xml b/flutter-template/src/main/res/values/colors.xml new file mode 100644 index 00000000..d6b2fcf1 --- /dev/null +++ b/flutter-template/src/main/res/values/colors.xml @@ -0,0 +1,20 @@ + + + #1A6B52 + #FFFFFF + #A4F2D3 + #002117 + #4C6359 + #FFFFFF + #FBFDF9 + #191C1A + #404943 + #707973 + #BFC9C1 + #191C1A + #E1E7E2 + #1A6B52 + #D4F5E6 + #BA1A1A + #FFDAD6 + diff --git a/flutter-template/src/main/res/values/strings.xml b/flutter-template/src/main/res/values/strings.xml new file mode 100644 index 00000000..27bb8b8a --- /dev/null +++ b/flutter-template/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Flutter Template + Installs Flutter starter project templates + diff --git a/flutter-template/src/main/res/values/styles.xml b/flutter-template/src/main/res/values/styles.xml new file mode 100644 index 00000000..f38efd34 --- /dev/null +++ b/flutter-template/src/main/res/values/styles.xml @@ -0,0 +1,16 @@ + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..a4b76b95 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e2935348 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..f5feea6d --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100755 index 00000000..db3a6ac2 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@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=. +@rem This is normally unused +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% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega