Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
97 changes: 97 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Tag-driven Android release pipeline.
#
# Required repository secrets:
# EXPO_TOKEN Personal access token from expo.dev (User Settings → Access
# Tokens). Used by expo/expo-github-action to authenticate
# `eas build` non-interactively.
# The default GITHUB_TOKEN (provided by Actions) is used by `gh release`
# to create the release and upload the APK.
#
# Optional repository variables / secrets:
# ANDROID_SIGNING_FINGERPRINT SHA-256 fingerprint of the production signing
# key (retrieve with `eas credentials --platform android`).
# If unset, a placeholder is written into the release notes and
# the maintainer must edit the release before publishing.
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write

jobs:
release:
name: Build and publish Android APK
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Typecheck
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Test
run: npm test -- --ci

- name: Setup Expo and EAS
uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}

- name: Build Android APK on EAS
id: build
run: |
eas build \
--platform android \
--profile production \
--non-interactive \
--json \
--wait > build.json
BUILD_ID=$(jq -r '.[0].id' build.json)
ARTIFACT_URL=$(jq -r '.[0].artifacts.buildUrl' build.json)
echo "build-id=${BUILD_ID}" >> "$GITHUB_OUTPUT"
echo "artifact-url=${ARTIFACT_URL}" >> "$GITHUB_OUTPUT"

- name: Download APK
run: |
TAG="${GITHUB_REF_NAME}"
mkdir -p dist
curl -fL -o "dist/tocarta-${TAG}.apk" \
"${{ steps.build.outputs.artifact-url }}"
echo "APK_PATH=dist/tocarta-${TAG}.apk" >> "$GITHUB_ENV"

- name: Generate release notes
run: |
TAG="${GITHUB_REF_NAME}"
FINGERPRINT="${{ secrets.ANDROID_SIGNING_FINGERPRINT }}"
if [ -n "$FINGERPRINT" ]; then
scripts/release-notes.sh "$TAG" "$APK_PATH" "$FINGERPRINT" > release-notes.md
else
scripts/release-notes.sh "$TAG" "$APK_PATH" > release-notes.md
fi

- name: Create GitHub Release and upload APK
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${GITHUB_REF_NAME}"
gh release create "$TAG" \
--title "Tocarta $TAG" \
--notes-file release-notes.md \
"$APK_PATH"
86 changes: 57 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,78 @@
# Welcome to your Expo app 👋
# Tocarta

This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
A self-hosted, open-source music year-guessing card game built on your own Deezer playlists.

## Get started
## Status

1. Install dependencies
V1 in development. Android-only sideload. Not on the Play Store.

```bash
npm install
```
## Demo

2. Start the app
<!-- TODO: add screenshots -->

```bash
npx expo start
```
## How it works

In the output, you'll find options to open the app in a
Paste a public Deezer playlist URL. The app generates a printable PDF of QR cards for that playlist. During play, the player whose turn it is scans a card with the device camera, the app plays a 30-second preview of the track without showing the title, artist, year, or cover art on any screen, and players guess the year. The physical card is then flipped to reveal the answer. Scoring stays at the table; the app participates only in the audio layer.

- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
## Install (sideload)

