Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ See the official [plugin documentation](https://www.appdevforall.org/codeonthego
| [`markdown-preview/`](markdown-preview/) | Renders Markdown files with a live preview pane in the editor. |
| [`keystore-generator/`](keystore-generator/) | Generates signing keystores from inside the IDE. |
| [`snippets/`](snippets/) | Adds user-managed code snippets with prefix-triggered expansions. |
| [`random-xkcd/`](random-xkcd/) | Random xkcd comic in the editor bottom sheet; canonical small-plugin walkthrough with in-IDE help. |

## Building a plugin

Expand Down
29 changes: 29 additions & 0 deletions random-xkcd/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Gradle
.gradle/
build/
gradle-app.setting
!gradle-wrapper.jar
.gradletasknamecache

# IDE
.idea/
*.iml
*.ipr
*.iws
.project
.classpath
.settings/
.kotlin/

# Local configuration
local.properties

# OS
.DS_Store
Thumbs.db

# Logs
*.log

# Test outputs
test-results/
92 changes: 92 additions & 0 deletions random-xkcd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# random-xkcd

A small Code on the Go plugin that shows a random xkcd comic in the
editor bottom sheet. Three buttons above the comic — Random / Copy
URL / Copy image — mirror the xkcd.com control bar. Tap the image
to copy URL, double-tap to copy image. Long-press the tab for
in-IDE help.

Designed as a canonical "this is what a small CoGo plugin looks like"
example. Under 300 lines of Kotlin, every plugin-specific concept
called out where it shows up in the code.

## The tutorial

The full walkthrough lives in `src/main/assets/docs/index.html` —
the **Tier 3 docs page** served by the host IDE at
`http://localhost:6174/plugin/org.appdevforall.randomxkcd/index.html`
once the plugin is installed.

To read it:

- **Inside CoGo** (the canonical path) — long-press the **XKCD** tab in
the editor bottom sheet → tap **"See More"** → tap **"Code
walkthrough"**. The IDE opens the page in an in-IDE WebView.
- **Outside CoGo** — open `src/main/assets/docs/index.html` directly
in any browser. Renders identically.

The tutorial covers the plugin in 7 steps:

1. Plugin entry point (`IPlugin` lifecycle)
2. Manifest + permissions
3. Bottom-sheet tab UI (`UIExtension`)
4. UI interactions: buttons + gestures (`GestureDetector.SimpleOnGestureListener`)
5. Network fetch over HTTPS
6. Clipboard support (text + image via host `FileProvider`)
7. Three-tier tooltip help (`DocumentationExtension`)

## Build

```bash
./gradlew assemblePlugin
```

Produces `build/plugin/random-xkcd.cgp` — the bundle you sideload
into Code on the Go via **Preferences → Plugin Manager → +**.

## Source layout

```
random-xkcd/
├── build.gradle.kts
└── src/main/
├── AndroidManifest.xml
├── assets/
│ ├── docs/ ← Tier 3 walkthrough (the tutorial)
│ ├── icon_day.png ← Plugin Manager icon, light theme
│ └── icon_night.png ← Plugin Manager icon, dark theme
├── kotlin/org/appdevforall/randomxkcd/
│ ├── XkcdRandomPlugin.kt ← lifecycle + tab + tooltip registration
│ ├── fragments/XkcdPanelFragment.kt ← button wiring + GestureDetector
│ ├── net/XkcdApiClient.kt ← HTTP, two endpoints, no auth
│ └── net/XkcdComic.kt
└── res/
├── layout/fragment_xkcd_panel.xml
└── values/, values-night/
```

No custom test surface — every piece is small enough that JVM unit
tests would just re-test Android framework behavior. UX is covered
by mobile-MCP / Android QA on real devices.

## Run tests

```bash
./gradlew testDebugUnitTest
```

## xkcd attribution + license

xkcd comics are © Randall Munroe and licensed **CC BY-NC 2.5**
(https://xkcd.com/license.html). This plugin:

- Fetches comics over HTTPS from xkcd.com (no caching, no redistribution
beyond what the user explicitly copies to their own clipboard).
- Displays an attribution line — *"Comics © Randall Munroe · xkcd.com ·
CC BY-NC 2.5"* — beneath every comic in the bottom-sheet panel.
- Is itself non-commercial (open-source demo plugin for an
open-source IDE), consistent with the NC term.

The plugin's own source code is licensed per the surrounding
`plugin-examples` repository (see `LICENSE` at the repo root). xkcd's
license applies only to the comic content the plugin displays.
93 changes: 93 additions & 0 deletions random-xkcd/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.itsaky.androidide.plugins.build")
}

pluginBuilder {
pluginName = "random-xkcd"
}

android {
namespace = "org.appdevforall.randomxkcd"
compileSdk = 34

defaultConfig {
applicationId = "org.appdevforall.randomxkcd"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0.0"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}

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"
)
}
}

testOptions {
unitTests.isReturnDefaultValues = true
}
}

kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}

dependencies {
// The plugin-api jar is the canonical contract for plugins. Available
// at compile time; the IDE provides it at runtime.
compileOnly(files("../libs/plugin-api.jar"))

implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.10.0")
implementation("androidx.fragment:fragment-ktx:1.8.8")
implementation("androidx.core:core-ktx:1.13.1")
implementation("org.jetbrains.kotlin:kotlin-stdlib:2.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")

// OkHttp for the xkcd JSON endpoint + image fetch.
// Kept tiny and dependency-free — no Glide/Retrofit, since this plugin is a
// teaching example and we want the network layer to read top-to-bottom.
implementation("com.squareup.okhttp3:okhttp:4.12.0")

testImplementation("junit:junit:4.13.2")
}

tasks.wrapper {
gradleVersion = "8.14.3"
distributionType = Wrapper.DistributionType.BIN
}

// Disable AAR metadata checks that fail under the plugin-builder
// pipeline (Beepy + Forms use the same workaround).
tasks.matching {
it.name.contains("checkDebugAarMetadata") ||
it.name.contains("checkReleaseAarMetadata")
}.configureEach {
enabled = false
}
3 changes: 3 additions & 0 deletions random-xkcd/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
Binary file added random-xkcd/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
7 changes: 7 additions & 0 deletions random-xkcd/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading