Skip to content

Add Android (Google Play) app + wire iOS TestFlight auto-upload#115

Open
Copilot wants to merge 2 commits intomainfrom
copilot/deploy-android-app-stores
Open

Add Android (Google Play) app + wire iOS TestFlight auto-upload#115
Copilot wants to merge 2 commits intomainfrom
copilot/deploy-android-app-stores

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 28, 2026

VOID RIFT has no Android presence and the iOS CI pipeline builds archives but never pushes them to TestFlight. This PR adds a full Android WebView wrapper project and automates delivery to both stores.

iOS — TestFlight upload

  • Removed broken cp game-3d-integration.js step (file deleted from main; was failing CI)
  • Added xcrun altool --upload-app step after IPA export; runs only when ASC_KEY_PATH is set (uses existing ASC_API_KEY_ID / ASC_API_ISSUER_ID secrets — no new secrets required)
  • Updated step summary to reflect upload status

Android project (android/)

New Kotlin WebView wrapper, API 26+ (Android 8.0), landscape-first, full-screen immersive:

  • MainActivity.kt: loads file:///android_asset/WebContent/index.html; uses WindowInsetsController on API 30+ with legacy systemUiVisibility fallback; modern OnBackPressedDispatcher (supports predictive back); hardware-accelerated WebView with JS + DOM storage; no allowFileAccessFromFileURLs / allowUniversalAccessFromFileURLs (unnecessary and risky)
  • app/build.gradle: signing wired to env vars (ANDROID_KEYSTORE_FILE, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD) — release build is unsigned if vars absent
  • Standard Gradle 8.6 project structure; WebContent/ assets directory populated at build time

Android CI (.github/workflows/android-build.yml)

  • Syncs web files into android/app/src/main/assets/WebContent/ as a separate job (artifact-based, mirrors iOS workflow pattern)
  • Always builds debug APK; always builds release AAB
  • Uploads AAB to Google Play Internal track when ANDROID_KEYSTORE_BASE64 + GOOGLE_PLAY_SERVICE_ACCOUNT_JSON secrets are both set and branch is main
