name: Validate PR on: pull_request: branches: [main] push: branches: [main] jobs: validate: name: Analyze & Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: subosito/flutter-action@v2 with: channel: 'stable' cache: true - run: flutter pub get - name: Analyze run: flutter analyze - name: Test run: flutter test build-debug-apk: name: Build Debug APK runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v5 - uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '17' - uses: subosito/flutter-action@v2 with: channel: 'stable' cache: true - uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: gradle-${{ runner.os }}- - run: flutter pub get - name: Generate icons and splash screens run: | dart run flutter_launcher_icons dart run flutter_native_splash:create - name: Build debug APK run: flutter build apk --debug - name: Upload debug APK uses: actions/upload-artifact@v4 with: name: debug-apk path: build/app/outputs/flutter-apk/app-debug.apk if-no-files-found: error retention-days: 14 build-ios-simulator: name: Build iOS Simulator runs-on: macos-26 if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v5 - uses: subosito/flutter-action@v2 with: channel: 'stable' cache: true - uses: actions/cache@v4 with: path: ios/Pods key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }} restore-keys: pods-${{ runner.os }}- - run: flutter pub get - name: Generate icons and splash screens run: | dart run flutter_launcher_icons dart run flutter_native_splash:create - name: Build iOS simulator app run: flutter build ios --debug --simulator - name: Zip Runner.app run: cd build/ios/iphonesimulator && zip -r "$GITHUB_WORKSPACE/Runner.app.zip" Runner.app - name: Upload simulator build uses: actions/upload-artifact@v4 with: name: ios-simulator path: Runner.app.zip if-no-files-found: error retention-days: 14 comment-artifacts: name: Post Artifact Links needs: [build-debug-apk, build-ios-simulator] runs-on: ubuntu-latest if: github.event_name == 'pull_request' && !cancelled() permissions: pull-requests: write steps: - uses: actions/github-script@v7 continue-on-error: true env: APK_RESULT: ${{ needs.build-debug-apk.result }} IOS_RESULT: ${{ needs.build-ios-simulator.result }} with: script: | const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}#artifacts`; const apkOk = process.env.APK_RESULT === 'success'; const iosOk = process.env.IOS_RESULT === 'success'; const lines = ['## Debug builds', '']; if (apkOk || iosOk) { lines.push(`Download from the [artifacts page](${runUrl}):`); if (apkOk) lines.push('- **debug-apk** — install on Android device/emulator'); if (iosOk) lines.push('- **ios-simulator** — unzip and install with `xcrun simctl install booted Runner.app`'); } if (!apkOk || !iosOk) { const failed = []; if (!apkOk) failed.push('Android APK'); if (!iosOk) failed.push('iOS simulator'); lines.push('', `**Failed:** ${failed.join(', ')} — check the [workflow run](${runUrl}) for details.`); } const body = lines.join('\n'); const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, }); const existing = comments.find(c => c.body.startsWith('## Debug builds')); if (existing) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body, }); } else { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body, }); }