name: Release on: push: tags: - 'v*' workflow_dispatch: inputs: version: description: 'Version tag (e.g., v1.0.0)' required: true default: 'v1.0.0' jobs: # Get version first (quick job) get-version: runs-on: ubuntu-latest outputs: version: ${{ steps.version.outputs.version }} steps: - name: Get version id: version run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT else echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT fi # Android and iOS build in PARALLEL build-android: runs-on: ubuntu-latest needs: get-version steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' - name: Setup Go uses: actions/setup-go@v5 with: go-version: '1.21' cache-dependency-path: go_backend/go.sum # Cache Gradle for faster builds - name: Cache Gradle uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: gradle-${{ runner.os }}- - name: Install Android SDK & NDK uses: android-actions/setup-android@v3 - name: Install gomobile run: | go install golang.org/x/mobile/cmd/gomobile@latest gomobile init - name: Build Go backend for Android working-directory: go_backend run: | mkdir -p ../android/app/libs gomobile bind -target=android -androidapi 24 -o ../android/app/libs/gobackend.aar . env: CGO_ENABLED: 1 - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: 'stable' cache: true - name: Get Flutter dependencies run: flutter pub get - name: Generate app icons run: dart run flutter_launcher_icons - name: Build APK (Release) run: flutter build apk --release --split-per-abi - name: Rename APKs run: | VERSION=${{ needs.get-version.outputs.version }} cd build/app/outputs/flutter-apk mv app-arm64-v8a-release.apk SpotiFLAC-${VERSION}-arm64.apk || true mv app-armeabi-v7a-release.apk SpotiFLAC-${VERSION}-arm32.apk || true mv app-release.apk SpotiFLAC-${VERSION}-universal.apk || true ls -la - name: Upload APK artifact uses: actions/upload-artifact@v4 with: name: android-apk path: build/app/outputs/flutter-apk/SpotiFLAC-*.apk build-ios: runs-on: macos-latest needs: get-version # Only depends on version, NOT android build! steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: '1.21' cache-dependency-path: go_backend/go.sum # Cache CocoaPods - name: Cache CocoaPods uses: actions/cache@v4 with: path: ios/Pods key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }} restore-keys: pods-${{ runner.os }}- - name: Install gomobile run: | go install golang.org/x/mobile/cmd/gomobile@latest gomobile init - name: Build Go backend for iOS working-directory: go_backend run: | mkdir -p ../ios/Frameworks gomobile bind -target=ios -o ../ios/Frameworks/Gobackend.xcframework . env: CGO_ENABLED: 1 - name: Verify XCFramework created run: | ls -la ios/Frameworks/ ls -la ios/Frameworks/Gobackend.xcframework/ || (echo "ERROR: XCFramework not found!" && exit 1) - name: Add XCFramework to Xcode project run: | # Install xcodeproj gem for modifying Xcode project sudo gem install xcodeproj # Create Ruby script to add framework cat > add_framework.rb << 'EOF' require 'xcodeproj' project_path = 'ios/Runner.xcodeproj' project = Xcodeproj::Project.open(project_path) # Get the main target target = project.targets.find { |t| t.name == 'Runner' } # Get or create Frameworks group frameworks_group = project.main_group.find_subpath('Frameworks', true) frameworks_group ||= project.main_group.new_group('Frameworks') # Add XCFramework reference framework_path = 'Frameworks/Gobackend.xcframework' framework_ref = frameworks_group.new_file(framework_path, :project) # Add to frameworks build phase frameworks_build_phase = target.frameworks_build_phase frameworks_build_phase.add_file_reference(framework_ref) # Add to embed frameworks build phase embed_phase = target.build_phases.find { |p| p.is_a?(Xcodeproj::Project::Object::PBXCopyFilesBuildPhase) && p.name == 'Embed Frameworks' } if embed_phase build_file = embed_phase.add_file_reference(framework_ref) build_file.settings = { 'ATTRIBUTES' => ['CodeSignOnCopy', 'RemoveHeadersOnCopy'] } end project.save puts "Successfully added Gobackend.xcframework to Xcode project" EOF ruby add_framework.rb - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: 'stable' cache: true - name: Get Flutter dependencies run: flutter pub get - name: Generate app icons run: dart run flutter_launcher_icons - name: Build iOS (unsigned) run: flutter build ios --release --no-codesign - name: Create IPA run: | VERSION=${{ needs.get-version.outputs.version }} mkdir -p build/ios/ipa cd build/ios/iphoneos mkdir Payload cp -r Runner.app Payload/ zip -r ../ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa Payload rm -rf Payload - name: Upload IPA artifact uses: actions/upload-artifact@v4 with: name: ios-ipa path: build/ios/ipa/SpotiFLAC-*.ipa create-release: runs-on: ubuntu-latest needs: [get-version, build-android, build-ios] permissions: contents: write steps: - name: Download Android APK uses: actions/download-artifact@v4 with: name: android-apk path: ./release - name: Download iOS IPA uses: actions/download-artifact@v4 with: name: ios-ipa path: ./release - name: Create Release uses: softprops/action-gh-release@v1 with: tag_name: ${{ needs.get-version.outputs.version }} name: SpotiFLAC ${{ needs.get-version.outputs.version }} body: | ## SpotiFLAC ${{ needs.get-version.outputs.version }} Download Spotify tracks in FLAC quality from Tidal, Qobuz & Amazon Music. ### Downloads - **Android (arm64)**: `SpotiFLAC-${{ needs.get-version.outputs.version }}-arm64.apk` (recommended) - **Android (arm32)**: `SpotiFLAC-${{ needs.get-version.outputs.version }}-arm32.apk` (older devices) - **iOS**: `SpotiFLAC-${{ needs.get-version.outputs.version }}-ios-unsigned.ipa` (sideload required) ### Installation **Android**: Enable "Install from unknown sources" and install the APK **iOS**: Use AltStore, Sideloadly, or similar tools to sideload the IPA files: ./release/* draft: false prerelease: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}