You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
1. On your Android device, open **Settings → Apps → Special app access → Install unknown apps** and enable installation for the browser or file manager you will use to download the APK.
2. Open the [latest release](https://github.com/damarals/tocarta/releases/latest) on your device and download the APK.
3. (Optional but recommended) Verify the SHA-256 and the signing key fingerprint as described under [Verify](#verify).
4. Tap the downloaded APK to install. Confirm the install when prompted.
5. On first launch, grant camera permission. The camera is used only to scan cards.

## Get a fresh project
**Compatibility:** Android 12+ (API level 31+).

When you're ready, run:
## Verify

Each release publishes a SHA-256 of the APK and the SHA-256 fingerprint of the key it was signed with. Both values are listed in the release notes on the [Releases page](https://github.com/damarals/tocarta/releases).

**SHA-256 of the APK** — confirms the file you downloaded matches what the maintainer published. On your computer:

```bash
npm run reset-project
shasum -a 256 tocarta-vX.Y.Z.apk
```

This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
The output must match the value in the release notes. If it does not, do not install the APK; download it again from the Releases page.

**Signing key fingerprint** — confirms the APK was signed by the maintainer's release key. The fingerprint stays the same across every Tocarta release; if you have installed Tocarta before, the fingerprint in the release notes should match the one on the previous release. A mismatch means the APK is not from the maintainer and you should not install it.

(For reference: the maintainer obtains the fingerprint once via `eas credentials --platform android` after EAS generates or imports the keystore, and pastes it into each release's notes.)

## Build from source

For local development:

```bash
git clone https://github.com/damarals/tocarta.git
cd tocarta
npm install
npx expo run:android
```

For a production-signed APK (requires an EAS account and the project's signing key):

```bash
eas build --platform android --profile production
```

See [`eas.json`](./eas.json) for the build profiles.

## Privacy

No account, no login, no telemetry. Your playlist URLs and decks live only on the device. Deezer and MusicBrainz are contacted directly from the app for track and year resolution; no third party sees that traffic.

Decks are stored in local app storage. Uninstalling the app deletes them. Export the PDF if you want to keep a deck across reinstalls.

## Learn more
## License

To learn more about developing your project with Expo, look at the following resources:
MIT. See [LICENSE](./LICENSE).

- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
## Acknowledgements

## Join the community
Inspired by the commercial card game Hitster. Tocarta is independent and is not affiliated with or endorsed by Hitster or its publisher.

Join our community of developers creating universal apps.
## Disclaimer

- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
_Unofficial, independent, fan-made tool for personal non-commercial use. Not affiliated with, endorsed by, or connected to Hitster, Koninklijke Jumbo, Spotify, Deezer, or any related entities. All trademarks belong to their respective owners._
27 changes: 27 additions & 0 deletions eas.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"cli": {
"version": ">= 7.0.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"android": {
"buildType": "apk"
},
"autoIncrement": "version"
}
},
"submit": {
"production": {}
}
}
60 changes: 60 additions & 0 deletions scripts/release-notes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Generate the GitHub Release notes body for a Tocarta tagged release.
#
# Usage:
# scripts/release-notes.sh <tag> <apk-path> [fingerprint]
#
# Arguments:
# tag The release tag (e.g. v0.1.0).
# apk-path Path to the built APK.
# fingerprint Optional SHA-256 signing key fingerprint. If omitted, a
# placeholder is written and the maintainer must edit the
# release before publishing. Retrieve with:
# eas credentials --platform android
#
# Output: prints the release-notes Markdown to stdout.
set -euo pipefail

if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
echo "usage: $0 <tag> <apk-path> [fingerprint]" >&2
exit 1
fi

TAG="$1"
APK_PATH="$2"
FINGERPRINT="${3:-<paste from \`eas credentials --platform android\`>}"

if [ ! -f "$APK_PATH" ]; then
echo "error: APK not found at $APK_PATH" >&2
exit 1
fi

APK_NAME="$(basename "$APK_PATH")"
SHA256="$(shasum -a 256 "$APK_PATH" | awk '{print $1}')"

cat <<EOF
## Tocarta ${TAG}

Android APK for sideload. Tocarta is an unofficial, independent, fan-made music year-guess card game.

### Verify the download

- SHA-256: \`${SHA256}\`
- Signing key fingerprint: \`${FINGERPRINT}\`

On your computer:

\`\`\`bash
shasum -a 256 ${APK_NAME}
\`\`\`

The output must match the SHA-256 above.

### Install on Android

See [Sideloading instructions](https://github.com/damarals/tocarta#install-sideload) in the README.

---

_Unofficial, independent, fan-made tool for personal non-commercial use. Not affiliated with, endorsed by, or connected to Hitster, Koninklijke Jumbo, Spotify, Deezer, or any related entities. All trademarks belong to their respective owners._
EOF
Loading