History review requested by the owner: when and why did the Android Auto UI stop working. ANSWER: 31 July, commit2540556, "give the equalizer actions distinct, state-aware icons".9eff760(31-07) androidIcon: 'drawable/ic_stat_pluriwave' -> in the APK2540556(31-07) androidIcon: 'drawable/ic_auto_eq_on' -> NEVER in it That commit swapped a drawable that shipped for two that the stale CI resource cache never included. From that moment getResourceId returned 0, PlaybackStateCompat.CustomAction.Builder threw, and the throw aborted AudioService.setState before the session was published -- so every Android Auto symptom chased since is one line of that commit. The bitter part is that2540556was itself a fix for a report about two identical icons. Three changes. 1. CI guard. The build now unzips the release APK and fails if a drawable resolved by NAME at runtime is missing. Resolution by name cannot fail at compile time -- it fails in the car, silently, with id 0. This class of bug shipped undetected for a week; it cannot ship again. 2. Skipping stations now walks the favourites GROUP first, as requested: group -> all favourites -> my stations -> catalogue. Two deliberate exclusions, both tested: `sinAsignarId` is the ABSENCE of a group, not a group, so those walk all favourites; and a one-member group falls through too, or both buttons would be dead ends. The group is read from the FAVOURITE record, never from the playing station -- that one is rebuilt by emisoraDesdeMediaItem, which carries no group id and would always report "unfiled". 3. Diagnostics on the station skip. It was reported as doing nothing for radio, and every early return in that method is silent: an empty list and a single-entry list look identical from outside. The log now names which one fired, so the next capture answers it instead of another hypothesis. Tests: 1161 -> 1165.
234 lines
10 KiB
YAML
234 lines
10 KiB
YAML
name: Build & Deploy PluriWave
|
|
|
|
on:
|
|
push:
|
|
branches: [main, PRO]
|
|
|
|
env:
|
|
PATH: /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
|
ANDROID_HOME: /Users/freetlab/Library/Android/sdk
|
|
KEYSTORE_PATH: /Users/freetlab/.openclaw/workspace/.secure/pluriwave/pluriwave-upload.jks
|
|
KEYSTORE_ALIAS: pluriwave-upload
|
|
PLAY_PACKAGE_NAME: es.freetimelab.pluriwave
|
|
CURRENT_REF: ${{ gitea.ref }}
|
|
|
|
jobs:
|
|
analizar:
|
|
name: Análisis de código
|
|
runs-on: [self-hosted, macos, arm64, flutter]
|
|
steps:
|
|
- name: Clonar rama actual
|
|
run: |
|
|
BRANCH="${CURRENT_REF#refs/heads/}"
|
|
git clone https://ShanaiaBot:${{ secrets.GITEA_TOKEN }}@git.freetimelab.es/FreeTLab/pluriwave.git .
|
|
git fetch origin "$BRANCH"
|
|
git checkout "$BRANCH"
|
|
|
|
- name: Obtener dependencias
|
|
run: flutter pub get
|
|
|
|
- name: Verificar integridad de literales i18n
|
|
run: python3 tool/check_arb_placeholder_corruption.py
|
|
|
|
- name: Analizar código
|
|
run: flutter analyze --no-fatal-infos --no-fatal-warnings
|
|
|
|
- name: Ejecutar tests criticos
|
|
timeout-minutes: 15
|
|
run: |
|
|
flutter test test/servicios/servicio_programacion_alarmas_test.dart test/estado/estado_alarmas_test.dart --concurrency=1 --timeout=60s
|
|
|
|
- name: Limpiar procesos Flutter de tests
|
|
if: always()
|
|
run: pkill -f 'flutter_tester|flutter_tools.snapshot|dartaotruntime' 2>/dev/null || true
|
|
|
|
build:
|
|
name: Build APK + AAB release
|
|
runs-on: [self-hosted, macos, arm64, flutter]
|
|
needs: analizar
|
|
steps:
|
|
- name: Clonar rama actual
|
|
run: |
|
|
BRANCH="${CURRENT_REF#refs/heads/}"
|
|
git clone https://ShanaiaBot:${{ secrets.GITEA_TOKEN }}@git.freetimelab.es/FreeTLab/pluriwave.git .
|
|
git fetch origin "$BRANCH"
|
|
git checkout "$BRANCH"
|
|
|
|
- name: Configurar keystore de firma
|
|
env:
|
|
KEYSTORE_PASSWORD: ${{ secrets.PLURIWAVE_KEYSTORE_PASSWORD }}
|
|
run: |
|
|
if [ ! -f "$KEYSTORE_PATH" ]; then
|
|
echo "ERROR: Keystore no encontrado en $KEYSTORE_PATH"
|
|
exit 1
|
|
fi
|
|
echo "storeFile=$KEYSTORE_PATH" > android/key.properties
|
|
echo "storePassword=$KEYSTORE_PASSWORD" >> android/key.properties
|
|
echo "keyAlias=$KEYSTORE_ALIAS" >> android/key.properties
|
|
echo "keyPassword=$KEYSTORE_PASSWORD" >> android/key.properties
|
|
echo "✅ Keystore configurado"
|
|
|
|
- name: Bump versión patch + commit
|
|
run: |
|
|
BRANCH="${CURRENT_REF#refs/heads/}"
|
|
git config user.name "ShanaiaBot"
|
|
git config user.email "shanaia@freetimelab.es"
|
|
CURRENT=$(grep '^version:' pubspec.yaml | awk '{print $2}')
|
|
SEMVER=$(echo "$CURRENT" | cut -d'+' -f1)
|
|
BUILD=$(echo "$CURRENT" | cut -d'+' -f2)
|
|
NEW_BUILD=$((BUILD + 1))
|
|
# If the triggering commit explicitly pins the version name via the
|
|
# [version set] marker, ship that semver as-is (a milestone like 1.0.0
|
|
# or a major/minor jump the automatic patch bump cannot reach) and only
|
|
# advance the build number, which Google Play requires to stay
|
|
# monotonic. Otherwise keep the default automatic patch+build bump.
|
|
if git log -1 --pretty=%B | grep -q '\[version set\]'; then
|
|
NEW_VERSION="${SEMVER}+${NEW_BUILD}"
|
|
else
|
|
MAJOR=$(echo "$SEMVER" | cut -d. -f1)
|
|
MINOR=$(echo "$SEMVER" | cut -d. -f2)
|
|
PATCH=$(echo "$SEMVER" | cut -d. -f3)
|
|
NEW_PATCH=$((PATCH + 1))
|
|
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}+${NEW_BUILD}"
|
|
fi
|
|
sed -i '' "s/^version: .*/version: ${NEW_VERSION}/" pubspec.yaml
|
|
git add pubspec.yaml
|
|
git commit -m "chore: bump version to ${NEW_VERSION} [ci skip]"
|
|
git push origin "HEAD:${BRANCH}"
|
|
|
|
- name: Extraer versión
|
|
id: version
|
|
run: |
|
|
VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}' | cut -d'+' -f1)
|
|
BUILD_NUMBER=$(grep '^version:' pubspec.yaml | awk '{print $2}' | cut -d'+' -f2)
|
|
COMMIT=$(git rev-parse --short HEAD)
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "build_number=$BUILD_NUMBER" >> "$GITHUB_OUTPUT"
|
|
echo "commit=$COMMIT" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Obtener dependencias
|
|
run: flutter pub get
|
|
|
|
# OBLIGATORIO en este runner autoalojado, no es higiene opcional.
|
|
#
|
|
# El directorio build/ sobrevive entre ejecuciones y el merge
|
|
# incremental de recursos de Gradle se queda rancio: los drawables
|
|
# ic_auto_eq_on/ic_auto_eq_off (anadidos el 31-07 en 2540556) NUNCA
|
|
# llegaron a entrar en el APK, mientras que ic_stat_pluriwave -- misma
|
|
# carpeta, anadido el 02-07 -- si estaba. Verificado extrayendo el
|
|
# base.apk instalado en el dispositivo: los ficheros no existen ni como
|
|
# entrada del zip ni en resources.arsc.
|
|
#
|
|
# El coste fue semanas de diagnostico equivocado. Cada setState
|
|
# publicaba una CustomAction cuyo icono resolvia a 0, y
|
|
# PlaybackStateCompat.CustomAction.Builder lanza en ese caso, abortando
|
|
# setState antes de activar la sesion de medios: Android Auto se
|
|
# quedaba con la sesion congelada e inactiva. El codigo Dart siempre
|
|
# llegaba porque se recompila; el recurso Android no.
|
|
- name: Limpiar artefactos de compilacion
|
|
run: flutter clean
|
|
|
|
- name: Reinstalar dependencias tras limpiar
|
|
run: flutter pub get
|
|
|
|
- name: Build APK release
|
|
run: flutter build apk --release
|
|
|
|
# Guardian de recursos: el APK debe contener los drawables que el codigo
|
|
# resuelve POR NOMBRE en runtime (getResources().getIdentifier).
|
|
#
|
|
# Un nombre que no resuelve devuelve id 0, y eso no falla la
|
|
# compilacion: falla en el coche. Concretamente
|
|
# PlaybackStateCompat.CustomAction.Builder lanza con icono 0, ese throw
|
|
# aborta AudioService.setState antes de activar la sesion de medios, y
|
|
# Android Auto se queda con la interfaz congelada. Paso exactamente eso
|
|
# entre el 31-07 (commit 2540556) y el 07-08 sin que nada lo detectara.
|
|
#
|
|
# Anadir un drawable nuevo referenciado por nombre => anadirlo aqui.
|
|
- name: Verificar recursos criticos en el APK
|
|
run: |
|
|
APK=build/app/outputs/flutter-apk/app-release.apk
|
|
FALTAN=0
|
|
for RECURSO in ic_auto_eq_on ic_auto_eq_off ic_stat_pluriwave; do
|
|
if unzip -l "$APK" | grep -q "res/drawable/$RECURSO.xml"; then
|
|
echo "OK res/drawable/$RECURSO.xml"
|
|
else
|
|
echo "FALTA res/drawable/$RECURSO.xml"
|
|
FALTAN=1
|
|
fi
|
|
done
|
|
if [ "$FALTAN" -ne 0 ]; then
|
|
echo ""
|
|
echo "Hay drawables resueltos por nombre que NO estan en el APK."
|
|
echo "En runtime resolveran a id 0 y tumbaran la sesion de medios."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Build AAB release
|
|
run: flutter build appbundle --release
|
|
|
|
- name: Publicar en ftl-builds (Zimaboard)
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
APK_NOMBRE="pluriwave-v${VERSION}.apk"
|
|
AAB_NOMBRE="pluriwave-v${VERSION}.aab"
|
|
DESTINO="/opt/ftl-builds/builds/pluriwave/v${VERSION}"
|
|
SSH_KEY="/Users/freetlab/.openclaw/workspace/.secure/zimaboard_ed25519"
|
|
|
|
ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no ShanaiaBot@192.168.0.33 "mkdir -p ${DESTINO}"
|
|
scp -i "$SSH_KEY" -o StrictHostKeyChecking=no \
|
|
build/app/outputs/flutter-apk/app-release.apk \
|
|
"ShanaiaBot@192.168.0.33:${DESTINO}/${APK_NOMBRE}"
|
|
scp -i "$SSH_KEY" -o StrictHostKeyChecking=no \
|
|
build/app/outputs/bundle/release/app-release.aab \
|
|
"ShanaiaBot@192.168.0.33:${DESTINO}/${AAB_NOMBRE}"
|
|
echo "✅ APK: builds.freetimelab.es → pluriwave → v${VERSION}"
|
|
echo "✅ AAB: builds.freetimelab.es → pluriwave → v${VERSION}"
|
|
|
|
- name: Preparar credenciales de Google Play
|
|
if: ${{ gitea.ref == 'refs/heads/PRO' }}
|
|
env:
|
|
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
|
|
run: |
|
|
if [ -z "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON" ]; then
|
|
echo "ERROR: falta el secreto GOOGLE_PLAY_SERVICE_ACCOUNT_JSON"
|
|
exit 1
|
|
fi
|
|
mkdir -p fastlane/credentials
|
|
printf '%s' "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON" > fastlane/credentials/google-play-service-account.json
|
|
|
|
- name: Instalar Fastlane
|
|
if: ${{ gitea.ref == 'refs/heads/PRO' }}
|
|
run: |
|
|
gem list -i fastlane >/dev/null 2>&1 || gem install fastlane --no-document
|
|
|
|
- name: Publicar AAB en Google Play Internal Testing
|
|
if: ${{ gitea.ref == 'refs/heads/PRO' }}
|
|
env:
|
|
PLAY_JSON_KEY_PATH: fastlane/credentials/google-play-service-account.json
|
|
PLAY_AAB_PATH: build/app/outputs/bundle/release/app-release.aab
|
|
PLAY_TRACK: internal
|
|
PLAY_RELEASE_STATUS: completed
|
|
run: fastlane android upload_internal
|
|
|
|
- name: Notificar Telegram
|
|
if: always()
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
COMMIT="${{ steps.version.outputs.commit }}"
|
|
BRANCH="${CURRENT_REF#refs/heads/}"
|
|
BOT_TOKEN=$(plutil -extract 'EnvironmentVariables:TELEGRAM_BOT_TOKEN' raw /Users/freetlab/Library/LaunchAgents/ai.openclaw.gateway.plist 2>/dev/null || echo "")
|
|
if [ -z "$BOT_TOKEN" ]; then exit 0; fi
|
|
if [ "${{ job.status }}" = "success" ]; then
|
|
MSG="✅ *PluriWave* v${VERSION} · rama ${BRANCH} · ${COMMIT}%0AAPK + AAB generados"
|
|
if [ "$BRANCH" = "PRO" ]; then
|
|
MSG="${MSG}%0APublicado en Google Play · Internal Testing"
|
|
else
|
|
MSG="${MSG}%0APublicado en builds.freetimelab.es"
|
|
fi
|
|
else
|
|
MSG="❌ *PluriWave* build FAILED · rama ${BRANCH} · ${COMMIT}"
|
|
fi
|
|
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
|
-d "chat_id=221721467" -d "parse_mode=Markdown" -d "text=${MSG}" || true
|