diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 16a47b9..1c41aa4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,10 @@ jobs: - name: Decode google-services.json env: GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }} - run: echo $GOOGLE_SERVICES > app/src/debug/google-services.json + GOOGLE_SERVICES_DEBUG: ${{ secrets.GOOGLE_SERVICES_DEBUG }} + run: | + echo $GOOGLE_SERVICES > app/google-services.json + echo $GOOGLE_SERVICES_DEBUG > app/src/debug/google-services.json - name: Build debug APK and run jvm tests env: diff --git a/.github/workflows/prod-release.yml b/.github/workflows/prod-release.yml index 9ccd0a8..cec2d8f 100644 --- a/.github/workflows/prod-release.yml +++ b/.github/workflows/prod-release.yml @@ -34,6 +34,17 @@ jobs: echo $KEYSTORE_PROPERTIES | base64 --decode > keystore.properties echo $KEYSTORE_FILE | base64 --decode > Keystore.p12 + - name: Create debug directory + run: mkdir -p app/src/debug + + - name: Decode google-services.json + env: + GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }} + GOOGLE_SERVICES_DEBUG: ${{ secrets.GOOGLE_SERVICES_DEBUG }} + run: | + echo $GOOGLE_SERVICES > app/google-services.json + echo $GOOGLE_SERVICES_DEBUG > app/src/debug/google-services.json + - name: Set up Ruby uses: ruby/setup-ruby@v1 with: diff --git a/.gitignore b/.gitignore index 8fac899..348ecaf 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ keystore.properties Keystore.p12 app/release news-feed-play-config.json +google-services.json diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 92d5794..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,166 +0,0 @@ -plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.kotlin.parcelize) - alias(libs.plugins.androidx.baselineprofile) - alias(libs.plugins.detekt) -} - -def final debugAppSuffix = ".debug" - -def localPropertiesFile = rootProject.file('local.properties') -Properties localProperties = new Properties() -def newsFeedApiKey = "" - -if (localPropertiesFile.exists()) { - localProperties.load(new FileInputStream(localPropertiesFile)) - newsFeedApiKey = localProperties['NEWS_FEED_API_KEY'] ?: "\"${System.getenv('NEWS_FEED_API_KEY')}\"" -} else { - newsFeedApiKey = "\"${System.getenv('NEWS_FEED_API_KEY')}\"" -} - -def keystorePropertiesFile = rootProject.file('keystore.properties') -def keystoreProperties = new Properties() -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(keystorePropertiesFile.newDataInputStream()) -} - -android { - experimentalProperties["android.experimental.art-profile-r8-rewriting"] = true - experimentalProperties["android.experimental.r8.dex-startup-optimization"] = true - signingConfigs { - release { - release { - storeFile file('../Keystore.p12') - storePassword keystoreProperties['storePassword'] - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - } - } - } - compileSdk 34 - - defaultConfig { - applicationId "com.rob729.newsfeed" - minSdk 24 - targetSdk 34 - versionCode 1 - versionName "1.0.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary true - } - - buildConfigField("String", "NEWS_FEED_API_KEY", newsFeedApiKey) - - ksp { - arg("room.schemaLocation", "$projectDir/schemas") - } - } - - buildTypes { - debug { - applicationIdSuffix = debugAppSuffix - } - release { - shrinkResources true - minifyEnabled true - debuggable false - signingConfig signingConfigs.release - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - benchmark { - initWith(release) - signingConfig = signingConfigs.getByName("debug") - proguardFiles("benchmark-rules.pro") - matchingFallbacks = ['release'] - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - kotlin { - jvmToolchain(17) - } - buildFeatures { - compose true - buildConfig true - } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.7" - } - packagingOptions { - resources { - excludes += '/META-INF/{AL2.0,LGPL2.1}' - } - } - namespace 'com.rob729.newsfeed' -} - -baselineProfile { - dexLayoutOptimization(true) - baselineProfileRulesRewrite(true) - baselineProfileOutputDir = "../../src/main/baselineProfiles" -} - -dependencies { - - implementation libs.androidx.core.ktx - implementation platform(libs.compose.bom) - implementation libs.compose.ui - implementation libs.androidx.compose.ui.tooling.preview - implementation libs.androidx.lifecycle.runtime.ktx - implementation libs.androidx.activity.compose - testImplementation libs.junit - - androidTestImplementation libs.androidx.junit - androidTestImplementation libs.androidx.espresso.core - baselineProfile project(':baselineprofile') - debugImplementation libs.androidx.compose.ui.tooling - debugImplementation libs.androidx.compose.ui.test.manifest - implementation libs.androidx.navigation.compose - implementation libs.androidx.compose.material.icons.extended - implementation libs.androidx.compose.material3 - implementation libs.androidx.lifecycle.viewmodel.compose - implementation libs.androidx.constraintlayout.compose - - implementation libs.androidx.work.runtime.ktx - implementation libs.androidx.browser - implementation libs.androidx.profileinstaller - implementation libs.androidx.startup.runtime - implementation libs.androidx.datastore.preferences - implementation libs.androidx.runtime.tracing - - implementation libs.coil - implementation libs.coil.compose - - implementation libs.moshi.kotlin - ksp libs.moshi.codegen - - implementation libs.orbit.viewmodel - implementation libs.orbit.compose - - implementation libs.retrofit - implementation libs.converter.moshi - implementation libs.logging.interceptor - - implementation libs.kotlinx.datetime - - implementation libs.androidx.room.runtime - ksp libs.androidx.room.compiler - implementation libs.androidx.room.ktx - - implementation libs.koin.android - implementation libs.koin.androidx.compose - - debugImplementation libs.pluto - benchmarkImplementation libs.pluto.no.op - releaseImplementation libs.pluto.no.op - - debugImplementation libs.bundle.core - benchmarkImplementation libs.bundle.core.no.op - releaseImplementation libs.bundle.core.no.op -} diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..1b8d477 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,174 @@ +@file:Suppress("UnstableApiUsage") + +import java.util.Properties + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.androidx.baselineprofile) + alias(libs.plugins.detekt) + if (File("app/google-services.json").exists() && File("app/src/debug/google-services.json").exists()) { + alias(libs.plugins.google.services) + alias(libs.plugins.firebase.crashlytics) + } +} + +val debugAppSuffix = ".debug" + +val localPropertiesFile = rootProject.file("local.properties") +val localProperties = Properties() +var newsFeedApiKey = if (localPropertiesFile.exists()) { + localProperties.load(localPropertiesFile.inputStream()) + localProperties.getProperty("NEWS_FEED_API_KEY") ?: "\"${System.getenv("NEWS_FEED_API_KEY")}\"" +} else { + "\"${System.getenv("NEWS_FEED_API_KEY")}\"" +} + +val keystorePropertiesFile = rootProject.file("keystore.properties") +val keystoreProperties = Properties() +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(keystorePropertiesFile.inputStream()) +} + +android { + experimentalProperties["android.experimental.art-profile-r8-rewriting"] = true + experimentalProperties["android.experimental.r8.dex-startup-optimization"] = true + signingConfigs { + create("release") { + storeFile = file("../Keystore.p12") + storePassword = keystoreProperties["storePassword"] as? String + keyAlias = keystoreProperties["keyAlias"] as? String + keyPassword = keystoreProperties["keyPassword"] as? String + } + } + compileSdk = 34 + + defaultConfig { + applicationId = "com.rob729.newsfeed" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true + + buildConfigField("String", "NEWS_FEED_API_KEY", newsFeedApiKey) + + ksp { + arg("room.schemaLocation", "$projectDir/schemas") + } + } + + buildTypes { + getByName("debug") { + applicationIdSuffix = debugAppSuffix + } + getByName("release") { + isShrinkResources = true + isMinifyEnabled = true + isDebuggable = false + + signingConfig = signingConfigs.getByName("release") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + create("benchmark") { + initWith(getByName("release")) + signingConfig = signingConfigs.getByName("debug") + proguardFiles("benchmark-rules.pro") + matchingFallbacks += listOf("release") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + buildConfig = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.7" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + namespace = "com.rob729.newsfeed" +} + +baselineProfile { + dexLayoutOptimization = true + baselineProfileRulesRewrite = true + baselineProfileOutputDir = "../../src/main/baselineProfiles" +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + testImplementation(libs.junit) + + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + "baselineProfile"(project(":baselineprofile")) + debugImplementation(libs.androidx.compose.ui.tooling) + debugImplementation(libs.androidx.compose.ui.test.manifest) + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.compose.material.icons.extended) + implementation(libs.androidx.compose.material3) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.constraintlayout.compose) + + implementation(libs.androidx.work.runtime.ktx) + implementation(libs.androidx.browser) + implementation(libs.androidx.profileinstaller) + implementation(libs.androidx.startup.runtime) + implementation(libs.androidx.datastore.preferences) + implementation(libs.androidx.runtime.tracing) + + implementation(libs.coil) + implementation(libs.coil.compose) + + implementation(libs.moshi.kotlin) + ksp(libs.moshi.codegen) + + implementation(libs.orbit.viewmodel) + implementation(libs.orbit.compose) + + implementation(libs.retrofit) + implementation(libs.converter.moshi) + implementation(libs.logging.interceptor) + + implementation(libs.kotlinx.datetime) + + implementation(libs.androidx.room.runtime) + ksp(libs.androidx.room.compiler) + implementation(libs.androidx.room.ktx) + + implementation(libs.koin.android) + implementation(libs.koin.androidx.compose) + + debugImplementation(libs.pluto) + releaseImplementation(libs.pluto.no.op) + + debugImplementation(libs.bundle.core) + releaseImplementation(libs.bundle.core.no.op) + + if (File("${project.projectDir}/google-services.json").exists() && File("${project.projectDir}/src/debug/google-services.json").exists()) { + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.analytics) + implementation(libs.firebase.crashlytics) + } +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e39deed..eeb9117 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html @@ -31,4 +31,4 @@ # With R8 full mode generic signatures are stripped for classes that are not # kept. Suspend functions are wrapped in continuations where the type argument # is used. - -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation \ No newline at end of file + -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation diff --git a/baselineprofile/build.gradle b/baselineprofile/build.gradle.kts similarity index 59% rename from baselineprofile/build.gradle rename to baselineprofile/build.gradle.kts index 624e844..8d640c3 100644 --- a/baselineprofile/build.gradle +++ b/baselineprofile/build.gradle.kts @@ -5,8 +5,8 @@ plugins { } android { - namespace 'com.rob729.baselineprofile' - compileSdk 34 + namespace = "com.rob729.baselineprofile" + compileSdk = 34 compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 @@ -18,10 +18,10 @@ android { } defaultConfig { - minSdk 28 - targetSdk 34 + minSdk = 28 + targetSdk = 34 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } targetProjectPath = ":app" @@ -35,9 +35,9 @@ baselineProfile { } dependencies { - implementation libs.androidx.junit - implementation libs.androidx.espresso.core - implementation libs.androidx.uiautomator - implementation libs.androidx.benchmark.macro.junit4 - implementation libs.androidx.rules -} \ No newline at end of file + implementation(libs.androidx.junit) + implementation(libs.androidx.espresso.core) + implementation(libs.androidx.uiautomator) + implementation(libs.androidx.benchmark.macro.junit4) + implementation(libs.androidx.rules) +} diff --git a/build.gradle b/build.gradle.kts similarity index 66% rename from build.gradle rename to build.gradle.kts index 936e25a..0c7c035 100644 --- a/build.gradle +++ b/build.gradle.kts @@ -7,6 +7,10 @@ plugins { alias(libs.plugins.androidx.baselineprofile) apply false alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.detekt) + if (File("app/google-services.json").exists() && File("app/src/debug/google-services.json").exists()) { + alias(libs.plugins.google.services) apply false + alias(libs.plugins.firebase.crashlytics) apply false + } } dependencies { @@ -20,6 +24,6 @@ detekt { config.setFrom("$projectDir/config/detekt/detekt.yml") } -task clean(type: Delete) { - delete rootProject.buildDir +tasks.register("clean", Delete::class) { + delete(rootProject.buildDir) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7cf43bb..ca225bc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,8 @@ coreKtx = "1.12.0" datastorePreferences = "1.0.0" detekt = "1.23.4" espressoCore = "3.5.1" +firebaseCrashlytics = "2.9.9" +googleServices = "4.4.0" junit = "4.13.2" junitVersion = "1.1.5" koinAndroid = "3.5.0" @@ -65,6 +67,9 @@ compose-bom = "androidx.compose:compose-bom:2023.10.01" compose-ui = { module = "androidx.compose.ui:ui" } converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" } detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } +firebase-bom = "com.google.firebase:firebase-bom:32.7.0" +firebase-analytics = { module = "com.google.firebase:firebase-analytics" } +firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } junit = { module = "junit:junit", version.ref = "junit" } koin-android = { module = "io.insert-koin:koin-android", version.ref = "koinAndroid" } koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koinAndroid" } @@ -87,3 +92,5 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } androidx-baselineprofile = { id = "androidx.baselineprofile", version.ref = "baselineProfile" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlytics"} +google-services = { id = 'com.google.gms.google-services', version.ref = "googleServices"} diff --git a/settings.gradle b/settings.gradle.kts similarity index 87% rename from settings.gradle rename to settings.gradle.kts index 1e213cc..46af24e 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -13,5 +13,5 @@ dependencyResolutionManagement { } } rootProject.name = "News Feed" -include ':app' -include ':baselineprofile' +include(":app") +include(":baselineprofile")