- name: Upload to Google Play (if configured)
  if: |
    (github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch') &&
    env.ANDROID_KEYSTORE_BASE64 != '' &&
    env.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON != ''
  uses: r0adkll/upload-google-play@v1.1.3
  with:
    serviceAccountJsonPlainText: ${{ env.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
    packageName: com.voidrift.game
    releaseFiles: android/app/build/outputs/bundle/release/*.aab
    track: internal

Supporting files

  • sync-android-content.sh — mirrors sync-ios-content.sh; copies all web game files into Android assets
  • ANDROID_SETUP.md — keystore creation, Play Console first-submission checklist, CI secrets table
  • .gitignore — excludes android/.gradle/, android/app/build/, and *.jks

Required secrets to enable store delivery

Secret Store
ASC_API_KEY_ID, ASC_API_ISSUER_ID, ASC_API_KEY_P8_BASE64 iOS → TestFlight
IOS_CERTIFICATE_BASE64, P12_PASSWORD, KEYCHAIN_PASSWORD, IOS_PROVISIONING_PROFILE_BASE64, EXPORT_OPTIONS_PLIST_BASE64 iOS → IPA signing
ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD Android → AAB signing
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON Android → Play upload

Both pipelines are fully optional-secret-gated: CI does not fail when signing/upload secrets are absent.


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 28, 2026

Deployment failed with the following error:

Hobby accounts are limited to daily cron jobs. This cron expression (*/5 * * * *) would run more than once per day. Upgrade to the Pro plan to unlock all Cron Jobs features on Vercel.

Learn More: https://vercel.link/3Fpeeb1

- Fix ios-build.yml: remove broken 3D file copy, add TestFlight upload via xcrun altool
- Add Android Kotlin WebView project (android/) targeting API 26+, full-screen immersive
- Add sync-android-content.sh to mirror web files into Android assets
- Add android-build.yml: builds debug APK + release AAB, optional Google Play upload
- Add ANDROID_SETUP.md with complete setup and CI secrets guide
- Update .gitignore to exclude Android build artifacts"

Co-authored-by: astickleyid <201894828+astickleyid@users.noreply.github.com>
Copilot AI changed the title [WIP] Deploy to IOOS and Google Play Android app stores Add Android (Google Play) app + wire iOS TestFlight auto-upload Feb 28, 2026
@astickleyid astickleyid requested a review from Copilot March 7, 2026 14:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an Android WebView wrapper project for VOID RIFT and wires up automated TestFlight uploads for the existing iOS CI pipeline. The Android app mirrors the iOS approach of bundling the HTML/CSS/JS game in a native wrapper (Kotlin WebView instead of Swift WKWebView), with a complete CI/CD pipeline for building and publishing to Google Play.

Changes:

  • Added a new Kotlin Android WebView wrapper project (android/) with full-screen immersive mode, landscape orientation, and hardware-accelerated rendering
  • Added a GitHub Actions workflow for Android CI (.github/workflows/android-build.yml) that builds debug APK + release AAB and optionally uploads to Google Play Internal track
  • Updated the iOS CI workflow to add a TestFlight upload step using xcrun altool and removed the broken game-3d-integration.js copy step

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
android/app/src/main/java/com/voidrift/game/MainActivity.kt Kotlin activity with WebView setup, immersive UI, back button handling, and state restoration
android/app/src/main/AndroidManifest.xml Android manifest with INTERNET permission, landscape orientation, and full-screen theme
android/app/build.gradle App-level Gradle config with signing from env vars, Kotlin, and AndroidX dependencies
android/build.gradle Project-level Gradle plugin declarations (AGP 8.2.2, Kotlin 1.9.22)
android/settings.gradle Gradle settings with repository configuration
android/gradle.properties Gradle JVM args, AndroidX, and Jetifier flags
android/gradle/wrapper/gradle-wrapper.properties Gradle 8.6 wrapper distribution URL
android/app/src/main/res/layout/activity_main.xml Simple FrameLayout with full-screen WebView
android/app/src/main/res/values/themes.xml Full-screen NoActionBar theme with black background
android/app/src/main/res/values/strings.xml App name string resource
android/app/src/main/assets/WebContent/.gitkeep Placeholder for build-time-populated web assets
android/app/proguard-rules.pro ProGuard rules to keep JavascriptInterface methods
.github/workflows/android-build.yml Android CI: syncs web content, builds APK/AAB, uploads to Google Play
.github/workflows/ios-build.yml iOS CI: removed 3D integration copy, added TestFlight upload step, updated summary
sync-android-content.sh Shell script to sync web files to Android assets directory
ANDROID_SETUP.md Comprehensive setup guide for Android development and Play Store publishing
.gitignore Added Android build artifact exclusions (.gradle/, build/, *.jks, *.keystore)
package-lock.json Minor lockfile churn removing peer flags from several packages

-t ios \
--apiKey "$ASC_API_KEY_ID" \
--apiIssuer "$ASC_API_ISSUER_ID" \
--api-key-path "$ASC_KEY_PATH"
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xcrun altool --api-key-path expects a directory path containing a file named AuthKey_{KEY_ID}.p8, not a direct file path. Currently $ASC_KEY_PATH is set to $RUNNER_TEMP/AuthKey.p8 (a file path), and the file is named AuthKey.p8 instead of AuthKey_${ASC_API_KEY_ID}.p8.

To fix this, the "Prepare App Store Connect API Key" step should rename the file to AuthKey_${ASC_API_KEY_ID}.p8, and this step should pass the directory ($RUNNER_TEMP) instead of the file path to --api-key-path.

Suggested change
--api-key-path "$ASC_KEY_PATH"
--api-key-path "$RUNNER_TEMP"

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +100
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
hideSystemUI()
}
}
}
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MainActivity does not override onPause() / onResume() / onDestroy() to call webView.onPause(), webView.onResume(), and webView.destroy(). Without these, the WebView's JavaScript timers and rendering continue running when the app is backgrounded, draining battery and wasting CPU — which is especially impactful for a game running a 60 FPS game loop. Add lifecycle overrides to pause/resume the WebView appropriately.

