Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
isMinifyEnabled = true
isShrinkResources = true

proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}

Expand Down
270 changes: 172 additions & 98 deletions composeApp/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,124 +1,198 @@
# === CRITICAL: Keep Everything for Networking ===
-keeppackagenames io.ktor.**
-keeppackagenames okhttp3.**
-keeppackagenames okio.**

# Kotlin
-keep class kotlin.** { *; }
-keep class kotlinx.** { *; }
-keepclassmembers class kotlin.** { *; }

# Coroutines
-keep class kotlinx.coroutines.** { *; }
# ============================================================================
# ProGuard / R8 Rules for GitHub Store (KMP + Compose Multiplatform)
# ============================================================================
# Used with: proguard-android-optimize.txt (enables optimization passes)
# ============================================================================

# ── General Attributes ──────────────────────────────────────────────────────
-keepattributes Signature
-keepattributes *Annotation*
-keepattributes InnerClasses,EnclosingMethod
-keepattributes SourceFile,LineNumberTable
-keepattributes Exceptions

# ── Kotlin Core ─────────────────────────────────────────────────────────────
# Keep Kotlin metadata for reflection used by serialization & Koin
-keep class kotlin.Metadata { *; }
-keep class kotlin.reflect.jvm.internal.** { *; }
-dontwarn kotlin.**
-dontwarn kotlinx.**

# ── Kotlin Coroutines ──────────────────────────────────────────────────────
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepclassmembernames class kotlinx.** { volatile <fields>; }
-dontwarn kotlinx.coroutines.**

# === Ktor - Keep EVERYTHING ===
-keep class io.ktor.** { *; }
-keep interface io.ktor.** { *; }
-keepclassmembers class io.ktor.** { *; }
# ── Kotlinx Serialization ──────────────────────────────────────────────────
# Serialization engine internals
-keep class kotlinx.serialization.** { *; }
-keepclassmembers class kotlinx.serialization.json.** { *** Companion; }
-dontnote kotlinx.serialization.**

# Generated serializers for ALL @Serializable classes
-keep class **$$serializer { *; }
-keepclassmembers @kotlinx.serialization.Serializable class ** {
*** Companion;
*** INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}

# App @Serializable classes (DTOs, models, navigation routes) across all packages
-keep @kotlinx.serialization.Serializable class zed.rainxch.** { *; }
-keep,includedescriptorclasses class zed.rainxch.**$$serializer { *; }
-keepclassmembers @kotlinx.serialization.Serializable class zed.rainxch.** {
*** Companion;
}

# ── Navigation Routes ──────────────────────────────────────────────────────
# Type-safe navigation requires these classes to survive R8
-keep class zed.rainxch.githubstore.app.navigation.GithubStoreGraph { *; }
-keep class zed.rainxch.githubstore.app.navigation.GithubStoreGraph$* { *; }

# ── Network DTOs – Core Module ─────────────────────────────────────────────
-keep class zed.rainxch.core.data.dto.** { *; }

# ── Network DTOs – Feature Modules ─────────────────────────────────────────
-keep class zed.rainxch.search.data.dto.** { *; }
-keep class zed.rainxch.devprofile.data.dto.** { *; }
-keep class zed.rainxch.home.data.dto.** { *; }

# ── Domain Models ──────────────────────────────────────────────────────────
-keep class zed.rainxch.core.domain.model.GithubRepoSummary { *; }
-keep class zed.rainxch.core.domain.model.GithubUser { *; }

# Keep enums used by Room TypeConverters and serialization
-keep class zed.rainxch.core.domain.model.InstallSource { *; }
-keep class zed.rainxch.core.domain.model.AppTheme { *; }
-keep class zed.rainxch.core.domain.model.FontTheme { *; }
-keep class zed.rainxch.core.domain.model.Platform { *; }
-keep class zed.rainxch.core.domain.model.SystemArchitecture { *; }
-keep class zed.rainxch.core.domain.model.PackageChangeType { *; }

# ── Room Database ──────────────────────────────────────────────────────────
# Database class and generated implementation
-keep class zed.rainxch.core.data.local.db.AppDatabase { *; }
-keep class zed.rainxch.core.data.local.db.AppDatabase_Impl { *; }

# Entities
-keep class zed.rainxch.core.data.local.db.entities.** { *; }

# DAOs
-keep interface zed.rainxch.core.data.local.db.dao.** { *; }
-keep class zed.rainxch.core.data.local.db.dao.** { *; }

# Room runtime
-keep class androidx.room.** { *; }
-dontwarn androidx.room.**

# ── Ktor ───────────────────────────────────────────────────────────────────
# Engine discovery, plugin system, and content negotiation use reflection
-keep class io.ktor.client.engine.** { *; }
-keep class io.ktor.client.plugins.** { *; }
-keep class io.ktor.serialization.** { *; }
-keep class io.ktor.utils.io.** { *; }
-keep class io.ktor.http.** { *; }
-keepnames class io.ktor.** { *; }
-dontwarn io.ktor.**

# Ktor Debug
-dontwarn java.lang.management.**

