Skip to content

Track 3 submission: rework of notch floating bar into a chat-first dynamic island #2861

Track 3 submission: rework of notch floating bar into a chat-first dynamic island

Track 3 submission: rework of notch floating bar into a chat-first dynamic island #2861

name: Mobile App Checks
on:
push:
branches: main
pull_request:
branches: main
concurrency:
group: mobile-app-checks-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
has_app_codegen: ${{ steps.changes.outputs.has_app_codegen }}
has_app_compile_smoke: ${{ steps.changes.outputs.has_app_compile_smoke }}
has_app_dart: ${{ steps.changes.outputs.has_app_dart }}
has_app_l10n: ${{ steps.changes.outputs.has_app_l10n }}
has_flutter_generated: ${{ steps.changes.outputs.has_flutter_generated }}
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Detect changed paths
id: changes
uses: ./.github/actions/detect-changes
generated-files:
name: Generated Files
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.has_flutter_generated == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Resolve Flutter dependencies
working-directory: app
run: flutter pub get
- name: Check Flutter generated code
if: needs.changes.outputs.has_app_codegen == 'true'
working-directory: app
run: |
dart run build_runner build --delete-conflicting-outputs
git diff --exit-code -- lib || {
echo "Generated Dart files are stale. Run: cd app && dart run build_runner build --delete-conflicting-outputs"
exit 1
}
if [ -n "$(git ls-files --others --exclude-standard -- lib)" ]; then
echo "Generated Dart files are stale; untracked generated files were produced:"
git ls-files --others --exclude-standard -- lib
exit 1
fi
- name: Check Flutter l10n
if: needs.changes.outputs.has_app_l10n == 'true'
working-directory: app
run: |
flutter gen-l10n
git diff --exit-code -- lib/l10n || {
echo "Generated localization files are stale. Run: cd app && flutter gen-l10n"
exit 1
}
if [ -n "$(git ls-files --others --exclude-standard -- lib/l10n)" ]; then
echo "Generated localization files are stale; untracked generated files were produced:"
git ls-files --others --exclude-standard -- lib/l10n
exit 1
fi
analyze:
name: Dart Analyze (ratchet)
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.has_app_dart == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Resolve Flutter dependencies
working-directory: app
run: flutter pub get
- name: Prepare Flutter dev compile inputs
working-directory: app
run: |
cp setup/prebuilt/firebase_options.dart lib/firebase_options_dev.dart
cp setup/prebuilt/firebase_options.dart lib/firebase_options_prod.dart
{
echo "API_BASE_URL="
echo "USE_WEB_AUTH=true"
echo "USE_AUTH_CUSTOM_TOKEN=true"
echo "STAGING_API_URL="
} > .dev.env
: > .env
- name: Generate Flutter code
working-directory: app
run: dart run build_runner build --delete-conflicting-outputs
- name: Run analyzer ratchet
working-directory: app
run: bash scripts/analyze_ratchet.sh
flutter-test:
name: Flutter Tests
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.has_app_dart == 'true'
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Run Flutter tests
id: flutter_tests
working-directory: app
run: |
set -o pipefail
bash test.sh \
--reporter expanded \
--file-reporter "json:$GITHUB_WORKSPACE/flutter-test-results.json" \
2>&1 | tee "$GITHUB_WORKSPACE/flutter-test-output.log"
- name: Summarize failed Flutter tests
if: ${{ failure() && steps.flutter_tests.outcome == 'failure' }}
env:
FLUTTER_TEST_RESULTS: ${{ github.workspace }}/flutter-test-results.json
run: |
python3 - <<'PY'
import json
import os
from collections import defaultdict
from pathlib import Path
report = Path(os.environ['FLUTTER_TEST_RESULTS'])
summary = Path(os.environ['GITHUB_STEP_SUMMARY'])
tests = {}
errors = defaultdict(list)
failures = []
if report.exists():
for raw_line in report.read_text().splitlines():
try:
event = json.loads(raw_line)
except json.JSONDecodeError:
continue
event_type = event.get('type')
if event_type == 'testStart':
test = event.get('test', {})
tests[str(test.get('id'))] = test
elif event_type == 'error':
details = str(event.get('error', 'Unknown error'))
if event.get('stackTrace'):
details = f"{details}\n{event['stackTrace']}"
errors[str(event.get('testID'))].append(details)
elif event_type == 'testDone' and event.get('result') in {'failure', 'error'}:
failures.append(event)
with summary.open('a') as output:
output.write('## Flutter test diagnostics\n\n')
if not failures:
output.write(
'Flutter exited unsuccessfully but emitted no failed-test event. '
'Download the `flutter-test-diagnostics` artifact for the complete JSON stream and console log.\n'
)
for failure in failures:
test_id = str(failure.get('testID'))
test = tests.get(test_id, {})
name = test.get('name', f'Unknown test (id {test_id})')
output.write(f'### {name}\n\n')
details = '\n'.join(errors[test_id]).strip()
if details:
output.write('```text\n')
output.write(details[-6000:].replace('```', '``\\`'))
output.write('\n```\n\n')
PY
- name: Upload Flutter test diagnostics
if: ${{ failure() && steps.flutter_tests.outcome == 'failure' }}
uses: actions/upload-artifact@v7
with:
name: flutter-test-diagnostics
path: |
flutter-test-results.json
flutter-test-output.log
if-no-files-found: warn
android-compile-smoke:
name: Android Compile Smoke
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.has_app_compile_smoke == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Free runner disk space
# ubuntu-latest ships with ~14GB free; the Gradle build runs out of
# space during stripDebugSymbols. Remove unused preinstalled toolchains
# (keep /usr/local/lib/android — the build needs the Android SDK).
run: |
sudo rm -rf /usr/share/dotnet /usr/local/.ghcup /opt/hostedtoolcache/CodeQL \
/usr/local/share/boost /usr/local/share/chromium /usr/local/lib/node_modules
sudo docker image prune --all --force
df -h /
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Resolve Flutter dependencies
working-directory: app
run: flutter pub get
- name: Prepare Flutter dev compile inputs
working-directory: app
run: |
mkdir -p android/app/src/dev/ android/app/src/prod/
cp setup/prebuilt/firebase_options.dart lib/firebase_options_dev.dart
cp setup/prebuilt/firebase_options.dart lib/firebase_options_prod.dart
cp setup/prebuilt/google-services.json android/app/src/dev/
cp setup/prebuilt/google-services.json android/app/src/prod/
cp setup/prebuilt/key.properties android/key.properties
{
echo "API_BASE_URL="
echo "USE_WEB_AUTH=true"
echo "USE_AUTH_CUSTOM_TOKEN=true"
echo "STAGING_API_URL="
} > .dev.env
: > .env
- name: Generate Flutter code for compile smoke
working-directory: app
run: dart run build_runner build --delete-conflicting-outputs
- name: Setup Gradle for Flutter compile smoke
uses: gradle/actions/setup-gradle@v6
- name: Build Flutter Android dev debug APK
working-directory: app
env:
# GitHub Actions sets CI=true; android/app/build.gradle reads Codemagic
# keystore env vars in that case even for debug APK builds.
CM_KEYSTORE_PATH: ${{ github.workspace }}/app/setup/prebuilt/debug.keystore
CM_KEYSTORE_PASSWORD: android
CM_KEY_ALIAS: androiddebugkey
CM_KEY_PASSWORD: android
# Single ABI: a compile smoke doesn't need arm + arm64 + x86_64 native
# libs, and the extra ABIs double disk usage during stripDebugSymbols.
run: flutter build apk --debug --flavor dev --target-platform android-arm64