A free, open-source Magic: The Gathering life counter, commander damage tracker, and dice roller built for the Meta Portal Go 10″ touchscreen.
Features · Screenshots · Installation · Building from Source · Supported Devices · Tech Stack · Contributing · Known Limitations · License
Portal Counters turns a discontinued Meta Portal Go into a dedicated tabletop companion for Magic: The Gathering games. The large 10.1″ landscape display shows all players simultaneously — no more passing a phone around the table.
- Standard — 2–4 players, 20 / 25 / 30 starting life, or any custom value
- Commander / EDH — 2–4 players, 30 / 40 / 50 starting life with per-player commander damage tracking
- Life totals with ±1 and ±5 buttons, animated floating damage/heal numbers
- Poison counters (☠) — game ends at 10 poison
- Energy counters (⚡) — track energy generators
- Commander damage — per-opponent damage breakdown (Commander mode); a player dies when they take 21+ damage from a single commander
- Full undo history — every action can be reversed, including lethal actions
- Counter clamping — poison, energy, and commander damage cannot go below 0
- Built-in D6 and D20 dice — no more reaching for physical dice
- Mute/unmute sound effects with a toggle button in the control bar
- Preference persists between sessions
- Player leaderboard — wins, losses, win rate percentage, current streak
- Recent form tracker — W/L pattern for the last 5 games per player
- Head-to-head stats — who beats whom, how often
- Game history — saved when you leave a completed game (not auto-saved on lethal)
- Streak badges — 🔥 fire emoji for hot streaks (3+ consecutive wins)
- Last 100 games stored locally on device
- Name players from a saved roster (dropdown + "Add New")
- Assign MTG color identity — White, Blue, Black, Red, Green, Colorless, or Multi
- Player setups remembered between games
- Duplicate name validation prevents stats confusion
- Screen shake on damage
- Green/red glow pulse on life changes
- Floating +N / -N text particles that drift and fade
- Scale bounce on life total updates
- 10 synthesized sound effects — 5 damage sounds (hit, crunch, sting, doom, dark) and 5 heal sounds (sparkle, shimmer, chime, ascending, glow), randomly selected per event
- Landscape-first layout — 2, 3, or 4 player zones arranged for the 10.1″ screen
- Opposite players are rendered upside-down so everyone can read their own zone
- 64dp top reservation for Portal system overlay
- Minimum 40dp touch targets on counter buttons — comfortable for tabletop use
- Dark theme with atmospheric colors — no pure black or white
- Inter font at 18sp body / 140sp life total
- Keep-screen-on flag — display never sleeps during a game
- Elapsed time displayed in the control bar
- Duration saved to game history for stats
| Game Setup & Stats Dashboard | 2-Player Mid-Game |
![]() |
![]() |
| Dice Roller (D6/D20) | 4-Player Commander Game |
![]() |
![]() |
Download the latest APK from the Releases page.
# Enable ADB on your Portal Go:
# Settings > Device Info > tap "Build" 7 times > Developer Options > enable ADB
# Connect via USB-C (port under the rubber cover on Portal Go)
adb install portal-counters.apk
adb shell am start -n com.pgedeon.portalcounters/.Main- JDK 17 (OpenJDK recommended)
- Android SDK with platform
android-29and build tools34.0.0+ - A Meta Portal Go with ADB enabled, connected via USB-C
git clone https://github.com/pgedeon/portal-counters.git
cd portal-counters
./gradlew assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n com.pgedeon.portalcounters/.Main./gradlew test # Unit tests
./gradlew connectedAndroidTest # Instrumented tests (requires device)Release builds have minification and resource shrinking enabled. To build a signed release APK locally:
-
Create a keystore (if you don't have one):
keytool -genkey -v -keystore release.keystore -alias portal -keyalg RSA -keysize 2048 -validity 10000
-
Add signing config to
app/build.gradle.kts(do NOT commit secrets):android { signingConfigs { create("release") { storeFile = file("release.keystore") storePassword = System.getenv("KEYSTORE_PASSWORD") keyAlias = "portal" keyPassword = System.getenv("KEY_PASSWORD") } } buildTypes { release { signingConfig = signingConfigs.getByName("release") } } } -
Build:
export KEYSTORE_PASSWORD=yourpassword export KEY_PASSWORD=yourpassword ./gradlew assembleRelease
The signed APK will be at app/build/outputs/apk/release/app-release.apk.
Portal Counters targets the Meta Portal Go (10.1″ touchscreen, landscape, SDK 29) but works on any Android device running API 24+.
| Device | Screen | Connection | Status |
|---|---|---|---|
| Meta Portal Go | 10.1″ 1280×800 landscape | USB-C (under rubber cover) | ✅ Primary target |
| Meta Portal (1st/2nd gen) | 10″/15.6″ | USB-C | ✅ Compatible |
| Meta Portal+ | 15.6″ | USB-C | ✅ Compatible |
| Meta Portal Mini | 8″ | USB-C | ✅ Compatible |
| Meta Portal TV | TV (no touch) | USB-C | |
| Other Android tablets | Any | USB / wireless | ✅ Should work |
| Component | Technology |
|---|---|
| Language | Kotlin |
| UI Framework | Jetpack Compose with Material 3 |
| Architecture | Single-activity Compose app |
| State Management | Compose mutable state + sealed class actions with undo history |
| Persistence | SharedPreferences with JSON serialization |
| Audio | SoundPool for low-latency SFX |
| Build | Gradle (AGP), Kotlin Compose plugin |
| Min SDK | 24 (Android 7.0) |
| Target SDK | 29 (Android 10 — Portal hardware) |
| Font | Inter via XML bundled fonts |
| Animations | Compose Animatable, spring physics, keyframe sequences |
app/src/main/java/com/pgedeon/portalcounters/
├── MainActivity.kt # Entry point, navigation, deferred game saving
├── audio/
│ └── SoundManager.kt # SoundPool-based SFX with mute support
├── data/
│ └── GameStorage.kt # SharedPreferences: game history, player names, sound pref
├── model/
│ └── GameState.kt # Game state: players, actions, undo, death detection, counter clamping
└── ui/
├── ControlBar.kt # Bottom bar: timer, dice, undo, sound toggle, new game, menu
├── GameScreen.kt # Active game layout with SoundPool lifecycle management
├── GameSetupScreen.kt # Pre-game: config, validation, stats dashboard
├── PlayerZone.kt # Player zone: life, buttons, counters with 40dp touch targets
└── theme/
├── Color.kt # MTG color palette, dark theme tokens
├── Theme.kt # Material 3 theme
└── Type.kt # Inter font family + 140sp life total style
- No Planeswalker loyalty counters — not yet implemented.
- No custom counter types — energy is tracked but arbitrary counters are not supported.
- Portal TV D-pad support — the app requires touch input; D-pad/remote control navigation is not implemented.
- No data export — game history cannot be exported as CSV/JSON.
- Stats keyed by player name — changing a player's name creates a separate stats entry. Duplicate names in the same game are blocked at setup.
- SoundPool resource — SoundPool is created per game screen session. While it's released via
DisposableEffectwhen leaving the game, rapid screen switching could cause brief audio glitches. - No Material You — dynamic color is disabled to maintain consistent dark theme on Portal hardware.
- Planeswalker loyalty counter support
- Custom counter types (experience, +1/+1, etc.)
- Portal TV D-pad / remote control support
- Material You theming for non-Portal devices
- Game replay / turn-by-turn history
- Export stats as CSV/JSON
See open issues for the full list.
Contributions are welcome! This is a hobby project for repurposing discontinued hardware.
- Fork the repo
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See CONTRIBUTING.md for detailed guidelines and STYLE_GUIDE.md for Portal-specific UI requirements.
No — the app runs on any Android tablet with API 24+. The UI is optimized for the Portal Go's 10.1″ landscape screen, but it works fine on other devices.
No. Everything is stored locally on the device. No accounts, no cloud, no tracking.
The counter system (life, poison, energy, commander damage) is designed for Magic: The Gathering, but the life counter works for any game. Future versions may add custom counter types.
Meta Portal devices run Android 10 (SDK 29). Targeting this SDK ensures maximum compatibility with Portal hardware.
Check that the 🔊 sound toggle in the control bar is not muted. The app uses SoundPool which should work on all Android devices.
This project is licensed under the MIT License — see the LICENSE file for details.
Based on the Meta Portal Sample App template. Original Meta code is Copyright © Meta Platforms, Inc. and affiliates, licensed under MIT.
Special thanks to @facebook (Meta) for unlocking developer mode and ADB access when the Portal hardware line was discontinued. Instead of bricking the devices, Meta chose to let owners sideload any Android app — turning what would have been e-waste into fully programmable 10-inch Android tablets. That decision saved countless Portal Go units from the landfill. This app exists because that door was left open.
💜 Repurpose > landfill.
Made with ⚔ for the Magic: The Gathering community and the Meta Portal ecosystem.