# === OkHttp - Keep EVERYTHING ===
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-keepclassmembers class okhttp3.** { *; }
-keepnames class okhttp3.** { *; }
# ── OkHttp (Ktor engine) ──────────────────────────────────────────────────
-keep class okhttp3.internal.platform.** { *; }
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-dontwarn okhttp3.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**

# === Okio - Keep EVERYTHING ===
-keep class okio.** { *; }
-keepclassmembers class okio.** { *; }
-keepnames class okio.** { *; }
# ── Okio ───────────────────────────────────────────────────────────────────
-dontwarn okio.**

# === Network Stack - Keep EVERYTHING ===
-keep class java.net.** { *; }
-keep class javax.net.** { *; }
-keep class sun.security.ssl.** { *; }
-keepclassmembers class java.net.** { *; }
-keepclassmembers class javax.net.** { *; }

# DNS Resolution
-keep class java.net.InetAddress { *; }
-keep class java.net.Inet4Address { *; }
-keep class java.net.Inet6Address { *; }
-keep class java.net.InetSocketAddress { *; }

# SSL/TLS
-keep class javax.net.ssl.** { *; }
# ── SSL/TLS ────────────────────────────────────────────────────────────────
-keep class org.conscrypt.** { *; }
-dontwarn org.conscrypt.**

# === Kotlinx Serialization ===
-keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.**
-keep,includedescriptorclasses class zed.rainxch.githubstore.**$$serializer { *; }
-keep @kotlinx.serialization.Serializable class zed.rainxch.githubstore.** { *; }
-keepclassmembers @kotlinx.serialization.Serializable class zed.rainxch.githubstore.** {
*** Companion;
}

# Keep your models
-keep class zed.rainxch.githubstore.core.domain.model.** { *; }
# ── Koin DI ────────────────────────────────────────────────────────────────
# Koin uses reflection for constructor injection
-keep class org.koin.** { *; }
-keep interface org.koin.** { *; }
-dontwarn org.koin.**

# Keep ViewModels so Koin can instantiate them
-keep class zed.rainxch.**.presentation.**ViewModel { *; }
-keep class zed.rainxch.**.presentation.**ViewModel$* { *; }

# ── Compose / AndroidX ────────────────────────────────────────────────────
# Compose runtime and navigation (most rules come bundled with the library)
-dontwarn androidx.compose.**
-dontwarn androidx.lifecycle.**
Comment on lines 124 to 127
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Over-broad keeps for Compose/AndroidX will significantly bloat the APK.

-keep class androidx.compose.** { *; } prevents R8 from shrinking any Compose class — runtime, foundation, material, UI, animation, etc. The same applies to androidx.navigation.** and androidx.lifecycle.**. These are some of the largest dependencies in a Compose app, and they already ship consumer ProGuard rules in their AARs that handle the necessary reflection/keep cases.

Since the PR is enabling R8 minification, these blanket keeps directly undermine the size savings. In projects I've seen, removing the Compose blanket keep alone can save 2–5 MB from the release APK.

Recommendation: Remove these three blanket keeps and rely on the libraries' bundled consumer rules. If you hit a specific runtime issue (e.g., a crash from a stripped class), add a targeted keep for just that class.

♻️ Proposed change
 # ── Compose / AndroidX ────────────────────────────────────────────────────
 # Compose runtime and navigation (most rules come bundled with the library)
--keep class androidx.compose.** { *; }
 -dontwarn androidx.compose.**
--keep class androidx.navigation.** { *; }
--keep class androidx.lifecycle.** { *; }
 -dontwarn androidx.lifecycle.**

If specific classes cause runtime issues after removal, add targeted rules like:

# Example: keep only if a specific reflection-based crash occurs
-keep class androidx.lifecycle.ViewModelProvider$* { *; }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# ── Compose / AndroidX ────────────────────────────────────────────────────
# Compose runtime and navigation (most rules come bundled with the library)
-keep class androidx.compose.** { *; }
-dontwarn androidx.compose.**
-keep class androidx.navigation.** { *; }
-keep class androidx.lifecycle.** { *; }
-dontwarn androidx.lifecycle.**
# ── Compose / AndroidX ────────────────────────────────────────────────────
# Compose runtime and navigation (most rules come bundled with the library)
-dontwarn androidx.compose.**
-dontwarn androidx.lifecycle.**
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/proguard-rules.pro` around lines 124 - 130, The proguard rules
currently include overly broad keeps (-keep class androidx.compose.** { *; },
-keep class androidx.navigation.** { *; }, -keep class androidx.lifecycle.** {
*; }) which prevent R8 from shrinking those libraries and will bloat the APK;
remove these blanket -keep entries and rely on the libraries' consumer
ProGuard/R8 rules instead, and if you encounter a runtime crash after
minification, add a targeted keep for the specific class or symbol (e.g., keep
only androidx.lifecycle.ViewModelProvider$* or the exact class reported) rather
than restoring the broad package-wide keeps.


# ── DataStore ──────────────────────────────────────────────────────────────
-keep class androidx.datastore.** { *; }
-keepclassmembers class androidx.datastore.preferences.** { *; }
-dontwarn androidx.datastore.**

# ── Landscapist / Coil3 (Image Loading) ────────────────────────────────────
-keep class com.skydoves.landscapist.** { *; }
-keep interface com.skydoves.landscapist.** { *; }
-keep class coil3.** { *; }
-dontwarn coil3.**
-dontwarn com.skydoves.landscapist.**

# ── Multiplatform Markdown Renderer ────────────────────────────────────────
-keep class com.mikepenz.markdown.** { *; }
-keep class org.intellij.markdown.** { *; }
-dontwarn com.mikepenz.markdown.**
-dontwarn org.intellij.markdown.**

# ── Kermit Logging ─────────────────────────────────────────────────────────
-keep class co.touchlab.kermit.** { *; }
-dontwarn co.touchlab.kermit.**

# ── MOKO Permissions ──────────────────────────────────────────────────────
-keep class dev.icerock.moko.permissions.** { *; }
-dontwarn dev.icerock.moko.**

# ── BuildKonfig (Generated Build Constants) ────────────────────────────────
-keep class zed.rainxch.githubstore.BuildConfig { *; }
-keep class zed.rainxch.**.BuildKonfig { *; }
-keep class **.BuildKonfig { *; }

# === AndroidX Security ===
# ── AndroidX Security / Crypto ─────────────────────────────────────────────
-keep class androidx.security.crypto.** { *; }
-keep class com.google.crypto.tink.** { *; }
-dontwarn com.google.crypto.tink.**
-dontwarn com.google.errorprone.annotations.**

# BuildConfig
-keep class zed.rainxch.githubstore.BuildConfig { *; }

# General
-keepattributes Signature
-keepattributes Exceptions
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable

# === START: Auth Fix ===
-dontoptimize
-keepattributes *Annotation*,Signature,Exception,InnerClasses,EnclosingMethod
# ── Firebase (if integrated) ──────────────────────────────────────────────
-keep class com.google.firebase.** { *; }
-dontwarn com.google.firebase.**

# Keep serialization infrastructure
-keep class kotlinx.serialization.** { *; }
-keep class **$$serializer { *; }
-keepclassmembers @kotlinx.serialization.Serializable class ** {
*** Companion;
*** INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
# ── Enum safety ────────────────────────────────────────────────────────────
# Keep all enum values and valueOf methods (used by serialization/Room)
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

# Keep Ktor plugins
-keep class io.ktor.client.plugins.** { *; }
-keep class io.ktor.serialization.** { *; }

# Keep your entire core package (narrow this down later)
-keep class zed.rainxch.githubstore.core.** { *; }
-keepclassmembers class zed.rainxch.githubstore.core.** { *; }
# === END: Auth Fix ===

-keep class zed.rainxch.githubstore.core.data.remote.dto.** { *; }
-keep class zed.rainxch.githubstore.core.domain.model.auth.** { *; }

# If your models are in different packages, list them:
-keep class zed.rainxch.githubstore.**.*DeviceStart* { *; }
-keep class zed.rainxch.githubstore.**.*DeviceToken* { *; }
-keep class zed.rainxch.githubstore.**.*AuthConfig* { *; }

# Keep the companion objects explicitly
-keepclassmembers class zed.rainxch.githubstore.**.DeviceStart {
public static ** Companion;
# ── Parcelable ─────────────────────────────────────────────────────────────
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}
-keepclassmembers class zed.rainxch.githubstore.**.DeviceTokenSuccess {
public static ** Companion;

# ── Java Serializable Compatibility ───────────────────────────────────────
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
-keepclassmembers class zed.rainxch.githubstore.**.DeviceTokenError {
public static ** Companion;
}

# ── Suppress Warnings for Missing Classes ──────────────────────────────────
-dontwarn java.lang.invoke.StringConcatFactory
-dontwarn javax.annotation.**
-dontwarn org.slf4j.**
-dontwarn org.codehaus.mojo.animal_sniffer.**
Binary file modified composeApp/release/baselineProfiles/0/composeApp-release.dm
Binary file not shown.
Binary file modified composeApp/release/baselineProfiles/1/composeApp-release.dm
Binary file not shown.
38 changes: 24 additions & 14 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
<uses-permission
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
tools:ignore="RequestInstallPackagesPolicy" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />

<application
android:name=".app.GithubStoreApp"
android:allowBackup="true"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar"
android:usesCleartextTraffic="false">
android:usesCleartextTraffic="false"
tools:targetApi="29">

<activity
android:name=".MainActivity"
Expand Down Expand Up @@ -54,17 +58,22 @@
android:scheme="githubstore" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
<data android:mimeType="text/html" />
</intent-filter>

<!-- GitHub repository links: https://github.com/{owner}/{repo} -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="github.com"
android:pathPattern="/.*/..*"
android:scheme="https" />
<data android:scheme="https" />
<data android:host="github.com" />
<data android:pathPattern="/.*/.*" />
</intent-filter>

<intent-filter android:autoVerify="true">
Expand All @@ -73,10 +82,11 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="github-store.org"
android:pathPrefix="/app/"
android:scheme="https" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="github-store.org" />
<data android:pathPrefix="/app/" />

</intent-filter>
</activity>

Expand Down
Loading