Copilot uses AI. Check for mistakes.
env.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON != ''
uses: r0adkll/upload-google-play@v1.1.3
with:
serviceAccountJsonPlainText: ${{ env.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using ${{ env.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} instead of ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} for serviceAccountJsonPlainText risks the secret not being masked in workflow logs. GitHub Actions automatically masks ${{ secrets.* }} values in logs, but ${{ env.* }} values are only masked if they were directly set from a secret expression. While this should be masked because the env variable is set from ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} at the job level, using the secret directly here is safer and more explicit.

Suggested change
serviceAccountJsonPlainText: ${{ env.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +45
cp -v "$ROOT_DIR/auth-system.js" "$ANDROID_WEB_DIR/auth-system.js"
cp -v "$ROOT_DIR/leaderboard-system.js" "$ANDROID_WEB_DIR/leaderboard-system.js"
cp -v "$ROOT_DIR/social-ui.js" "$ANDROID_WEB_DIR/social-ui.js"
cp -v "$ROOT_DIR/social-ui.css" "$ANDROID_WEB_DIR/social-ui.css"
echo ""

# API and supporting files
echo "🔌 Syncing API and supporting files..."
cp -v "$ROOT_DIR/backend-api.js" "$ANDROID_WEB_DIR/backend-api.js"
cp -v "$ROOT_DIR/audio-manager.js" "$ANDROID_WEB_DIR/audio-manager.js"
cp -v "$ROOT_DIR/game-utils.js" "$ANDROID_WEB_DIR/game-utils.js"
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root auth-system.js, leaderboard-system.js, backend-api.js, and social-api.js detect only localhost for the API base URL override, and default to a relative path like /api or /api/leaderboard. On Android WebView, the page loads from file:///android_asset/WebContent/index.html, so window.location.hostname is empty and protocol is file:. A relative API URL like /api/leaderboard will resolve to the unusable file:///api/leaderboard.

The iOS WebContent versions of these files were manually patched with file:// protocol handling, but this sync script copies from the un-patched root files. The fix should be in the root source files (adding file:// / empty hostname detection as the iOS WebContent versions do), so both iOS and Android sync scripts produce working builds.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJetifier=true
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

android.enableJetifier=true is unnecessary since all dependencies are already AndroidX. Jetifier adds build overhead by scanning dependencies for Support Library usage and converting them to AndroidX. Since this is a new project with only AndroidX dependencies (androidx.core, androidx.appcompat, androidx.activity, androidx.webkit), you can safely remove this flag.

Suggested change
android.enableJetifier=true

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +65
// Stay within the asset:// scope; open external links in the browser
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean {
val url = request.url.toString()
// Only allow local file URLs
return !url.startsWith("file://")
}
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "open external links in the browser" but returning true from shouldOverrideUrlLoading just cancels the navigation — it doesn't open the URL in an external browser. If you intend to open non-file URLs in the system browser, you need to launch an Intent(Intent.ACTION_VIEW, request.url) via startActivity(). If the intent is to simply block external navigation (which is reasonable for a game), update the comment to reflect that.

Copilot uses AI. Check for mistakes.

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manifest references @mipmap/ic_launcher for the app icon, but there are no mipmap resource directories in android/app/src/main/res/. The build will fail with a resource linking error because the launcher icon drawable cannot be found. You need to add mipmap-* directories with ic_launcher.png (or an adaptive icon with ic_launcher.xml) for the required densities (at minimum mipmap-hdpi, mipmap-mdpi, mipmap-xhdpi, mipmap-xxhdpi, mipmap-xxxhdpi), or generate them via Android Studio's Image Asset wizard.

Suggested change
android:icon="@mipmap/ic_launcher"

Copilot uses AI. Check for mistakes.
@astickleyid astickleyid marked this pull request as ready for review March 8, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants