R8 (minify + resource-shrink) flipped on for BOTH debug AND release
variants — we publish the debug APK to fdroid (per existing
pipeline), and the audit-flagged Log.d strip discipline required R8
to actually run on the variant we ship.
New strawApp/proguard-rules.pro covers:
* UniFFI bindings (uniffi.strawcore.*) — reflective FFI dispatch
from Rust side, must survive minification
* JNA — Library subclasses reflectively loaded by name
* kotlinx-serialization @Serializable — generated $$serializer
companions, kept via both the package-anchored rule and the
annotation-wildcard rule for belt + suspenders
* Media3 session Parcelables (cross-process via Binder)
* Compose runtime + Strawcore exception hierarchy
Surface-handoff polish on inline ↔ fullscreen transitions:
setKeepContentOnPlayerReset(true) on both PlayerViews (inline in
VideoDetail + fullscreen Player). When the detaching view's player
is nulled on dispose, it holds the last rendered frame instead of
flashing black. The receiving view's surface takeover then renders
the next frame without the ~1-frame black gap. Round-4 audit
HIGH-5 was the closest writeup.
Expected APK-size win from R8: ~30-40%. Need real-device
verification post-install — the keep rules are best-effort and a
missing rule manifests as runtime ClassNotFoundException or
silently-broken kotlinx-serialization decoding.
223 lines
8.3 KiB
Kotlin
223 lines
8.3 KiB
Kotlin
/*
|
|
* SPDX-FileCopyrightText: 2026 Sulkta-Coop
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* :strawApp — thin Android application shell. Day-2: pulls NewPipeExtractor,
|
|
* Media3, Ktor-style HTTP-via-OkHttp + kotlinx-serialization JSON for the
|
|
* search → detail → player → SponsorBlock + RYD flow. We keep our deps in
|
|
* this module, NOT in the shared libs.versions.toml, so upstream NewPipe
|
|
* stays cleanly mergeable.
|
|
*/
|
|
|
|
import com.android.build.api.dsl.ApplicationExtension
|
|
|
|
plugins {
|
|
alias(libs.plugins.android.application)
|
|
alias(libs.plugins.jetbrains.kotlin.compose)
|
|
alias(libs.plugins.jetbrains.kotlinx.serialization)
|
|
}
|
|
|
|
kotlin {
|
|
jvmToolchain(21)
|
|
}
|
|
|
|
configure<ApplicationExtension> {
|
|
compileSdk {
|
|
version = release(NEWPIPE_VERSION_SDK_COMPILE_MAJOR) {
|
|
minorApiLevel = NEWPIPE_VERSION_SDK_COMPILE_MINOR
|
|
}
|
|
}
|
|
namespace = STRAW_APPLICATION_ID
|
|
|
|
defaultConfig {
|
|
applicationId = STRAW_APPLICATION_ID
|
|
minSdk { version = release(24) }
|
|
targetSdk { version = release(NEWPIPE_VERSION_SDK_TARGET) }
|
|
versionCode = STRAW_VERSION_CODE
|
|
versionName = STRAW_VERSION_NAME
|
|
resValue("string", "app_name", "Straw")
|
|
}
|
|
|
|
buildTypes {
|
|
// R8 enabled on BOTH variants — we publish the debug APK to
|
|
// fdroid (com.sulkta.straw.debug) per the existing pipeline,
|
|
// and audit-flagged Log.d strips depended on R8 actually
|
|
// running on the variant we ship. Keep rules in
|
|
// strawApp/proguard-rules.pro cover UniFFI + JNA +
|
|
// kotlinx-serialization companions.
|
|
debug {
|
|
isDebuggable = true
|
|
applicationIdSuffix = ".debug"
|
|
resValue("string", "app_name", "Straw debug")
|
|
isMinifyEnabled = true
|
|
isShrinkResources = true
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro",
|
|
)
|
|
}
|
|
release {
|
|
isMinifyEnabled = true
|
|
isShrinkResources = true
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro",
|
|
)
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_21
|
|
targetCompatibility = JavaVersion.VERSION_21
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
buildConfig = true
|
|
resValues = true
|
|
}
|
|
|
|
packaging {
|
|
resources {
|
|
excludes += setOf(
|
|
"META-INF/README.md",
|
|
"META-INF/CHANGES",
|
|
"META-INF/COPYRIGHT",
|
|
"META-INF/INDEX.LIST",
|
|
"META-INF/io.netty.versions.properties",
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
// Compose + AndroidX core
|
|
implementation(libs.androidx.activity)
|
|
implementation(libs.androidx.core)
|
|
implementation(libs.jetbrains.compose.runtime)
|
|
implementation(libs.jetbrains.compose.foundation)
|
|
implementation(libs.jetbrains.compose.material3)
|
|
implementation(libs.jetbrains.compose.ui)
|
|
implementation("androidx.compose.material:material-icons-extended:1.7.5")
|
|
|
|
// Lifecycle + ViewModel for Compose
|
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
|
|
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.10.0")
|
|
|
|
// Coroutines
|
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0")
|
|
|
|
// Image loading
|
|
implementation(libs.coil.compose)
|
|
implementation(libs.coil.network.okhttp)
|
|
|
|
// NewPipeExtractor (JVM/Android-only) + its OkHttp dep
|
|
// libs.newpipe.extractor — REMOVED in Path C-6. Extractor is now strawcore
|
|
// (Rust + rustypipe via UniFFI). See rust/strawcore/ + the cargoBuild +
|
|
// uniffiBindgen Gradle tasks below.
|
|
implementation(libs.squareup.okhttp)
|
|
|
|
// JSON for SponsorBlock + Return YouTube Dislike clients
|
|
implementation(libs.kotlinx.serialization.json)
|
|
|
|
// Media3 ExoPlayer
|
|
implementation("androidx.media3:media3-exoplayer:1.4.1")
|
|
implementation("androidx.media3:media3-exoplayer-dash:1.4.1")
|
|
implementation("androidx.media3:media3-exoplayer-hls:1.4.1")
|
|
implementation("androidx.media3:media3-ui:1.4.1")
|
|
// Media3 session — MediaSessionService for background audio + lock-screen controls.
|
|
implementation("androidx.media3:media3-session:1.4.1")
|
|
// Guava ListenableFuture support for awaiting MediaController connect.
|
|
implementation("androidx.concurrent:concurrent-futures-ktx:1.2.0")
|
|
|
|
// strawcore — Rust YouTube extractor via UniFFI/JNA. Built by the
|
|
// cargoBuild + uniffiBindgen tasks below; phase U-2+ exposes search /
|
|
// streamInfo / channelInfo to replace NewPipeExtractor.
|
|
implementation("net.java.dev.jna:jna:5.14.0@aar")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Phase U-1 / Path-C-2 — Rust core build glue.
|
|
//
|
|
// Two tasks chain into the Android build:
|
|
// cargoBuild — cross-compiles rust/strawcore for the four Android ABIs
|
|
// via cargo-ndk and drops the .so files in strawApp/src/main/jniLibs/.
|
|
// uniffiBindgen — generates the Kotlin bindings from the freshly-built lib
|
|
// into strawApp/src/main/java/uniffi/strawcore/.
|
|
//
|
|
// Both depend on:
|
|
// - cargo + rustup with the four Android targets installed
|
|
// - cargo-ndk on PATH
|
|
// - ANDROID_NDK_HOME pointing at an NDK with the right toolchains
|
|
// All of that lives in the crafting-table container.
|
|
// =============================================================================
|
|
|
|
val rustRoot = file("../rust").absolutePath
|
|
val jniLibsDir = file("src/main/jniLibs").absolutePath
|
|
val bindingsDir = file("src/main/java").absolutePath
|
|
|
|
val cargoHome: String = System.getenv("CARGO_HOME") ?: "/caches/cargo"
|
|
val cargoBin: String = "$cargoHome/bin/cargo"
|
|
val ndkHome: String = System.getenv("ANDROID_NDK_HOME")
|
|
?: System.getenv("ANDROID_NDK_ROOT")
|
|
?: "/caches/android-sdk/ndk/27.2.12479018"
|
|
// Honor CARGO_TARGET_DIR if set (we redirect it to /caches on crafting-table
|
|
// because the container's writable rootfs hits 100% before the cross-compile
|
|
// for 4 ABIs finishes). Falls back to the default `<workspace>/target`.
|
|
val cargoTargetDir: String = System.getenv("CARGO_TARGET_DIR")
|
|
?: "$rustRoot/target"
|
|
|
|
val cargoBuild by tasks.registering(Exec::class) {
|
|
group = "rust"
|
|
description = "Cross-compile strawcore for all Android ABIs via cargo-ndk."
|
|
workingDir = file(rustRoot)
|
|
environment("ANDROID_NDK_HOME", ndkHome)
|
|
environment("PATH", "$cargoHome/bin:${System.getenv("PATH") ?: ""}")
|
|
commandLine = listOf(
|
|
cargoBin, "ndk",
|
|
"-t", "arm64-v8a",
|
|
"-t", "armeabi-v7a",
|
|
"-t", "x86",
|
|
"-t", "x86_64",
|
|
"-o", jniLibsDir,
|
|
"build", "--release", "-p", "strawcore",
|
|
)
|
|
standardOutput = System.out
|
|
errorOutput = System.err
|
|
}
|
|
|
|
val cargoBuildHost by tasks.registering(Exec::class) {
|
|
group = "rust"
|
|
description = "Build host-arch debug strawcore so bindgen can read its UniFFI metadata."
|
|
workingDir = file(rustRoot)
|
|
environment("PATH", "$cargoHome/bin:${System.getenv("PATH") ?: ""}")
|
|
commandLine = listOf(cargoBin, "build", "-p", "strawcore")
|
|
standardOutput = System.out
|
|
errorOutput = System.err
|
|
}
|
|
|
|
val uniffiBindgen by tasks.registering(Exec::class) {
|
|
group = "rust"
|
|
description = "Generate Kotlin bindings for strawcore via uniffi-bindgen."
|
|
dependsOn(cargoBuildHost)
|
|
workingDir = file(rustRoot)
|
|
environment("PATH", "$cargoHome/bin:${System.getenv("PATH") ?: ""}")
|
|
commandLine = listOf(
|
|
cargoBin, "run", "--quiet", "--bin", "uniffi-bindgen", "--",
|
|
"generate",
|
|
"--library", "$cargoTargetDir/debug/libstrawcore.so",
|
|
"--crate", "strawcore",
|
|
"--language", "kotlin",
|
|
"--no-format",
|
|
"--out-dir", bindingsDir,
|
|
)
|
|
standardOutput = System.out
|
|
errorOutput = System.err
|
|
}
|
|
|
|
// Make sure Android's JNI-libs merge picks up the freshly built .so files,
|
|
// and Kotlin compilation can resolve the generated bindings.
|
|
tasks.matching { it.name.startsWith("merge") && it.name.endsWith("JniLibFolders") }
|
|
.configureEach { dependsOn(cargoBuild) }
|
|
tasks.matching { it.name.startsWith("compile") && it.name.endsWith("Kotlin") }
|
|
.configureEach { dependsOn(uniffiBindgen) }
|