diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2cb57f..a20b762 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,20 +3,42 @@ name: Flutter CI Builds on: workflow_dispatch: push: - branches: [ main ] + branches: [ main, ci-setup ] tags: - 'v*' - 'release-*' pull_request: branches: [ main ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read jobs: + preflight: + name: Preflight Checks + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Show Runner Info + run: | + uname -a + df -h + echo "Branch: ${{ github.ref }}" + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + - name: Flutter Doctor + run: flutter doctor -v android: - name: Build Android APK - runs-on: ubuntu-latest + name: Build Android APK/AAB + runs-on: ubuntu-22.04 env: CI: true steps: @@ -29,21 +51,58 @@ jobs: distribution: 'temurin' java-version: '17' + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + cmdline-tools-version: latest + packages: | + platforms;android-34 + build-tools;34.0.0 + platform-tools + + - name: Accept Android licenses + run: yes | sdkmanager --licenses + - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: 'stable' cache: true - - name: Flutter version run: flutter --version + - name: Doctor (sanity) + run: flutter doctor -v + - name: Install dependencies run: flutter pub get + - name: Prepare Android keystore (optional) + if: ${{ secrets.ANDROID_KEYSTORE_BASE64 != '' }} + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + shell: bash + run: | + echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks + cat > android/key.properties <<'EOF' + storeFile=../app/keystore.jks + storePassword=${ANDROID_KEYSTORE_PASSWORD} + keyAlias=${ANDROID_KEY_ALIAS} + keyPassword=${ANDROID_KEY_PASSWORD} + EOF + - name: Build APK (split per ABI) run: flutter build apk --release --split-per-abi --build-number ${{ github.run_number }} + - name: Build APK (universal) + run: flutter build apk --release --build-number ${{ github.run_number }} + + - name: Build App Bundle (AAB) + run: flutter build appbundle --release --build-number ${{ github.run_number }} + - name: Upload APKs uses: actions/upload-artifact@v4 with: @@ -51,9 +110,22 @@ jobs: path: | build/app/outputs/**/*.apk + - name: Upload Universal APK + uses: actions/upload-artifact@v4 + with: + name: android-apk-universal + path: build/app/outputs/flutter-apk/app-release.apk + + - name: Upload AAB + uses: actions/upload-artifact@v4 + with: + name: android-aab + path: | + build/app/outputs/**/*.aab + ios: name: Build iOS App (no codesign) - runs-on: macos-latest + runs-on: macos-14 env: CI: true steps: @@ -66,16 +138,23 @@ jobs: channel: 'stable' cache: true - - name: Flutter version - run: flutter --version + - name: Xcode version + run: xcodebuild -version - - name: Install dependencies - run: flutter pub get + - name: Install CocoaPods (gem) + run: sudo gem install cocoapods --no-document - - name: Ensure CocoaPods + - name: Pod install run: | - brew --version - brew install cocoapods || true + cd ios + pod repo update + pod install + + - name: Doctor (sanity) + run: flutter doctor -v + + - name: Install dependencies + run: flutter pub get - name: Build iOS (no codesign) run: flutter build ios --release --no-codesign --build-number ${{ github.run_number }} @@ -89,4 +168,40 @@ jobs: uses: actions/upload-artifact@v4 with: name: ios-app - path: build/ios/iphoneos/Runner.app.zip \ No newline at end of file + path: build/ios/iphoneos/Runner.app.zip + + release: + name: Publish GitHub Release + runs-on: ubuntu-latest + needs: [android, ios] + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + steps: + - name: Download Android APKs + uses: actions/download-artifact@v4 + with: + name: android-apk + path: dist + + - name: Download Android AAB + uses: actions/download-artifact@v4 + with: + name: android-aab + path: dist + + - name: Download iOS App + uses: actions/download-artifact@v4 + with: + name: ios-app + path: dist + + - name: Publish Release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: | + dist/**/*.apk + dist/**/*.aab + dist/**/*.zip \ No newline at end of file diff --git a/.github/workflows/preflight.yml b/.github/workflows/preflight.yml new file mode 100644 index 0000000..a9a8514 --- /dev/null +++ b/.github/workflows/preflight.yml @@ -0,0 +1,16 @@ +name: Preflight (no external actions) + +on: + push: + branches: + - ci-setup + +jobs: + preflight-only: + name: Preflight Only + runs-on: ubuntu-22.04 + steps: + - name: Say Hello + run: | + echo "Runner: $(uname -a)" + echo "Branch: ${{ github.ref }}" \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 4281d5c..9712efa 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -5,6 +5,13 @@ plugins { id("dev.flutter.flutter-gradle-plugin") } +// Remove imports to keep plugins block first; use fully-qualified names +val keystoreProperties = java.util.Properties() +val keystorePropertiesFile = rootProject.file("key.properties") +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(java.io.FileInputStream(keystorePropertiesFile)) +} + android { namespace = "com.example.flutter_app" compileSdk = flutter.compileSdkVersion @@ -30,11 +37,26 @@ android { versionName = flutter.versionName } + signingConfigs { + create("release") { + val storeFileProp = keystoreProperties.getProperty("storeFile") + if (storeFileProp != null) { + storeFile = file(storeFileProp) + storePassword = keystoreProperties.getProperty("storePassword") + keyAlias = keystoreProperties.getProperty("keyAlias") + keyPassword = keystoreProperties.getProperty("keyPassword") + } + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") + // Use release signing if keystore is configured, otherwise fall back to debug + signingConfig = if (keystoreProperties.getProperty("storeFile") != null) { + signingConfigs.getByName("release") + } else { + signingConfigs.getByName("debug") + } } } }