diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 958627b3..5fc94cfb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,12 +71,18 @@ jobs: run: dart run flutter_launcher_icons - name: Build APK (Release) - run: flutter build apk --release + run: flutter build apk --release --split-per-abi - - name: Rename APK + - name: Rename APKs run: | VERSION=${{ steps.get_version.outputs.version }} - mv build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/SpotiFLAC-${VERSION}-android.apk + cd build/app/outputs/flutter-apk + # Rename split APKs + mv app-arm64-v8a-release.apk SpotiFLAC-${VERSION}-arm64.apk || true + mv app-armeabi-v7a-release.apk SpotiFLAC-${VERSION}-arm32.apk || true + # Also rename universal if exists + mv app-release.apk SpotiFLAC-${VERSION}-universal.apk || true + ls -la - name: Upload APK artifact uses: actions/upload-artifact@v4 @@ -185,7 +191,8 @@ jobs: Download Spotify tracks in FLAC quality from Tidal, Qobuz & Amazon Music. ### Downloads - - **Android**: `SpotiFLAC-${{ needs.build-android.outputs.version }}-android.apk` + - **Android (arm64)**: `SpotiFLAC-${{ needs.build-android.outputs.version }}-arm64.apk` (recommended for most devices) + - **Android (arm32)**: `SpotiFLAC-${{ needs.build-android.outputs.version }}-arm32.apk` (for older devices) - **iOS**: `SpotiFLAC-${{ needs.build-android.outputs.version }}-ios-unsigned.ipa` (requires sideloading) ### Features diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index e1dd5970..93fc0cb7 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -29,13 +29,34 @@ android { versionCode = flutter.versionCode versionName = flutter.versionName multiDexEnabled = true + + // Only include arm64-v8a for smaller APK (most modern devices) + // Remove this line if you need to support older 32-bit devices + ndk { + abiFilters += listOf("arm64-v8a", "armeabi-v7a") + } } buildTypes { release { signingConfig = signingConfigs.getByName("debug") - isMinifyEnabled = false - isShrinkResources = false + // Enable code shrinking and resource shrinking + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + // Split APKs by ABI for smaller individual downloads + splits { + abi { + isEnable = true + reset() + include("arm64-v8a", "armeabi-v7a") + isUniversalApk = true // Also generate universal APK } } } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 00000000..d6c8aaf1 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,33 @@ +# Flutter specific rules +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } +-keep class io.flutter.view.** { *; } +-keep class io.flutter.** { *; } +-keep class io.flutter.plugins.** { *; } + +# Go backend (gobackend.aar) +-keep class gobackend.** { *; } +-keep class go.** { *; } + +# FFmpeg Kit +-keep class com.arthenica.ffmpegkit.** { *; } +-keep class com.arthenica.smartexception.** { *; } + +# Keep native methods +-keepclasseswithmembernames class * { + native ; +} + +# Kotlin coroutines +-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} +-keepclassmembers class kotlinx.coroutines.** { + volatile ; +} + +# Prevent R8 from removing metadata +-keepattributes *Annotation* +-keepattributes SourceFile,LineNumberTable +-keepattributes Signature +-keepattributes Exceptions diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index bc91ab6b..38b20300 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart'; -import 'package:ffmpeg_kit_flutter_new/return_code.dart'; +import 'package:ffmpeg_kit_flutter_new_audio/ffmpeg_kit.dart'; +import 'package:ffmpeg_kit_flutter_new_audio/return_code.dart'; import 'package:spotiflac_android/models/download_item.dart'; import 'package:spotiflac_android/models/settings.dart'; import 'package:spotiflac_android/models/track.dart'; diff --git a/lib/services/ffmpeg_service.dart b/lib/services/ffmpeg_service.dart index 043aa3d5..fcad30f8 100644 --- a/lib/services/ffmpeg_service.dart +++ b/lib/services/ffmpeg_service.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart'; -import 'package:ffmpeg_kit_flutter_new/return_code.dart'; +import 'package:ffmpeg_kit_flutter_new_audio/ffmpeg_kit.dart'; +import 'package:ffmpeg_kit_flutter_new_audio/return_code.dart'; /// FFmpeg service for audio conversion and remuxing class FFmpegService { diff --git a/pubspec.lock b/pubspec.lock index 012e56ea..7937d2f2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -313,14 +313,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - ffmpeg_kit_flutter_new: + ffmpeg_kit_flutter_new_audio: dependency: "direct main" description: - name: ffmpeg_kit_flutter_new - sha256: d127635f27e93a7f21f0a14ce0a1a148e80919c402dac4a2118d73bfb17ce841 + name: ffmpeg_kit_flutter_new_audio + sha256: "0a698b46cd163c8e9917af75325c84d27871a2a8b2c37de3b40486cd0ab662ae" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "2.0.0" ffmpeg_kit_flutter_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cc8c7dfd..32f31427 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,8 +48,8 @@ dependencies: device_info_plus: ^12.3.0 share_plus: ^10.1.4 - # FFmpeg for audio conversion - ffmpeg_kit_flutter_new: ^4.1.0 + # FFmpeg for audio conversion (audio-only version - much smaller) + ffmpeg_kit_flutter_new_audio: ^2.0.0 open_filex: ^4.7.0 dev_dependencies: