From 8f14ff169a429260734ef437799a13c1e1db3aec Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 18:24:03 +0200 Subject: [PATCH 1/4] Aktualisieren von release.yml --- .github/workflows/release.yml | 505 +--------------------------------- 1 file changed, 5 insertions(+), 500 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4769f2ec..e2d0a080 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Release +name: Build Android on: push: @@ -12,52 +12,18 @@ on: default: "v1.0.0" jobs: - # Get version first (quick job) - get-version: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.version.outputs.version }} - is_prerelease: ${{ steps.version.outputs.is_prerelease }} - steps: - - name: Get version - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - VERSION="${{ github.event.inputs.version }}" - else - VERSION="${GITHUB_REF#refs/tags/}" - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - - # Check if version contains -preview, -beta, -rc, or -alpha (NOT -hotfix) - VERSION_LOWER=$(echo "$VERSION" | tr '[:upper:]' '[:lower:]') - if [[ "$VERSION_LOWER" == *"-preview"* ]] || [[ "$VERSION_LOWER" == *"-beta"* ]] || [[ "$VERSION_LOWER" == *"-rc"* ]] || [[ "$VERSION_LOWER" == *"-alpha"* ]]; then - echo "is_prerelease=true" >> $GITHUB_OUTPUT - echo "Detected pre-release version: $VERSION" - else - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "Detected stable version: $VERSION" - fi - - # Android and iOS build in PARALLEL build-android: runs-on: ubuntu-latest - needs: get-version - steps: - name: Free disk space run: | - # Remove large unused tools (~15GB total) sudo rm -rf /usr/share/dotnet sudo rm -rf /opt/ghc sudo rm -rf /opt/hostedtoolcache/CodeQL sudo rm -rf /usr/local/share/boost sudo rm -rf /usr/share/swift sudo rm -rf /usr/local/.ghcup - # Clean docker images sudo docker image prune --all --force - # Show available space - df -h - name: Checkout repository uses: actions/checkout@v6 @@ -74,7 +40,6 @@ jobs: go-version: "1.25.8" cache-dependency-path: go_backend/go.sum - # Cache Gradle for faster builds - name: Cache Gradle uses: actions/cache@v5 with: @@ -82,22 +47,11 @@ jobs: ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: gradle-${{ runner.os }}- - name: Install Android SDK & NDK run: | - # Use pre-installed Android SDK on GitHub runners - echo "ANDROID_HOME=$ANDROID_HOME" - echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" - - # Accept licenses yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true - - # Install NDK r29 (supports 16KB page size for Android 15+) - # Platform android-36 and build-tools 36.0.0 for targetSdk 36 (Android 16) $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;29.0.14206865" "platforms;android-36" "build-tools;36.0.0" - - # Set NDK path echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> $GITHUB_ENV - name: Install gomobile @@ -122,460 +76,11 @@ jobs: - name: Get Flutter dependencies run: flutter pub get - - name: Generate app icons - run: dart run flutter_launcher_icons - - - name: Build APK (Release - unsigned) - run: | - flutter build apk --release --split-per-abi || true - # Verify APKs were created - ls -la build/app/outputs/flutter-apk/ - if [ ! -f "build/app/outputs/flutter-apk/app-arm64-v8a-release.apk" ]; then - echo "ERROR: APK not found!" - exit 1 - fi - - - name: Sign APKs - uses: r0adkll/sign-android-release@v1 - id: sign_arm64 - with: - releaseDirectory: build/app/outputs/flutter-apk - signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }} - alias: ${{ secrets.KEY_ALIAS }} - keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }} - keyPassword: ${{ secrets.KEY_PASSWORD }} - env: - BUILD_TOOLS_VERSION: "36.0.0" - - - name: Rename APKs - run: | - VERSION=${{ needs.get-version.outputs.version }} - cd build/app/outputs/flutter-apk - # Signed files have -signed suffix - mv app-arm64-v8a-release-signed.apk SpotiFLAC-${VERSION}-arm64.apk || mv app-arm64-v8a-release.apk SpotiFLAC-${VERSION}-arm64.apk || true - mv app-armeabi-v7a-release-signed.apk SpotiFLAC-${VERSION}-arm32.apk || mv app-armeabi-v7a-release.apk SpotiFLAC-${VERSION}-arm32.apk || true - mv app-release-signed.apk SpotiFLAC-${VERSION}-universal.apk || mv app-release.apk SpotiFLAC-${VERSION}-universal.apk || true - ls -la + - name: Build APK (Only arm64) + run: flutter build apk --release --target-platform android-arm64 - name: Upload APK artifact uses: actions/upload-artifact@v6 with: - name: android-apk - path: build/app/outputs/flutter-apk/SpotiFLAC-*.apk - - build-ios: - runs-on: macos-15 - needs: get-version # Only depends on version, NOT android build! - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Select Xcode 26.1.1 - run: | - sudo xcode-select -s /Applications/Xcode_26.1.1.app - xcodebuild -version - - - name: Setup Go - uses: actions/setup-go@v6 - with: - go-version: "1.25.8" - cache-dependency-path: go_backend/go.sum - - # Cache CocoaPods - - name: Cache CocoaPods - uses: actions/cache@v5 - 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 -tags 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: | - # Build Flutter iOS without codesigning - flutter build ios --release --no-codesign --config-only - - # Use xcodebuild with code signing disabled - cd ios - xcodebuild -workspace Runner.xcworkspace \ - -scheme Runner \ - -configuration Release \ - -sdk iphoneos \ - -destination 'generic/platform=iOS' \ - -archivePath build/Runner.xcarchive \ - archive \ - CODE_SIGNING_ALLOWED=NO \ - CODE_SIGNING_REQUIRED=NO \ - CODE_SIGN_IDENTITY="" \ - DEVELOPMENT_TEAM="" - - - name: Create IPA - run: | - VERSION=${{ needs.get-version.outputs.version }} - mkdir -p build/ios/ipa - cd ios/build/Runner.xcarchive/Products/Applications - mkdir Payload - cp -r Runner.app Payload/ - # Use absolute path to avoid relative path issues - zip -r $GITHUB_WORKSPACE/build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa Payload - rm -rf Payload - - - name: Verify IPA created - run: | - ls -la build/ios/ipa/ - VERSION=${{ needs.get-version.outputs.version }} - if [ ! -f "build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa" ]; then - echo "ERROR: IPA not created!" - exit 1 - fi - - - name: Upload IPA artifact - uses: actions/upload-artifact@v6 - 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: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 # Full history needed for git-cliff - - - name: Generate changelog with git-cliff - id: changelog - uses: orhun/git-cliff-action@v4 - with: - config: cliff.toml - args: --latest --strip header - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OUTPUT: /tmp/changelog.txt - - - name: Show generated changelog - run: | - echo "Generated changelog:" - cat /tmp/changelog.txt - - - name: Download Android APK - uses: actions/download-artifact@v7 - with: - name: android-apk - path: ./release - - - name: Download iOS IPA - uses: actions/download-artifact@v7 - with: - name: ios-ipa - path: ./release - - - name: Prepare release body - run: | - VERSION=${{ needs.get-version.outputs.version }} - REPO_OWNER="${{ github.repository_owner }}" - REPO_NAME="${{ github.event.repository.name }}" - CURRENT_REF=$(git rev-list -n 1 "$VERSION" 2>/dev/null || git rev-parse HEAD) - PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${CURRENT_REF}^" 2>/dev/null || true) - - # Start with git-cliff changelog, but replace its compare footer with a - # deterministic previous-tag lookup from git. - sed '/^## [0-9][0-9.[:alpha:]-]*$/d; /^\*\*Full Changelog\*\*/d' /tmp/changelog.txt > /tmp/release_body.txt - - if [ -n "$PREVIOUS_TAG" ]; then - printf '\n**Full Changelog**: [%s...%s](https://github.com/%s/%s/compare/%s...%s)\n' \ - "$PREVIOUS_TAG" "$VERSION" "$REPO_OWNER" "$REPO_NAME" "$PREVIOUS_TAG" "$VERSION" \ - >> /tmp/release_body.txt - fi - - # Append download section - cat >> /tmp/release_body.txt << FOOTER - - --- - - ### Downloads - - #### Android - - **arm64**: \`SpotiFLAC-${VERSION}-arm64.apk\` (recommended for modern devices) - - **arm32**: \`SpotiFLAC-${VERSION}-arm32.apk\` (older devices) - - #### iOS - - **iOS**: \`SpotiFLAC-${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 - - ![arm64](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm64.apk?style=flat-square&logo=android&label=arm64&color=3DDC84) ![arm32](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm32.apk?style=flat-square&logo=android&label=arm32&color=3DDC84) ![iOS](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa?style=flat-square&logo=apple&label=iOS&color=0078D6) - FOOTER - - echo "Release body:" - cat /tmp/release_body.txt - - - name: Create Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ needs.get-version.outputs.version }} - name: SpotiFLAC ${{ needs.get-version.outputs.version }} - body_path: /tmp/release_body.txt - files: ./release/* - draft: false - prerelease: ${{ needs.get-version.outputs.is_prerelease == 'true' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - update-altstore: - runs-on: ubuntu-latest - needs: [get-version, build-ios, create-release] - if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} - permissions: - contents: write - - steps: - - name: Checkout main branch - uses: actions/checkout@v6 - with: - ref: main - - - name: Download iOS IPA - uses: actions/download-artifact@v7 - with: - name: ios-ipa - path: ./release - - - name: Update apps.json - run: | - VERSION="${{ needs.get-version.outputs.version }}" - VERSION_NUM="${VERSION#v}" - DATE=$(date -u +%Y-%m-%d) - IPA_FILE=$(find ./release -name "*ios*.ipa" | head -1) - - if [ -z "$IPA_FILE" ]; then - echo "WARNING: IPA file not found, skipping apps.json update" - exit 0 - fi - - IPA_SIZE=$(stat -c%s "$IPA_FILE" 2>/dev/null || stat -f%z "$IPA_FILE") - - if [ ! -f apps.json ]; then - echo "WARNING: apps.json not found on main, skipping" - exit 0 - fi - - jq --arg ver "$VERSION_NUM" \ - --arg date "$DATE" \ - --arg url "https://github.com/zarzet/SpotiFLAC-Mobile/releases/download/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa" \ - --argjson size "$IPA_SIZE" \ - '.apps[0].version = $ver | .apps[0].versionDate = $date | .apps[0].downloadURL = $url | .apps[0].size = $size' \ - apps.json > apps.json.tmp && mv apps.json.tmp apps.json - - echo "Updated apps.json:" - cat apps.json - - - name: Commit and push - run: | - VERSION="${{ needs.get-version.outputs.version }}" - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add apps.json - git diff --cached --quiet && echo "No changes to commit" || \ - (git commit -m "chore: update AltStore source to ${VERSION}" && git push) - - notify-telegram: - runs-on: ubuntu-latest - needs: [get-version, create-release] - if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Download Android APK - uses: actions/download-artifact@v7 - with: - name: android-apk - path: ./release - - - name: Download iOS IPA - uses: actions/download-artifact@v7 - with: - name: ios-ipa - path: ./release - - - name: Generate changelog with git-cliff for Telegram - uses: orhun/git-cliff-action@v4 - with: - config: cliff.toml - args: --latest --strip all - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OUTPUT: /tmp/cliff_tg.txt - - - name: Convert changelog for Telegram - id: changelog - run: | - if [ ! -s /tmp/cliff_tg.txt ]; then - echo "See release notes on GitHub for details." > /tmp/changelog.txt - else - # Convert Markdown to Telegram HTML - CHANGELOG=$(cat /tmp/cliff_tg.txt | \ - sed '/^## [0-9][0-9.[:alpha:]-]*$/d' | \ - sed '/^\*\*Full Changelog\*\*/d' | \ - sed 's/ by \[@[^]]*\](https:\/\/github\.com\/[^)]*)//g' | \ - sed 's/ by @[A-Za-z0-9_-]\+//g' | \ - sed 's/\[#\([0-9]*\)\]([^)]*)/#\1/g' | \ - sed 's/\[@\([^]]*\)\]([^)]*)/@\1/g' | \ - sed 's/&/\&/g' | \ - sed 's//\>/g' | \ - sed 's/\*\*\([^*]*\)\*\*/\1<\/b>/g' | \ - sed 's/^### \(.*\)$/\1<\/b>/g' | \ - sed 's/^## \(.*\)$/\1<\/b>/g' | \ - sed 's/^- /• /g') - - # Truncate for Telegram 4096 char limit - CHANGELOG=$(echo "$CHANGELOG" | head -c 2500 | sed '$d') - echo "$CHANGELOG" > /tmp/changelog.txt - fi - - echo "Telegram changelog:" - cat /tmp/changelog.txt - - - name: Send to Telegram Channel - env: - TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} - TELEGRAM_CHANNEL_ID: ${{ secrets.TELEGRAM_CHANNEL_ID }} - run: | - VERSION=${{ needs.get-version.outputs.version }} - CHANGELOG=$(cat /tmp/changelog.txt) - - # Find APK files - ARM64_APK=$(find ./release -name "*arm64*.apk" | head -1) - ARM32_APK=$(find ./release -name "*arm32*.apk" | head -1) - - # Prepare message with changelog (HTML format) - printf '%s\n' \ - "SpotiFLAC Mobile ${VERSION} Released!" \ - "" \ - "What's New:" \ - "${CHANGELOG}" \ - "" \ - "View Release Notes" \ - > /tmp/telegram_message.txt - - MESSAGE=$(cat /tmp/telegram_message.txt) - - # Send message first (using HTML parse mode) - # Use --data-urlencode for proper encoding of special chars (+, &, etc.) - # Use || true to ensure file uploads continue even if message fails - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ - --data-urlencode "chat_id=${TELEGRAM_CHANNEL_ID}" \ - --data-urlencode "text=${MESSAGE}" \ - --data-urlencode "parse_mode=HTML" \ - --data-urlencode "disable_web_page_preview=true" || true - - # Upload arm64 APK to channel - if [ -f "$ARM64_APK" ]; then - echo "Uploading arm64 APK to Telegram..." - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ - -F chat_id="${TELEGRAM_CHANNEL_ID}" \ - -F document=@"${ARM64_APK}" \ - -F caption="SpotiFLAC ${VERSION} - arm64 (recommended)" - fi - - # Upload arm32 APK to channel - if [ -f "$ARM32_APK" ]; then - echo "Uploading arm32 APK to Telegram..." - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ - -F chat_id="${TELEGRAM_CHANNEL_ID}" \ - -F document=@"${ARM32_APK}" \ - -F caption="SpotiFLAC ${VERSION} - arm32" - fi - - # Upload iOS IPA to channel - IOS_IPA=$(find ./release -name "*ios*.ipa" | head -1) - if [ -f "$IOS_IPA" ]; then - echo "Uploading iOS IPA to Telegram..." - curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ - -F chat_id="${TELEGRAM_CHANNEL_ID}" \ - -F document=@"${IOS_IPA}" \ - -F caption="SpotiFLAC ${VERSION} - iOS (unsigned, sideload required)" - fi - - echo "Telegram notification sent!" + name: SpotiFLAC-arm64-apk + path: build/app/outputs/flutter-apk/app-release.apk From f8b7812943f08d5e17d21785d6bd0fa8cc9ab996 Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 18:47:09 +0200 Subject: [PATCH 2/4] feat: add deduplicateDownloads setting & fix build errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add deduplicateDownloads field to AppSettings (default: true) - Add setDeduplicateDownloads() to SettingsNotifier - Fix type mismatch in files_settings_page (Object → String cast) - Run build_runner to regenerate settings.g.dart --- .github/workflows/release.yml | 3 +++ lib/models/settings.dart | 6 ++++++ lib/providers/settings_provider.dart | 5 +++++ lib/screens/settings/files_settings_page.dart | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2d0a080..51f74317 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -76,6 +76,9 @@ jobs: - name: Get Flutter dependencies run: flutter pub get + - name: Generate code + run: dart run build_runner build --delete-conflicting-outputs + - name: Build APK (Only arm64) run: flutter build apk --release --target-platform android-arm64 diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 147c2c20..17f125d5 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -82,6 +82,9 @@ class AppSettings { final String lastSeenVersion; // Last app version the user has acknowledged (e.g. '3.7.0') + final bool + deduplicateDownloads; // Skip downloading tracks already present in history + const AppSettings({ this.defaultService = 'tidal', this.audioQuality = 'LOSSLESS', @@ -144,6 +147,7 @@ class AppSettings { this.lyricsMultiPersonWordByWord = false, this.musixmatchLanguage = '', this.lastSeenVersion = '', + this.deduplicateDownloads = true, }); AppSettings copyWith({ @@ -205,6 +209,7 @@ class AppSettings { bool? lyricsMultiPersonWordByWord, String? musixmatchLanguage, String? lastSeenVersion, + bool? deduplicateDownloads, }) { return AppSettings( defaultService: defaultService ?? this.defaultService, @@ -281,6 +286,7 @@ class AppSettings { lyricsMultiPersonWordByWord ?? this.lyricsMultiPersonWordByWord, musixmatchLanguage: musixmatchLanguage ?? this.musixmatchLanguage, lastSeenVersion: lastSeenVersion ?? this.lastSeenVersion, + deduplicateDownloads: deduplicateDownloads ?? this.deduplicateDownloads, ); } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 4e2ea6ca..906715c3 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -544,6 +544,11 @@ class SettingsNotifier extends Notifier { state = state.copyWith(hasCompletedTutorial: true); _saveSettings(); } + + void setDeduplicateDownloads(bool enabled) { + state = state.copyWith(deduplicateDownloads: enabled); + _saveSettings(); + } } final settingsProvider = NotifierProvider( diff --git a/lib/screens/settings/files_settings_page.dart b/lib/screens/settings/files_settings_page.dart index 0382abc4..85a7bee6 100644 --- a/lib/screens/settings/files_settings_page.dart +++ b/lib/screens/settings/files_settings_page.dart @@ -753,7 +753,7 @@ class _FilesSettingsPageState extends ConsumerState { ), const SizedBox(height: 8), Text( - description ?? context.l10n.downloadFilenameDescription, + description ?? context.l10n.downloadFilenameDescription as String, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), From b55be00fab81023e257afbacee2468fabd6595e5 Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 19:04:39 +0200 Subject: [PATCH 3/4] i18n: add missing optionsDefaultSearchTabAlbums/Tracks keys to all locales --- lib/l10n/arb/app_de.arb | 8 ++++++++ lib/l10n/arb/app_en.arb | 8 ++++++++ lib/l10n/arb/app_es.arb | 8 ++++++++ lib/l10n/arb/app_es_ES.arb | 8 ++++++++ lib/l10n/arb/app_fr.arb | 8 ++++++++ lib/l10n/arb/app_hi.arb | 8 ++++++++ lib/l10n/arb/app_id.arb | 8 ++++++++ lib/l10n/arb/app_ja.arb | 8 ++++++++ lib/l10n/arb/app_ko.arb | 8 ++++++++ lib/l10n/arb/app_nl.arb | 8 ++++++++ lib/l10n/arb/app_pt.arb | 8 ++++++++ lib/l10n/arb/app_pt_PT.arb | 8 ++++++++ lib/l10n/arb/app_ru.arb | 8 ++++++++ lib/l10n/arb/app_tr.arb | 8 ++++++++ lib/l10n/arb/app_uk.arb | 8 ++++++++ lib/l10n/arb/app_zh.arb | 8 ++++++++ lib/l10n/arb/app_zh_CN.arb | 8 ++++++++ lib/l10n/arb/app_zh_TW.arb | 8 ++++++++ 18 files changed, 144 insertions(+) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 33150a80..c16f90a3 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tippe auf Deezer oder Spotify, um von der Erweiterung zurückzuwechseln", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index a5d4d5ac..aa5ce62c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index a17f7299..a5294db3 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -1753,6 +1753,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsReplayGain": "ReplayGain", "@optionsReplayGain": { "description": "Title for ReplayGain setting toggle" diff --git a/lib/l10n/arb/app_es_ES.arb b/lib/l10n/arb/app_es_ES.arb index 7fccb158..3d1432d2 100644 --- a/lib/l10n/arb/app_es_ES.arb +++ b/lib/l10n/arb/app_es_ES.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Toque Deezer o Spotify para volver desde la extensión", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index ab865785..f68dedc8 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Appuyez sur Deezer ou Spotify pour revenir à l'extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index 7935892f..2c18e2c2 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 6b0733dd..a53fd98d 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Ketuk Deezer atau Spotify untuk beralih dari ekstensi", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index 75f9e1b0..c755edf2 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index 398c09b5..cf77be41 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Deezer 또는 Spotify를 탭하여 확장 기능에서 다시 전환하세요.", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index b9cbaa09..91acf351 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_pt.arb b/lib/l10n/arb/app_pt.arb index de1ff199..bde8d0f1 100644 --- a/lib/l10n/arb/app_pt.arb +++ b/lib/l10n/arb/app_pt.arb @@ -1753,6 +1753,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsReplayGain": "ReplayGain", "@optionsReplayGain": { "description": "Title for ReplayGain setting toggle" diff --git a/lib/l10n/arb/app_pt_PT.arb b/lib/l10n/arb/app_pt_PT.arb index 5b7a11fc..a03a78e4 100644 --- a/lib/l10n/arb/app_pt_PT.arb +++ b/lib/l10n/arb/app_pt_PT.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Toque no Deezer ou Spotify para alternar de volta da extensão", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index 54b47b1a..a3e2658c 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Нажмите Deezer или Spotify для возврата с расширения", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_tr.arb b/lib/l10n/arb/app_tr.arb index 9ffbe3b0..fb92a71b 100644 --- a/lib/l10n/arb/app_tr.arb +++ b/lib/l10n/arb/app_tr.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Dahili kaynaklara dönmek için Deezer veya Spotify'a tıkla", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_uk.arb b/lib/l10n/arb/app_uk.arb index d9f2ed50..d301f9e9 100644 --- a/lib/l10n/arb/app_uk.arb +++ b/lib/l10n/arb/app_uk.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Натисніть Deezer або Spotify, щоб повернутися до розширення", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_zh.arb b/lib/l10n/arb/app_zh.arb index 3127c84e..3c25cbc7 100644 --- a/lib/l10n/arb/app_zh.arb +++ b/lib/l10n/arb/app_zh.arb @@ -1753,6 +1753,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsReplayGain": "ReplayGain", "@optionsReplayGain": { "description": "Title for ReplayGain setting toggle" diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index cd341cef..0d72d929 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "点击 Deezer 或 Spotify 即可从扩展程序切换回来", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index 139262bf..cb6bead3 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -166,6 +166,14 @@ "@optionsDefaultSearchTabSubtitle": { "description": "Subtitle for the preferred default search tab setting" }, + "optionsDefaultSearchTabAlbums": "Albums", + "@optionsDefaultSearchTabAlbums": { + "description": "Default search tab option - Albums tab" + }, + "optionsDefaultSearchTabTracks": "Tracks", + "@optionsDefaultSearchTabTracks": { + "description": "Default search tab option - Tracks tab" + }, "optionsSwitchBack": "Tap Deezer or Spotify to switch back from extension", "@optionsSwitchBack": { "description": "Hint to switch back to built-in providers" From 2c7621c1a541f1d71b01db878b4d3b0ece7c1922 Mon Sep 17 00:00:00 2001 From: Amonoman Date: Sat, 2 May 2026 19:28:26 +0200 Subject: [PATCH 4/4] revert: release.yml to normal --- .github/workflows/release.yml | 506 +++++++++++++++++++++++++++++++++- 1 file changed, 499 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51f74317..4769f2ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Build Android +name: Release on: push: @@ -12,18 +12,52 @@ on: default: "v1.0.0" jobs: + # Get version first (quick job) + get-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + is_prerelease: ${{ steps.version.outputs.is_prerelease }} + steps: + - name: Get version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Check if version contains -preview, -beta, -rc, or -alpha (NOT -hotfix) + VERSION_LOWER=$(echo "$VERSION" | tr '[:upper:]' '[:lower:]') + if [[ "$VERSION_LOWER" == *"-preview"* ]] || [[ "$VERSION_LOWER" == *"-beta"* ]] || [[ "$VERSION_LOWER" == *"-rc"* ]] || [[ "$VERSION_LOWER" == *"-alpha"* ]]; then + echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "Detected pre-release version: $VERSION" + else + echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "Detected stable version: $VERSION" + fi + + # Android and iOS build in PARALLEL build-android: runs-on: ubuntu-latest + needs: get-version + steps: - name: Free disk space run: | + # Remove large unused tools (~15GB total) sudo rm -rf /usr/share/dotnet sudo rm -rf /opt/ghc sudo rm -rf /opt/hostedtoolcache/CodeQL sudo rm -rf /usr/local/share/boost sudo rm -rf /usr/share/swift sudo rm -rf /usr/local/.ghcup + # Clean docker images sudo docker image prune --all --force + # Show available space + df -h - name: Checkout repository uses: actions/checkout@v6 @@ -40,6 +74,7 @@ jobs: go-version: "1.25.8" cache-dependency-path: go_backend/go.sum + # Cache Gradle for faster builds - name: Cache Gradle uses: actions/cache@v5 with: @@ -47,11 +82,22 @@ jobs: ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: gradle-${{ runner.os }}- - name: Install Android SDK & NDK run: | + # Use pre-installed Android SDK on GitHub runners + echo "ANDROID_HOME=$ANDROID_HOME" + echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" + + # Accept licenses yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true + + # Install NDK r29 (supports 16KB page size for Android 15+) + # Platform android-36 and build-tools 36.0.0 for targetSdk 36 (Android 16) $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;29.0.14206865" "platforms;android-36" "build-tools;36.0.0" + + # Set NDK path echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> $GITHUB_ENV - name: Install gomobile @@ -76,14 +122,460 @@ jobs: - name: Get Flutter dependencies run: flutter pub get - - name: Generate code - run: dart run build_runner build --delete-conflicting-outputs + - name: Generate app icons + run: dart run flutter_launcher_icons - - name: Build APK (Only arm64) - run: flutter build apk --release --target-platform android-arm64 + - name: Build APK (Release - unsigned) + run: | + flutter build apk --release --split-per-abi || true + # Verify APKs were created + ls -la build/app/outputs/flutter-apk/ + if [ ! -f "build/app/outputs/flutter-apk/app-arm64-v8a-release.apk" ]; then + echo "ERROR: APK not found!" + exit 1 + fi + + - name: Sign APKs + uses: r0adkll/sign-android-release@v1 + id: sign_arm64 + with: + releaseDirectory: build/app/outputs/flutter-apk + signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }} + alias: ${{ secrets.KEY_ALIAS }} + keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }} + keyPassword: ${{ secrets.KEY_PASSWORD }} + env: + BUILD_TOOLS_VERSION: "36.0.0" + + - name: Rename APKs + run: | + VERSION=${{ needs.get-version.outputs.version }} + cd build/app/outputs/flutter-apk + # Signed files have -signed suffix + mv app-arm64-v8a-release-signed.apk SpotiFLAC-${VERSION}-arm64.apk || mv app-arm64-v8a-release.apk SpotiFLAC-${VERSION}-arm64.apk || true + mv app-armeabi-v7a-release-signed.apk SpotiFLAC-${VERSION}-arm32.apk || mv app-armeabi-v7a-release.apk SpotiFLAC-${VERSION}-arm32.apk || true + mv app-release-signed.apk SpotiFLAC-${VERSION}-universal.apk || mv app-release.apk SpotiFLAC-${VERSION}-universal.apk || true + ls -la - name: Upload APK artifact uses: actions/upload-artifact@v6 with: - name: SpotiFLAC-arm64-apk - path: build/app/outputs/flutter-apk/app-release.apk + name: android-apk + path: build/app/outputs/flutter-apk/SpotiFLAC-*.apk + + build-ios: + runs-on: macos-15 + needs: get-version # Only depends on version, NOT android build! + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Select Xcode 26.1.1 + run: | + sudo xcode-select -s /Applications/Xcode_26.1.1.app + xcodebuild -version + + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version: "1.25.8" + cache-dependency-path: go_backend/go.sum + + # Cache CocoaPods + - name: Cache CocoaPods + uses: actions/cache@v5 + 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 -tags 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: | + # Build Flutter iOS without codesigning + flutter build ios --release --no-codesign --config-only + + # Use xcodebuild with code signing disabled + cd ios + xcodebuild -workspace Runner.xcworkspace \ + -scheme Runner \ + -configuration Release \ + -sdk iphoneos \ + -destination 'generic/platform=iOS' \ + -archivePath build/Runner.xcarchive \ + archive \ + CODE_SIGNING_ALLOWED=NO \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGN_IDENTITY="" \ + DEVELOPMENT_TEAM="" + + - name: Create IPA + run: | + VERSION=${{ needs.get-version.outputs.version }} + mkdir -p build/ios/ipa + cd ios/build/Runner.xcarchive/Products/Applications + mkdir Payload + cp -r Runner.app Payload/ + # Use absolute path to avoid relative path issues + zip -r $GITHUB_WORKSPACE/build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa Payload + rm -rf Payload + + - name: Verify IPA created + run: | + ls -la build/ios/ipa/ + VERSION=${{ needs.get-version.outputs.version }} + if [ ! -f "build/ios/ipa/SpotiFLAC-${VERSION}-ios-unsigned.ipa" ]; then + echo "ERROR: IPA not created!" + exit 1 + fi + + - name: Upload IPA artifact + uses: actions/upload-artifact@v6 + 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: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 # Full history needed for git-cliff + + - name: Generate changelog with git-cliff + id: changelog + uses: orhun/git-cliff-action@v4 + with: + config: cliff.toml + args: --latest --strip header + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OUTPUT: /tmp/changelog.txt + + - name: Show generated changelog + run: | + echo "Generated changelog:" + cat /tmp/changelog.txt + + - name: Download Android APK + uses: actions/download-artifact@v7 + with: + name: android-apk + path: ./release + + - name: Download iOS IPA + uses: actions/download-artifact@v7 + with: + name: ios-ipa + path: ./release + + - name: Prepare release body + run: | + VERSION=${{ needs.get-version.outputs.version }} + REPO_OWNER="${{ github.repository_owner }}" + REPO_NAME="${{ github.event.repository.name }}" + CURRENT_REF=$(git rev-list -n 1 "$VERSION" 2>/dev/null || git rev-parse HEAD) + PREVIOUS_TAG=$(git describe --tags --abbrev=0 "${CURRENT_REF}^" 2>/dev/null || true) + + # Start with git-cliff changelog, but replace its compare footer with a + # deterministic previous-tag lookup from git. + sed '/^## [0-9][0-9.[:alpha:]-]*$/d; /^\*\*Full Changelog\*\*/d' /tmp/changelog.txt > /tmp/release_body.txt + + if [ -n "$PREVIOUS_TAG" ]; then + printf '\n**Full Changelog**: [%s...%s](https://github.com/%s/%s/compare/%s...%s)\n' \ + "$PREVIOUS_TAG" "$VERSION" "$REPO_OWNER" "$REPO_NAME" "$PREVIOUS_TAG" "$VERSION" \ + >> /tmp/release_body.txt + fi + + # Append download section + cat >> /tmp/release_body.txt << FOOTER + + --- + + ### Downloads + + #### Android + - **arm64**: \`SpotiFLAC-${VERSION}-arm64.apk\` (recommended for modern devices) + - **arm32**: \`SpotiFLAC-${VERSION}-arm32.apk\` (older devices) + + #### iOS + - **iOS**: \`SpotiFLAC-${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 + + ![arm64](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm64.apk?style=flat-square&logo=android&label=arm64&color=3DDC84) ![arm32](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-arm32.apk?style=flat-square&logo=android&label=arm32&color=3DDC84) ![iOS](https://img.shields.io/github/downloads/${REPO_OWNER}/${REPO_NAME}/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa?style=flat-square&logo=apple&label=iOS&color=0078D6) + FOOTER + + echo "Release body:" + cat /tmp/release_body.txt + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.get-version.outputs.version }} + name: SpotiFLAC ${{ needs.get-version.outputs.version }} + body_path: /tmp/release_body.txt + files: ./release/* + draft: false + prerelease: ${{ needs.get-version.outputs.is_prerelease == 'true' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + update-altstore: + runs-on: ubuntu-latest + needs: [get-version, build-ios, create-release] + if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} + permissions: + contents: write + + steps: + - name: Checkout main branch + uses: actions/checkout@v6 + with: + ref: main + + - name: Download iOS IPA + uses: actions/download-artifact@v7 + with: + name: ios-ipa + path: ./release + + - name: Update apps.json + run: | + VERSION="${{ needs.get-version.outputs.version }}" + VERSION_NUM="${VERSION#v}" + DATE=$(date -u +%Y-%m-%d) + IPA_FILE=$(find ./release -name "*ios*.ipa" | head -1) + + if [ -z "$IPA_FILE" ]; then + echo "WARNING: IPA file not found, skipping apps.json update" + exit 0 + fi + + IPA_SIZE=$(stat -c%s "$IPA_FILE" 2>/dev/null || stat -f%z "$IPA_FILE") + + if [ ! -f apps.json ]; then + echo "WARNING: apps.json not found on main, skipping" + exit 0 + fi + + jq --arg ver "$VERSION_NUM" \ + --arg date "$DATE" \ + --arg url "https://github.com/zarzet/SpotiFLAC-Mobile/releases/download/${VERSION}/SpotiFLAC-${VERSION}-ios-unsigned.ipa" \ + --argjson size "$IPA_SIZE" \ + '.apps[0].version = $ver | .apps[0].versionDate = $date | .apps[0].downloadURL = $url | .apps[0].size = $size' \ + apps.json > apps.json.tmp && mv apps.json.tmp apps.json + + echo "Updated apps.json:" + cat apps.json + + - name: Commit and push + run: | + VERSION="${{ needs.get-version.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apps.json + git diff --cached --quiet && echo "No changes to commit" || \ + (git commit -m "chore: update AltStore source to ${VERSION}" && git push) + + notify-telegram: + runs-on: ubuntu-latest + needs: [get-version, create-release] + if: ${{ needs.get-version.outputs.is_prerelease != 'true' }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Download Android APK + uses: actions/download-artifact@v7 + with: + name: android-apk + path: ./release + + - name: Download iOS IPA + uses: actions/download-artifact@v7 + with: + name: ios-ipa + path: ./release + + - name: Generate changelog with git-cliff for Telegram + uses: orhun/git-cliff-action@v4 + with: + config: cliff.toml + args: --latest --strip all + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OUTPUT: /tmp/cliff_tg.txt + + - name: Convert changelog for Telegram + id: changelog + run: | + if [ ! -s /tmp/cliff_tg.txt ]; then + echo "See release notes on GitHub for details." > /tmp/changelog.txt + else + # Convert Markdown to Telegram HTML + CHANGELOG=$(cat /tmp/cliff_tg.txt | \ + sed '/^## [0-9][0-9.[:alpha:]-]*$/d' | \ + sed '/^\*\*Full Changelog\*\*/d' | \ + sed 's/ by \[@[^]]*\](https:\/\/github\.com\/[^)]*)//g' | \ + sed 's/ by @[A-Za-z0-9_-]\+//g' | \ + sed 's/\[#\([0-9]*\)\]([^)]*)/#\1/g' | \ + sed 's/\[@\([^]]*\)\]([^)]*)/@\1/g' | \ + sed 's/&/\&/g' | \ + sed 's//\>/g' | \ + sed 's/\*\*\([^*]*\)\*\*/\1<\/b>/g' | \ + sed 's/^### \(.*\)$/\1<\/b>/g' | \ + sed 's/^## \(.*\)$/\1<\/b>/g' | \ + sed 's/^- /• /g') + + # Truncate for Telegram 4096 char limit + CHANGELOG=$(echo "$CHANGELOG" | head -c 2500 | sed '$d') + echo "$CHANGELOG" > /tmp/changelog.txt + fi + + echo "Telegram changelog:" + cat /tmp/changelog.txt + + - name: Send to Telegram Channel + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHANNEL_ID: ${{ secrets.TELEGRAM_CHANNEL_ID }} + run: | + VERSION=${{ needs.get-version.outputs.version }} + CHANGELOG=$(cat /tmp/changelog.txt) + + # Find APK files + ARM64_APK=$(find ./release -name "*arm64*.apk" | head -1) + ARM32_APK=$(find ./release -name "*arm32*.apk" | head -1) + + # Prepare message with changelog (HTML format) + printf '%s\n' \ + "SpotiFLAC Mobile ${VERSION} Released!" \ + "" \ + "What's New:" \ + "${CHANGELOG}" \ + "" \ + "View Release Notes" \ + > /tmp/telegram_message.txt + + MESSAGE=$(cat /tmp/telegram_message.txt) + + # Send message first (using HTML parse mode) + # Use --data-urlencode for proper encoding of special chars (+, &, etc.) + # Use || true to ensure file uploads continue even if message fails + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + --data-urlencode "chat_id=${TELEGRAM_CHANNEL_ID}" \ + --data-urlencode "text=${MESSAGE}" \ + --data-urlencode "parse_mode=HTML" \ + --data-urlencode "disable_web_page_preview=true" || true + + # Upload arm64 APK to channel + if [ -f "$ARM64_APK" ]; then + echo "Uploading arm64 APK to Telegram..." + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ + -F chat_id="${TELEGRAM_CHANNEL_ID}" \ + -F document=@"${ARM64_APK}" \ + -F caption="SpotiFLAC ${VERSION} - arm64 (recommended)" + fi + + # Upload arm32 APK to channel + if [ -f "$ARM32_APK" ]; then + echo "Uploading arm32 APK to Telegram..." + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ + -F chat_id="${TELEGRAM_CHANNEL_ID}" \ + -F document=@"${ARM32_APK}" \ + -F caption="SpotiFLAC ${VERSION} - arm32" + fi + + # Upload iOS IPA to channel + IOS_IPA=$(find ./release -name "*ios*.ipa" | head -1) + if [ -f "$IOS_IPA" ]; then + echo "Uploading iOS IPA to Telegram..." + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendDocument" \ + -F chat_id="${TELEGRAM_CHANNEL_ID}" \ + -F document=@"${IOS_IPA}" \ + -F caption="SpotiFLAC ${VERSION} - iOS (unsigned, sideload required)" + fi + + echo "Telegram notification sent!"