Improve Gradle Configuration (#2093)

* initial migration to gradle kotlin DSL

* initial migration to version catalog

* finalize some last testing task configuration

* delete commented code

* update detekt and test configuration

* implement gradle plugin

* gradle:
 - Removing unused properties in libs.versions.toml
 - Enabling typesafe project accessors
 - Change allUnitTest task from GradleBuild to DefaultTask

* fix always looking for signingrelease.properties

* fix logic for properties

* improve condition

* signing config release

* update configuration based on new changes

---------

Co-authored-by: Jimly Asshiddiqy <jimly.asshiddiqy@accenture.com>
This commit is contained in:
Jim 2025-01-30 17:29:19 +07:00 committed by GitHub
parent d67e63a5a0
commit 584b274d85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 981 additions and 867 deletions

View file

@ -1,382 +0,0 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'androidx.navigation.safeargs'
id 'com.google.firebase.firebase-perf'
id 'jacoco-report-aggregation'
id 'org.jetbrains.kotlin.plugin.compose'
id("kotlin-parcelize")
}
apply plugin: 'kotlin-android'
apply plugin: "realm-android"
repositories {
mavenLocal()
mavenCentral()
google()
maven { url "https://jitpack.io" }
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: '../common/libs')
//Networking
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
//REST API handling
implementation("com.squareup.retrofit2:retrofit:$retrofit_version") {
exclude module: 'okhttp'
}
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
//Dependency Injection
implementation "com.google.dagger:hilt-android:$daggerhilt_version"
kapt "com.google.dagger:hilt-compiler:$daggerhilt_version"
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
//App Compatibility and Material Design
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation 'com.google.android.material:material:1.12.0'
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
implementation "androidx.preference:preference-ktx:$preferences_version"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
//Desugaring
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation('com.jaredrummler:android-device-names:2.1.1')
// IAP Handling / Verification
implementation "com.android.billingclient:billing-ktx:7.0.0"
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1@aar'
implementation("io.coil-kt:coil-compose:$coil_version")
//Analytics
implementation "com.amplitude:analytics-android:$amplitude_version"
//Tests
testImplementation 'androidx.test:core:1.6.1'
testImplementation "io.mockk:mockk:$mockk_version"
testImplementation "io.mockk:mockk-android:$mockk_version"
testImplementation "io.kotest:kotest-runner-junit5:$kotest_version"
testImplementation "io.kotest:kotest-assertions-core:$kotest_version"
testImplementation "io.kotest:kotest-framework-datatest:$kotest_version"
androidTestImplementation ('com.kaspersky.android-components:kaspresso:1.5.1') {
exclude module: "protobuf-lite"
}
androidTestImplementation "com.kaspersky.android-components:kaspresso-compose-support:1.5.1"
androidTestImplementation 'androidx.test:runner:1.6.2'
androidTestImplementation 'androidx.test:rules:1.6.1'
debugImplementation 'androidx.fragment:fragment-testing:1.8.2'
androidTestImplementation 'androidx.test:core-ktx:1.6.1'
debugImplementation "androidx.test:monitor:1.7.2"
androidTestImplementation 'androidx.test.ext:junit-ktx:1.2.1'
androidTestImplementation "io.mockk:mockk-android:$mockk_version"
androidTestImplementation "io.mockk:mockk-agent:$mockk_version"
androidTestImplementation "io.kotest:kotest-assertions-core:$kotest_version"
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
androidTestUtil("androidx.test:orchestrator:1.5.0")
implementation 'com.facebook.shimmer:shimmer:0.5.0'
//Leak Detection
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
//Push Notifications
implementation platform("com.google.firebase:firebase-bom:$firebase_bom")
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-crashlytics-ktx'
implementation 'com.google.firebase:firebase-messaging-ktx'
implementation 'com.google.firebase:firebase-config-ktx'
implementation 'com.google.firebase:firebase-perf-ktx'
implementation "com.google.android.gms:play-services-auth:$play_auth_version"
implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation "com.google.android.gms:play-services-wearable:$play_wearables_version"
implementation "androidx.core:core-ktx:$core_ktx_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
implementation "androidx.fragment:fragment-ktx:1.8.2"
implementation "androidx.paging:paging-runtime-ktx:$paging_version"
implementation "androidx.paging:paging-compose:$paging_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "androidx.compose.material3:material3:1.2.1"
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
implementation 'com.google.android.play:review:2.0.1'
implementation 'com.google.android.play:review-ktx:2.0.1'
implementation 'androidx.activity:activity-compose:1.9.1'
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.ui:ui-text-google-fonts:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
implementation project(':common')
implementation project(':shared')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.gu.android:toolargetool:0.3.0'
}
android {
compileSdk target_sdk
testOptions {
unitTests {
includeAndroidResources = true
}
animationsDisabled = true
}
namespace "com.habitrpg.android.habitica"
defaultConfig {
minSdkVersion min_sdk
compileSdk target_sdk
applicationId "com.habitrpg.android.habitica"
vectorDrawables.useSupportLibrary = true
buildConfigField "String", "STORE", "\"google\""
buildConfigField "String", "TESTING_LEVEL", "\"production\""
resourceConfigurations += ['en', 'bg', 'de', 'en-rGB', 'es', 'fr', 'hr-rHR', 'in', 'it', 'iw', 'ja', 'ko', 'lt', 'nl', 'pl', 'pt-rBR', 'pt-rPT', 'ru', 'tr', 'uk', 'zh', 'zh-rTW']
versionCode app_version_code
versionName app_version_name
targetSdkVersion target_sdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
def formattedDate = new Date().format('yyMMdd')
archivesBaseName = "Habitica-${formattedDate}${versionCode}"
}
buildFeatures {
viewBinding true
compose true
renderScript true
buildConfig = true
aidl true
}
signingConfigs {
release
}
flavorDimensions.add("buildType")
buildTypes {
debug {
//applicationIdSuffix ".debug"
debuggable true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// Disable fabric build ID generation for debug builds
ext.enableCrashlytics = false
ext.alwaysUpdateBuildId = false
//resValue "string", "content_provider", "com.habitrpg.android.habitica.debug.fileprovider"
resValue "string", "content_provider", "com.habitrpg.android.habitica.fileprovider"
resValue "string", "app_name", "Habitica Debug"
enableUnitTestCoverage false
enableAndroidTestCoverage false
}
debugIAP {
signingConfig signingConfigs.release
debuggable true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// Disable fabric build ID generation for debug builds
ext.enableCrashlytics = false
ext.alwaysUpdateBuildId = false
resValue "string", "content_provider", "com.habitrpg.android.habitica.fileprovider"
resValue "string", "app_name", "Habitica Debug"
}
release {
signingConfig signingConfigs.release
debuggable false
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
resValue "string", "content_provider", "com.habitrpg.android.habitica.fileprovider"
resValue "string", "app_name", "Habitica"
}
}
productFlavors {
dev {
dimension "buildType"
}
staff {
dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"staff\""
resValue "string", "app_name", "Habitica Staff"
versionCode app_version_code + 8
}
partners {
dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"partners\""
resValue "string", "app_name", "Habitica"
versionCode app_version_code + 6
}
alpha {
dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"alpha\""
resValue "string", "app_name", "Habitica Alpha"
versionCode app_version_code + 4
}
beta {
buildConfigField "String", "TESTING_LEVEL", "\"beta\""
dimension "buildType"
versionCode app_version_code + 2
}
prod {
buildConfigField "String", "TESTING_LEVEL", "\"production\""
dimension "buildType"
versionCode app_version_code
}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src/main/java']
resources.srcDirs = ['src/main/java']
aidl.srcDirs = ['src/main/java']
renderscript.srcDirs = ['src/main/java']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
test {
java.srcDir("src/test/java")
}
debugIAP { java.srcDirs = ['src/debug/java'] }
release { java.srcDirs = ['src/release/java'] }
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
bundle {
language {
// Specifies that the app bundle should not support
// configuration APKs for language resources. These
// resources are instead packaged with each base and
// dynamic feature APK.
enableSplit = false
}
}
lint {
abortOnError false
disable 'MissingTranslation', 'InvalidPackage'
enable 'LogConditional', 'IconExpectedSize', 'MissingRegistered', 'TypographyQuotes'
}
packagingOptions {
resources.excludes.add("META-INF/*")
}
}
android.testOptions {
unitTests.all {
useJUnitPlatform()
}
unitTests.returnDefaultValues = true
}
Properties props = new Properties()
def propFile = new File('signingrelease.properties')
if (propFile.canRead()) {
props.load(new FileInputStream(propFile))
if (props != null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
} else {
println 'signing.properties found but some entries are missing'
android.buildTypes.release.signingConfig = null
}
} else {
println 'signing.properties not found'
android.buildTypes.release.signingConfig = null
}
// Add Habitica Properties to buildConfigField
final File HRPG_PROPS_FILE = new File(projectDir.absolutePath + '/../habitica.properties')
if (HRPG_PROPS_FILE.canRead()) {
Properties HRPG_PROPS = new Properties()
HRPG_PROPS.load(new FileInputStream(HRPG_PROPS_FILE))
if (HRPG_PROPS != null) {
android.buildTypes.configureEach { buildType ->
HRPG_PROPS.any { property ->
buildType.buildConfigField "String", property.key, "\"${property.value}\""
}
}
} else {
throw new MissingResourceException('habitica.properties found but some entries are missing')
}
} else {
throw new MissingResourceException('habitica.properties not found')
}
// Add Habitica Resources to resources
final File HRPG_RES_FILE = new File(projectDir.absolutePath + '/../habitica.resources')
if (HRPG_RES_FILE.canRead()) {
Properties HRPG_RES = new Properties()
HRPG_RES.load(new FileInputStream(HRPG_RES_FILE))
if (HRPG_RES != null) {
android.buildTypes.configureEach { buildType ->
HRPG_RES.any { property ->
buildType.resValue "string", property.key, "\"${property.value}\""
}
}
} else {
throw new MissingResourceException('habitica.resources found but some entries are missing')
}
} else {
throw new MissingResourceException('habitica.resources not found')
}
tasks.configureEach { task ->
if (task.name == "lint") {
task.enabled = false
}
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << "-Xmaxerrs" << "500"
}
}
apply plugin: 'com.google.gms.google-services'

218
Habitica/build.gradle.kts Normal file
View file

@ -0,0 +1,218 @@
import com.android.build.gradle.internal.lint.AndroidLintTask
plugins {
`jacoco-report-aggregation`
alias(libs.plugins.kotlin.android)
alias(libs.plugins.android.application)
alias(libs.plugins.hilt)
alias(libs.plugins.navigation)
alias(libs.plugins.ksp)
id("kotlin-parcelize")
id("kotlin-kapt")
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.realm)
alias(libs.plugins.habitrpg.application)
alias(libs.plugins.habitrpg.convention)
alias(libs.plugins.crashlytics)
alias(libs.plugins.firebase.perf)
alias(libs.plugins.google.service)
}
android {
compileSdk = libs.versions.targetSdk.get().toInt()
namespace = "com.habitrpg.android.habitica"
defaultConfig {
applicationId = "com.habitrpg.android.habitica"
minSdk = libs.versions.minSdk.get().toInt()
compileSdk = libs.versions.targetSdk.get().toInt()
vectorDrawables.useSupportLibrary = true
targetSdk = libs.versions.targetSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
resourceConfigurations.addAll(
listOf("en", "bg", "de", "en-rGB", "es", "fr", "hr-rHR", "in", "it", "iw", "ja", "ko", "lt", "nl", "pl", "pt-rBR", "pt-rPT", "ru", "tr", "uk", "zh", "zh-rTW")
)
buildConfigField("String", "STORE", "\"google\"")
buildConfigField("String", "TESTING_LEVEL", "\"production\"")
}
buildFeatures {
viewBinding = true
compose = true
renderScript = true
buildConfig = true
aidl = true
}
buildTypes {
debug {
// Keep it commented!
//applicationIdSuffix ".debug"
isDebuggable = true
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
// Disable fabric build ID generation for debug builds
ext["enableCrashlytics"] = false
ext["alwaysUpdateBuildId"] = false
enableUnitTestCoverage = false
resValue("string", "content_provider", "com.habitrpg.android.habitica.debug.fileprovider")
resValue("string", "content_provider", "com.habitrpg.android.habitica.fileprovider")
resValue("string", "app_name", "Habitica Debug")
}
create("debugIAP") {
signingConfigs.asMap["release"]?.let { releaseSigning -> signingConfig = releaseSigning }
isDebuggable = true
isMinifyEnabled = false
enableUnitTestCoverage = false
enableAndroidTestCoverage = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
// Disable fabric build ID generation for debug builds
ext["enableCrashlytics"] = false
ext["alwaysUpdateBuildId"] = false
resValue("string", "content_provider", "com.habitrpg.android.habitica.fileprovider")
resValue("string", "app_name", "Habitica Debug")
}
release {
signingConfigs.asMap["release"]?.let { releaseSigning -> signingConfig = releaseSigning }
isDebuggable = false
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
resValue("string", "content_provider", "com.habitrpg.android.habitica.fileprovider")
resValue("string", "app_name", "Habitica")
}
}
sourceSets {
getByName("main") {
manifest.srcFile("AndroidManifest.xml")
java.srcDirs("src/main/java")
resources.srcDirs("src/main/java")
aidl.srcDirs("src/main/java")
renderscript.srcDirs("src/main/java")
res.srcDirs("res")
assets.srcDirs("assets")
}
getByName("test") { java.srcDir("src/test/java") }
getByName("debugIAP") { java.srcDirs("src/debug/java") }
getByName("release") { java.srcDirs("src/release/java") }
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
testOptions {
unitTests {
isIncludeAndroidResources = true
isReturnDefaultValues = true
all { it.useJUnitPlatform() }
}
animationsDisabled = true
}
lint {
abortOnError = false
disable.addAll(listOf("MissingTranslation", "InvalidPackage"))
enable.addAll(listOf("LogConditional", "IconExpectedSize", "MissingRegistered", "TypographyQuotes"))
}
bundle.language.enableSplit = false
packaging.resources.excludes.add("META-INF/*")
kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString()
}
tasks.withType<AndroidLintTask> { enabled = false }
tasks.withType<JavaCompile> { options.compilerArgs.addAll(listOf("-Xmaxerrs", "500")) }
dependencies {
implementation(projects.common)
implementation(projects.shared)
implementation(fileTree("../common/libs") { include("*.jar") })
//Networking
implementation(libs.bundles.okhttp)
//REST API handling
implementation(libs.retrofit) { exclude(module = libs.okhttp.asProvider().get().group) }
implementation(libs.retrofit.converter.gson)
//Dependency Injection
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
compileOnly(libs.javax.annotation)
//App Compatibility and Material Design
implementation(libs.bundles.design)
//Desugaring
coreLibraryDesugaring(libs.desugar)
implementation(libs.device.names)
// IAP Handling / Verification
implementation(libs.billing)
implementation(libs.viewPagerIndicator) { exclude(group = "com.google.android") }
implementation(libs.coil.compose)
//Analytics
implementation(libs.amplitude.analytic)
//Tests
testImplementation(libs.bundles.test.implementation)
androidTestImplementation(libs.bundles.android.test.implementation)
androidTestImplementation(libs.kaspresso) { exclude(module = "protobuf-lite") }
debugImplementation(libs.test.fragment)
debugImplementation(libs.test.monitor)
androidTestUtil(libs.test.orchestrator)
implementation(libs.shimmer)
//Leak Detection
debugImplementation(libs.leakcanary)
// Google Services
implementation(platform(libs.firebase.bom))
implementation(libs.bundles.google.services)
implementation(libs.flexbox)
implementation(libs.core)
implementation(libs.core.ktx)
implementation(libs.lifecycle.viewmodel)
implementation(libs.lifecycle.livedata)
implementation(libs.lifecycle.common)
implementation(libs.navigation.fragment)
implementation(libs.navigation.ui)
implementation(libs.fragment.ktx)
implementation(libs.paging)
implementation(libs.paging.compose)
implementation(libs.kotlinx.coroutine)
implementation(libs.coroutine.android)
implementation(libs.material3)
implementation(libs.accompanist.sysmtemUi)
implementation(libs.google.play.review)
implementation(libs.google.play.review.ktx)
implementation(libs.activity.compose)
implementation(libs.runtime.livedata)
implementation(libs.compose.animation)
implementation(libs.text.google.fonts)
implementation(libs.ui.tooling)
implementation(libs.viewmodel.compose)
implementation(libs.kotlin.jdk7)
implementation(libs.toolargetool)
}

View file

@ -0,0 +1,36 @@
{
"dimension": "buildType",
"flavors": [
{
"name": "dev"
},
{
"name": "staff",
"testingLevel": "\"staff\"",
"appName": "Habitica Staff",
"versionCodeIncrement": 8
},
{
"name": "partners",
"testingLevel": "\"partners\"",
"appName": "Habitica",
"versionCodeIncrement": 6
},
{
"name": "alpha",
"testingLevel": "\"alpha\"",
"appName": "Habitica Alpha",
"versionCodeIncrement": 4
},
{
"name": "beta",
"testingLevel": "\"beta\"",
"versionCodeIncrement": 2
},
{
"name": "prod",
"testingLevel": "\"production\"",
"versionCodeIncrement": 0
}
]
}

1
build-logic/convention/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,38 @@
import org.gradle.api.JavaVersion.VERSION_11
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
`kotlin-dsl`
}
group = "com.habitrpg.buildlogic"
version = "0.1.0"
java {
sourceCompatibility = VERSION_11
targetCompatibility = VERSION_11
}
kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_11
dependencies {
implementation(libs.android.gradlePlugin)
implementation(libs.detekt.gradlePlugin)
implementation(libs.gson)
implementation(libs.ktlint.gradlePlugin)
}
gradlePlugin {
plugins {
register("conventionPlugin") {
id = "com.habitrpg.buildlogic.convention"
version = project.version
implementationClass = "com.habitrpg.buildlogic.plugin.ConventionPlugin"
}
register("applicationPlugin") {
id = "com.habitrpg.buildlogic.application"
version = project.version
implementationClass = "com.habitrpg.buildlogic.plugin.ApplicationPlugin"
}
}
}

View file

@ -0,0 +1,10 @@
package com.habitrpg.buildlogic.model
import com.google.gson.annotations.SerializedName
data class Flavor(
@SerializedName("testingLevel") val testingLevel: String?,
@SerializedName("appName") val appName: String?,
@SerializedName("name") val name: String,
@SerializedName("versionCodeIncrement") val versionCodeIncrement: Int?
)

View file

@ -0,0 +1,8 @@
package com.habitrpg.buildlogic.model
import com.google.gson.annotations.SerializedName
data class HabiticaFlavor(
@SerializedName("dimension") val dimension: String,
@SerializedName("flavors") val flavors: List<Flavor>
)

View file

@ -0,0 +1,68 @@
package com.habitrpg.buildlogic.plugin
import com.android.build.gradle.AppExtension
import com.google.gson.Gson
import com.habitrpg.buildlogic.model.HabiticaFlavor
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType
import java.io.File
import java.io.FileInputStream
import java.io.FileReader
import java.util.Properties
class ApplicationPlugin : Plugin<Project> {
override fun apply(target: Project): Unit = with(target) {
pluginManager.withPlugin("com.android.application") {
val signingProps = try {
Properties().apply { load(FileInputStream(File("signingrelease.properties"))) }
} catch (t: Throwable) {
Properties()
}
val signingPropsAvailable = signingProps.containsKey("STORE_FILE") && signingProps.containsKey("STORE_PASSWORD") &&
signingProps.containsKey("KEY_ALIAS") && signingProps.containsKey("KEY_PASSWORD")
val versionProps = try {
Properties().apply { load(FileInputStream(File("version.properties"))) }
} catch (t: Throwable) {
Properties()
}
val versionPropsAvailable = versionProps.containsKey("NAME") && versionProps.containsKey("CODE")
val currentVersionCode = (versionProps["CODE"] ?: "0").toString().toInt()
val habiticaFlavor = Gson().fromJson(FileReader(file("habitica-flavor.json")), HabiticaFlavor::class.java)
extensions.getByType<AppExtension>().apply {
defaultConfig {
versionName = versionProps["NAME"].toString()
versionCode = currentVersionCode
val habiticaRes = Properties().apply { load(FileInputStream(File(projectDir.absolutePath + "/../habitica.resources"))) }
habiticaRes.forEach { key, value -> resValue("string", key.toString(), "\"${value}\"") }
val hrpgProps = Properties().apply { load(FileInputStream(File(projectDir.absolutePath + "/../habitica.properties"))) }
hrpgProps.forEach { key, value -> buildConfigField("String", key as String, "\"${value}\"") }
}
if (signingPropsAvailable && versionPropsAvailable) signingConfigs.register("release") {
storeFile = file(signingProps["STORE_FILE"].toString())
storePassword = signingProps["STORE_PASSWORD"].toString()
keyAlias = signingProps["KEY_ALIAS"].toString()
keyPassword = signingProps["KEY_PASSWORD"].toString()
}
flavorDimensions(habiticaFlavor.dimension)
productFlavors {
habiticaFlavor.flavors.forEach { flavor ->
register(flavor.name) {
dimension = habiticaFlavor.dimension
versionCode = currentVersionCode + (flavor.versionCodeIncrement ?: 0)
if (flavor.testingLevel != null) buildConfigField("String", "TESTING_LEVEL", flavor.testingLevel)
if (flavor.appName != null) resValue("string", "app_name", flavor.appName)
}
}
}
}
}
}
}

View file

@ -0,0 +1,77 @@
package com.habitrpg.buildlogic.plugin
import io.gitlab.arturbosch.detekt.Detekt
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.testing.Test
import org.gradle.api.tasks.testing.TestDescriptor
import org.gradle.api.tasks.testing.TestResult
import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED
import org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED
import org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED
import org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR
import org.gradle.kotlin.dsl.KotlinClosure2
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.withType
import org.jlleitschuh.gradle.ktlint.KtlintExtension
class ConventionPlugin : Plugin<Project> {
override fun apply(target: Project): Unit = with(target) {
with(pluginManager) {
apply("io.gitlab.arturbosch.detekt")
apply("org.jlleitschuh.gradle.ktlint")
}
configure<KtlintExtension> {
filter {
exclude { entry -> entry.file.toString().contains("generated") }
}
}
tasks.withType<Detekt> {
source = fileTree("$projectDir/src/main/java")
config = files("${rootProject.rootDir}/detekt.yml")
baseline = file("${rootProject.projectDir}/detekt_baseline.xml")
reports {
xml.required.set(false)
html.required.set(true)
html.outputLocation.set(layout.buildDirectory.file("reports/detekt.html"))
txt.required.set(false)
sarif.required.set(true)
sarif.outputLocation.set(layout.buildDirectory.file("reports/detekt.sarif"))
}
}
tasks.withType<Test> {
outputs.upToDateWhen { false }
testLogging {
showStandardStreams = true
events.addAll(listOf(PASSED, SKIPPED, FAILED, STANDARD_ERROR))
}
afterSuite(KotlinClosure2<TestDescriptor, TestResult, Unit>({ desc, result ->
if (desc.parent == null) { // will match the outermost suite
val output = buildString {
append("Results: ${result.resultType} ")
append("(${result.testCount} tests, ")
append("${result.successfulTestCount} passed, ")
append("${result.failedTestCount} failed, ")
append("${result.skippedTestCount} skipped)")
}
val startItem = "| "
val endItem = " |"
val repeatLength = startItem.length + output.length + endItem.length
println(buildString {
append("\n")
repeat(repeatLength) { append("") }
append("\n")
append(startItem + output + endItem)
append("\n")
repeat(repeatLength) { append("") }
})
}
}))
}
}
}

View file

@ -0,0 +1,4 @@
# Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true

View file

@ -0,0 +1,19 @@
dependencyResolutionManagement {
repositories {
gradlePluginPortal()
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
maven("https://jitpack.io")
mavenLocal()
}
versionCatalogs.create("libs").from(files("../gradle/libs.versions.toml"))
}
rootProject.name = "build-logic"
include(":convention")

View file

@ -1,138 +0,0 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
min_sdk = 21
target_sdk = 34
wearos_target_sdk = 33
app_version_name = ''
app_version_code = 0
accompanist_version = '0.30.0'
amplitude_version = '1.6.1'
appcompat_version = '1.7.0'
coil_version = '2.4.0'
compose_version = '1.6.8'
compose_compiler = '1.5.14'
core_ktx_version = '1.13.1'
coroutines_version = '1.8.0'
daggerhilt_version = '2.51.1'
firebase_bom = '31.3.0'
kotest_version = '5.6.2'
kotlin_version = '2.0.20'
ktlint_version = '1.2.1'
lifecycle_version = '2.8.4'
markwon_version = '4.6.2'
mockk_version = '1.13.4'
moshi_version = '1.15.0'
navigation_version = '2.7.7'
okhttp_version = '4.12.0'
paging_version = '3.3.0'
play_wearables_version = '18.2.0'
play_auth_version = '21.2.0'
preferences_version = '1.2.1'
realm_version = '1.9.1'
retrofit_version = '2.9.0'
recyclerview_version = '1.3.2'
}
repositories {
google()
maven { url "https://plugins.gradle.org/m2/" }
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.google.gms:google-services:4.4.2'
classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.2'
classpath "io.realm:realm-gradle-plugin:10.19.0"
classpath("io.realm.kotlin:gradle-plugin:$realm_version")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.19.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
classpath 'com.google.firebase:perf-plugin:1.4.2'
classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerhilt_version"
classpath "org.jlleitschuh.gradle:ktlint-gradle:11.3.1"
classpath "org.jetbrains.kotlin:compose-compiler-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'io.gitlab.arturbosch.detekt'
allprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint"
repositories {
google()
mavenCentral()
}
tasks.withType(Test).configureEach {
testLogging {
events "passed", "skipped", "failed", "standardError"
outputs.upToDateWhen {false}
afterSuite { desc, result ->
if (!desc.parent) { // will match the outermost suite
def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
def startItem = '| ', endItem = ' |'
def repeatLength = startItem.length() + output.length() + endItem.length()
println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength))
}
}
}
}
}
Properties props = new Properties()
def propFile = new File('version.properties')
if (propFile.canRead()) {
props.load(new FileInputStream(propFile))
if (props != null && props.containsKey('NAME') && props.containsKey('CODE') ) {
ext.app_version_name = props['NAME']
ext.app_version_code = props['CODE'] as Integer
} else {
println 'signing.properties found but some entries are missing'
android.buildTypes.release.signingConfig = null
}
} else {
println 'signing.properties not found'
}
detekt {
source = files("Habitica/src/main/java")
config = files("detekt.yml")
baseline = file("${rootProject.projectDir}/detekt_baseline.xml")
}
ktlint {
filter {
exclude { entry ->
entry.file.toString().contains("generated")
}
}
}
tasks.named("detekt").configure {
reports {
xml.required.set(false)
html.required.set(true)
html.outputLocation.set(file("build/reports/detekt.html"))
txt.required.set(false)
sarif.required.set(true)
sarif.outputLocation.set(file("build/reports/detekt.sarif"))
}
}
task allUnitTests(type: GradleBuild) {
tasks = [':Habitica:testProdDebugUnitTest', ':wearos:testProdDebugUnitTest', ':common:testProdDebugUnitTest']
}
subprojects {
tasks.withType(KotlinCompile).configureEach {
kotlinOptions.jvmTarget = "11"
}
}

23
build.gradle.kts Normal file
View file

@ -0,0 +1,23 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.crashlytics) apply false
alias(libs.plugins.detekt) apply false
alias(libs.plugins.firebase.perf) apply false
alias(libs.plugins.google.service) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.kotest) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.ktlint) apply false
alias(libs.plugins.navigation) apply false
alias(libs.plugins.realm) apply false
alias(libs.plugins.habitrpg.application) apply false
alias(libs.plugins.habitrpg.convention) apply false
}
tasks.register("allUnitTests", DefaultTask::class) {
dependsOn(":Habitica:testProdDebugUnitTest", ":wearos:testProdDebugUnitTest", ":common:testProdDebugUnitTest")
}

View file

@ -1,30 +1,24 @@
@file:Suppress("PropertyName")
import java.io.FileInputStream
import java.util.Properties
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
alias(libs.plugins.kotlin.android)
alias(libs.plugins.habitrpg.convention)
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.compose)
}
val rootExtra = rootProject.extra
android {
compileSdk = rootExtra.get("target_sdk") as Int
namespace = "com.habitrpg.common.habitica"
compileSdk = libs.versions.targetSdk.get().toInt()
defaultConfig {
minSdk = rootExtra.get("min_sdk") as Int
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
testOptions {
unitTests {
}
animationsDisabled = true
val hrpgProps = Properties().apply { load(FileInputStream(File(projectDir.absolutePath + "/../habitica.properties"))) }
hrpgProps.forEach { key, value -> buildConfigField("String", key as String, "\"${value}\"") }
}
buildTypes {
@ -44,8 +38,12 @@ android {
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = rootProject.extra.get("compose_compiler") as String
testOptions {
unitTests {
isIncludeAndroidResources = true
all { it.useJUnitPlatform() }
}
animationsDisabled = true
}
compileOptions {
@ -53,13 +51,6 @@ android {
targetCompatibility = JavaVersion.VERSION_11
}
kotlin {
jvmToolchain(11)
}
namespace = "com.habitrpg.common.habitica"
flavorDimensions.add("buildType")
productFlavors {
register("dev") {
dimension = "buildType"
@ -90,85 +81,42 @@ android {
buildConfigField("String", "TESTING_LEVEL", "\"production\"")
}
}
}
val core_ktx_version: String by rootExtra
val accompanist_version: String by rootExtra
val appcompat_version: String by rootExtra
val compose_version: String by rootExtra
val markwon_version: String by rootExtra
val coil_version: String by rootExtra
val mockk_version: String by rootExtra
val kotest_version: String by rootExtra
val kotlin_version: String by rootExtra
val navigation_version: String by rootExtra
kotlin.jvmToolchain(11)
composeOptions.kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString()
flavorDimensions.add("buildType")
}
dependencies {
implementation(projects.shared)
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
implementation("androidx.core:core-ktx:$core_ktx_version")
implementation("androidx.appcompat:appcompat:$appcompat_version")
implementation(libs.core)
implementation(libs.core.ktx)
implementation(libs.appcompat)
// Markdown
implementation("io.noties.markwon:core:$markwon_version")
implementation("io.noties.markwon:ext-strikethrough:$markwon_version")
implementation("io.noties.markwon:image:$markwon_version")
implementation("io.noties.markwon:recycler:$markwon_version")
implementation("io.noties.markwon:linkify:$markwon_version")
implementation(libs.bundles.markwon)
// Image Management Library
implementation("io.coil-kt:coil:$coil_version")
implementation("io.coil-kt:coil-gif:$coil_version")
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.navigation:navigation-common-ktx:$navigation_version")
implementation("androidx.navigation:navigation-runtime-ktx:$navigation_version")
implementation("com.google.android.material:material:1.12.0")
implementation(libs.coil)
implementation(libs.coil.gif)
testImplementation("io.mockk:mockk:$mockk_version")
testImplementation("io.mockk:mockk-android:$mockk_version")
testImplementation("io.kotest:kotest-runner-junit5:$kotest_version")
testImplementation("io.kotest:kotest-assertions-core:$kotest_version")
testImplementation("io.kotest:kotest-framework-datatest:$kotest_version")
testImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
implementation(libs.navigation.common)
implementation(libs.navigation.runtime)
implementation(libs.recyclerview)
implementation(libs.material)
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
testImplementation(libs.bundles.test.implementation)
androidTestImplementation(libs.bundles.android.test.implementation)
implementation("androidx.activity:activity-compose:1.9.1")
implementation("androidx.compose.runtime:runtime-livedata:$compose_version")
implementation("androidx.compose.animation:animation:$compose_version")
implementation("androidx.compose.ui:ui-text-google-fonts:$compose_version")
implementation("androidx.compose.ui:ui-tooling:$compose_version")
implementation("androidx.compose.material3:material3:1.2.1")
implementation("com.google.accompanist:accompanist-themeadapter-material3:$accompanist_version")
implementation(project(":shared"))
}
android.testOptions {
unitTests.all {
it.useJUnitPlatform()
}
}
tasks.withType<Test> {
this.testLogging {
this.showStandardStreams = true
}
}
// Add Habitica Properties to buildConfigField
val HRPG_PROPS_FILE = File(projectDir.absolutePath + "/../habitica.properties")
if (HRPG_PROPS_FILE.canRead()) {
val hrpgProps = Properties()
hrpgProps.load(FileInputStream(HRPG_PROPS_FILE))
android.buildTypes.configureEach {
hrpgProps.forEach { property ->
buildConfigField("String", property.key as String, "\"${property.value}\"")
}
}
} else {
throw MissingResourceException("habitica.properties not found")
}
implementation(libs.activity.compose)
implementation(libs.runtime.livedata)
implementation(libs.compose.animation)
implementation(libs.text.google.fonts)
implementation(libs.ui.tooling)
implementation(libs.material3)
implementation(libs.accompanist.theme)
}

View file

@ -10,3 +10,5 @@ kotlin.mpp.androidSourceSetLayoutVersion=2
android.nonTransitiveRClass=false
android.nonFinalResIds=false
android.enableR8.fullMode=false
org.gradle.caching=true
org.gradle.parallel=true

221
gradle/libs.versions.toml Normal file
View file

@ -0,0 +1,221 @@
[versions]
accompanist = "0.30.0"
agp = "8.5.2"
amplitude = "1.6.1"
androidTest = "1.6.1"
androidTestRunner = "1.6.2"
annotationApi = "1.3.2"
appcompat = "1.7.0"
billing = "7.0.0"
coil = "2.4.0"
compose = "1.6.8"
composeActivity = "1.9.1"
compose_compiler = "1.5.14"
constraintlayout = "2.2.0"
converterMoshi = "2.9.0"
coordinatorlayout = "1.2.0"
coreSplashscreen = "1.1.0-rc01"
core_ktx = "1.13.0"
coroutines = "1.8.0"
crashlytics = "3.0.2"
daggerhilt = "2.51.1"
desuggar = "2.0.4"
detekt = "1.19.0"
deviceNames = "2.1.1"
firebase-perf = "1.4.2"
firebase_bom = "31.3.0"
flexbox = "3.0.0"
fragmentKtx = "1.8.2"
fragmentTesting = "1.8.2"
google-service = "4.4.2"
gson = "2.11.0"
habitRpgPlugin = "0.1.0"
inAppReview = "2.0.1"
junitKtx = "1.2.1"
kaspresso = "1.5.1"
kotest = "5.6.2"
kotlin = "2.0.20"
ksp = "2.0.20-1.0.25"
ktlint = "1.2.1"
ktlintPlugin = "11.3.1"
leakCanary = "2.10"
lifecycle = "2.8.4"
lifecycleRuntimeKtx = "2.8.4"
markwon = "4.6.2"
material = "1.12.0"
material3 = "1.2.1"
minSdk = "26"
mockk = "1.13.4"
moshi = "1.15.0"
moshiKotlin = "1.15.0"
navigation = "2.7.7"
okhttp = "4.12.0"
orchestrator = "1.5.0"
pagerIndicator = "2.4.1"
paging = "3.3.0"
play_auth = "21.2.0"
play_wearables = "18.2.0"
preferences = "1.2.1"
realm = "1.9.1"
realmPlugin = "10.19.0"
recyclerview = "1.3.2"
retrofit = "2.9.0"
shimmer = "0.5.0"
swipeRefresh = "1.1.0"
targetSdk = "34"
tooLargeTool = "0.3.0"
turbine = "0.12.1"
wear = "1.3.0"
wearInput = "1.1.0"
wearOsTargetSdk = "33"
[libraries]
accompanist-sysmtemUi = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" }
accompanist-theme = { group = "com.google.accompanist", name = "accompanist-themeadapter-material3", version.ref = "accompanist" }
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "composeActivity" }
amplitude-analytic = { group = "com.amplitude", name = "analytics-android", version.ref = "amplitude" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
billing = { group = "com.android.billingclient", name = "billing-ktx", version.ref = "billing" }
coil = { group = "io.coil-kt", name = "coil", version.ref = "coil" }
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
coil-gif = { group = "io.coil-kt", name = "coil-gif", version.ref = "coil" }
compose-animation = { group = "androidx.compose.animation", name = "animation", version.ref = "compose" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "coordinatorlayout" }
core = { group = "androidx.core", name = "core", version.ref = "core_ktx" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core_ktx" }
core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
coroutine-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
desugar = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desuggar" }
device-names = { group = "com.jaredrummler", name = "android-device-names", version.ref = "deviceNames" }
firebase-analytic = { group = "com.google.firebase", name = "firebase-analytics-ktx" }
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase_bom" }
firebase-config = { group = "com.google.firebase", name = "firebase-config-ktx" }
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" }
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging-ktx" }
firebase-perf = { group = "com.google.firebase", name = "firebase-perf-ktx" }
flexbox = { group = "com.google.android.flexbox", name = "flexbox", version.ref = "flexbox" }
fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
google-play-auth = { group = "com.google.android.gms", name = "play-services-auth", version.ref = "play_auth" }
google-play-review = { group = "com.google.android.play", name = "review", version.ref = "inAppReview" }
google-play-review-ktx = { group = "com.google.android.play", name = "review-ktx", version.ref = "inAppReview" }
google-play-wearable = { group = "com.google.android.gms", name = "play-services-wearable", version.ref = "play_wearables" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "daggerhilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "daggerhilt" }
javax-annotation = { group = "javax.annotation", name = "javax.annotation-api", version.ref = "annotationApi" }
kaspresso = { group = "com.kaspersky.android-components", name = "kaspresso", version.ref = "kaspresso" }
kaspresso-compose = { group = "com.kaspersky.android-components", name = "kaspresso-compose-support", version.ref = "kaspresso" }
kotest-assertions = { group = "io.kotest", name = "kotest-assertions-core", version.ref = "kotest" }
kotest-framework = { group = "io.kotest", name = "kotest-framework-datatest", version.ref = "kotest" }
kotest-runner = { group = "io.kotest", name = "kotest-runner-junit5", version.ref = "kotest" }
kotlin-jdk7 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk7", version.ref = "kotlin" }
kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" }
kotlinx-coroutine = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakCanary" }
lifecycle-common = { group = "androidx.lifecycle", name = "lifecycle-common-java8", version.ref = "lifecycle" }
lifecycle-livedata = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" }
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
markwon-core = { group = "io.noties.markwon", name = "core", version.ref = "markwon" }
markwon-ext = { group = "io.noties.markwon", name = "ext-strikethrough", version.ref = "markwon" }
markwon-image = { group = "io.noties.markwon", name = "image", version.ref = "markwon" }
markwon-linkify = { group = "io.noties.markwon", name = "linkify", version.ref = "markwon" }
markwon-recycler = { group = "io.noties.markwon", name = "recycler", version.ref = "markwon" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
mockk-agent = { group = "io.mockk", name = "mockk-agent", version.ref = "mockk" }
mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" }
moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshiKotlin" }
moshi-kotlin-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshiKotlin" }
navigation-common = { group = "androidx.navigation", name = "navigation-common-ktx", version.ref = "navigation" }
navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" }
navigation-runtime = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "navigation" }
navigation-ui = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
paging = { group = "androidx.paging", name = "paging-runtime-ktx", version.ref = "paging" }
paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" }
preference = { group = "androidx.preference", name = "preference-ktx", version.ref = "preferences" }
recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
retrofit2-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "converterMoshi" }
runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "compose" }
shimmer = { group = "com.facebook.shimmer", name = "shimmer", version.ref = "shimmer" }
swipeRefreshLayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swipeRefresh" }
test-core = { group = "androidx.test", name = "core", version.ref = "androidTest" }
test-core-ktx = { group = "androidx.test", name = "core-ktx", version.ref = "androidTest" }
test-fragment = { group = "androidx.fragment", name = "fragment-testing", version.ref = "fragmentTesting" }
test-junit = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "junitKtx" }
test-monitor = { group = "androidx.test", name = "monitor", version.ref = "androidTest" }
test-orchestrator = { group = "androidx.test", name = "orchestrator", version.ref = "orchestrator" }
test-rules = { group = "androidx.test", name = "rules", version.ref = "androidTest" }
test-runner = { group = "androidx.test", name = "runner", version.ref = "androidTestRunner" }
text-google-fonts = { group = "androidx.compose.ui", name = "ui-text-google-fonts", version.ref = "compose" }
toolargetool = { group = "com.gu.android", name = "toolargetool", version.ref = "tooLargeTool" }
turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "compose" }
viewPagerIndicator = { group = "fr.avianey.com.viewpagerindicator", name = "library", version.ref = "pagerIndicator" }
viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
wear = { module = "androidx.wear:wear", version.ref = "wear" }
wear-input = { module = "androidx.wear:wear-input", version.ref = "wearInput" }
## Dependencies of included build-logic
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" }
detekt-gradlePlugin = { group = "io.gitlab.arturbosch.detekt", name = "detekt-gradle-plugin", version.ref = "detekt" }
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
ktlint-gradlePlugin = { group = "org.jlleitschuh.gradle", name = "ktlint-gradle", version.ref = "ktlintPlugin" }
[bundles]
okhttp = ["okhttp", "okhttp-logging"]
markwon = ["markwon-core", "markwon-ext", "markwon-image", "markwon-linkify", "markwon-recycler"]
design = ["appcompat", "material", "recyclerview", "preference", "swipeRefreshLayout"]
google-services = [
"firebase-analytic",
"firebase-crashlytics",
"firebase-messaging",
"firebase-config",
"firebase-perf",
"google-play-auth",
"google-play-wearable"
]
test-implementation = [
"test-core",
"mockk",
"mockk-android",
"kotest-runner",
"kotest-assertions",
"kotest-framework"
]
android-test-implementation = [
"test-runner",
"test-rules",
"test-core-ktx",
"test-junit",
"mockk-android",
"mockk-agent",
"kotest-assertions",
"kotlin-reflect",
"kaspresso-compose"
]
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "firebase-perf" }
google-service = { id = "com.google.gms.google-services", version.ref = "google-service" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "daggerhilt" }
kotest = { id = "io.kotest.multiplatform", version.ref = "kotest" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlintPlugin" }
navigation = { id = "androidx.navigation.safeargs", version.ref = "navigation" }
realm = { id = "realm-android", version.ref = "realmPlugin" }
## Plugins defined by build-logic
habitrpg-application = { id = "com.habitrpg.buildlogic.application", version.ref = "habitRpgPlugin" }
habitrpg-convention = { id = "com.habitrpg.buildlogic.convention", version.ref = "habitRpgPlugin" }

View file

@ -1,8 +0,0 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.6.0'
}
include 'Habitica', ':Habitica'
include ':wearos'
include ':common'
include ':shared'

48
settings.gradle.kts Normal file
View file

@ -0,0 +1,48 @@
@file:Suppress("UnstableApiUsage")
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
pluginManagement {
includeBuild("build-logic")
repositories {
gradlePluginPortal()
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
maven("https://jitpack.io")
mavenLocal()
}
resolutionStrategy.eachPlugin {
if (requested.id.id == "realm-android") useModule(
"io.realm:realm-gradle-plugin:${requested.version}"
)
}
}
dependencyResolutionManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
maven("https://jitpack.io")
maven("https://plugins.gradle.org/m2/")
mavenLocal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.6.0"
}
rootProject.name = "habitica-android"
include(":Habitica", ":wearos", ":common", ":shared")

View file

@ -1,20 +1,17 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("multiplatform")
id("com.android.library")
alias(libs.plugins.android.library)
id("kotlin-parcelize")
id("kotlin-kapt")
id("io.kotest.multiplatform") version "5.6.2"
}
allprojects {
repositories {
mavenCentral()
mavenLocal()
}
alias(libs.plugins.ksp)
alias(libs.plugins.habitrpg.convention)
alias(libs.plugins.kotest)
}
kotlin {
android()
androidTarget()
iosX64()
iosArm64()
iosSimulatorArm64()
@ -27,7 +24,7 @@ kotlin {
sourceSets {
commonMain {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${rootProject.extra.get("coroutines_version")}")
implementation(libs.kotlinx.coroutine)
}
}
commonTest {
@ -39,15 +36,12 @@ kotlin {
}
android {
compileSdk = rootProject.extra.get("target_sdk") as Int
compileSdk = libs.versions.targetSdk.get().toInt()
namespace = "com.habitrpg.shared.habitica"
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
}
defaultConfig.minSdk = 21
buildTypes {
release {
}
create("debugIAP") {
initWith(buildTypes["debug"])
isMinifyEnabled = false
@ -60,6 +54,8 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
namespace = "com.habitrpg.shared.habitica"
}
tasks.withType<KotlinCompile> {
compilerOptions.jvmTarget.set(JvmTarget.JVM_11)
}

View file

@ -1,224 +0,0 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
}
apply plugin: 'kotlin-android'
android {
compileSdk target_sdk
testOptions {
unitTests {
includeAndroidResources = true
}
animationsDisabled = true
}
defaultConfig {
applicationId "com.habitrpg.android.habitica"
minSdk 26
targetSdk wearos_target_sdk
compileSdk target_sdk
versionCode app_version_code + 1
versionName "${app_version_name}w"
buildConfigField "String", "TESTING_LEVEL", "\"production\""
def formattedDate = new Date().format('yyMMdd')
archivesBaseName = "Habitica-WearOS-${formattedDate}${versionCode}"
}
signingConfigs {
release
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
buildTypes {
debug {
minifyEnabled false
debuggable true
ext.enableCrashlytics = false
ext.alwaysUpdateBuildId = false
resValue "string", "app_name", "Habitica Debug"
}
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
resValue "string", "app_name", "Habitica"
}
}
bundle {
language {
// Specifies that the app bundle should not support
// configuration APKs for language resources. These
// resources are instead packaged with each base and
// dynamic feature APK.
enableSplit = false
}
}
flavorDimensions.add("buildType")
productFlavors {
dev {
dimension "buildType"
}
staff {
dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"staff\""
resValue "string", "app_name", "Habitica Staff"
versionCode app_version_code + 9
}
partners {
dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"partners\""
resValue "string", "app_name", "Habitica"
versionCode app_version_code + 7
}
alpha {
dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"alpha\""
resValue "string", "app_name", "Habitica Alpha"
versionCode app_version_code + 5
}
beta {
buildConfigField "String", "TESTING_LEVEL", "\"beta\""
dimension "buildType"
versionCode app_version_code + 3
}
prod {
buildConfigField "String", "TESTING_LEVEL", "\"production\""
dimension "buildType"
versionCode app_version_code + 1
}
}
buildFeatures {
viewBinding true
buildConfig true
}
namespace 'com.habitrpg.android.habitica'
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: '../common/libs')
implementation "androidx.core:core-ktx:$core_ktx_version"
implementation "com.google.android.gms:play-services-wearable:$play_wearables_version"
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
implementation 'androidx.wear:wear:1.3.0'
implementation "androidx.wear:wear-input:1.1.0"
//Networking
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
//REST API handling
implementation("com.squareup.retrofit2:retrofit:$retrofit_version") {
exclude module: 'okhttp'
}
implementation("com.squareup.retrofit2:converter-moshi:$retrofit_version")
implementation("com.squareup.moshi:moshi-kotlin:$moshi_version")
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshi_version")
implementation platform("com.google.firebase:firebase-bom:$firebase_bom")
implementation 'com.google.firebase:firebase-crashlytics-ktx'
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "androidx.preference:preference-ktx:$preferences_version"
implementation "androidx.navigation:navigation-fragment-ktx:2.7.7"
implementation "com.google.android.gms:play-services-auth:$play_auth_version"
implementation project(':common')
implementation project(':shared')
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "com.google.dagger:hilt-android:$daggerhilt_version"
kapt "com.google.dagger:hilt-compiler:$daggerhilt_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "androidx.core:core-splashscreen:1.1.0-rc01"
testImplementation "io.mockk:mockk:$mockk_version"
testImplementation "io.mockk:mockk-android:$mockk_version"
testImplementation "io.kotest:kotest-runner-junit5:$kotest_version"
testImplementation "io.kotest:kotest-assertions-core:$kotest_version"
testImplementation "io.kotest:kotest-framework-datatest:$kotest_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
testImplementation 'app.cash.turbine:turbine:0.12.1'
}
repositories {
mavenCentral()
}
android.testOptions {
unitTests.all {
it.useJUnitPlatform()
}
}
final File HRPG_PROPS_FILE = new File(projectDir.absolutePath + '/../habitica.properties')
if (HRPG_PROPS_FILE.canRead()) {
Properties HRPG_PROPS = new Properties()
HRPG_PROPS.load(new FileInputStream(HRPG_PROPS_FILE))
if (HRPG_PROPS != null) {
android.buildTypes.configureEach { buildType ->
HRPG_PROPS.any { property ->
buildType.buildConfigField "String", property.key, "\"${property.value}\""
}
}
} else {
throw new MissingResourceException('habitica.properties found but some entries are missing')
}
} else {
throw new MissingResourceException('habitica.properties not found')
}
Properties props = new Properties()
def propFile = new File('signingrelease.properties')
if (propFile.canRead()) {
props.load(new FileInputStream(propFile))
if (props != null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
} else {
println 'signing.properties found but some entries are missing'
android.buildTypes.release.signingConfig = null
}
} else {
println 'signing.properties not found'
android.buildTypes.release.signingConfig = null
}

113
wearos/build.gradle.kts Normal file
View file

@ -0,0 +1,113 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.hilt)
alias(libs.plugins.habitrpg.convention)
alias(libs.plugins.habitrpg.application)
alias(libs.plugins.crashlytics)
alias(libs.plugins.google.service)
}
android {
namespace = "com.habitrpg.android.habitica"
compileSdk = libs.versions.targetSdk.get().toInt()
defaultConfig {
applicationId = "com.habitrpg.android.habitica"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.wearOsTargetSdk.get().toInt()
compileSdk = libs.versions.targetSdk.get().toInt()
buildConfigField("String", "TESTING_LEVEL", "\"production\"")
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildTypes {
debug {
isMinifyEnabled = false
isDebuggable = true
ext["enableCrashlytics"] = false
ext["alwaysUpdateBuildId"] = false
resValue("string", "app_name", "Habitica Debug")
}
release {
signingConfigs.asMap["release"]?.let { releaseSigning -> signingConfig = releaseSigning }
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
resValue("string", "app_name", "Habitica")
}
}
buildFeatures {
viewBinding = true
buildConfig = true
}
testOptions {
unitTests {
isIncludeAndroidResources = true
all { it.useJUnitPlatform() }
}
animationsDisabled = true
}
bundle.language.enableSplit = false
kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString()
}
dependencies {
implementation(projects.shared)
implementation(projects.common)
implementation(fileTree("../common/libs") { include("*.jar") })
implementation(libs.core)
implementation(libs.core.ktx)
implementation(libs.google.play.wearable)
implementation(libs.recyclerview)
implementation(libs.wear)
implementation(libs.wear.input)
//Networking
implementation(libs.bundles.okhttp)
//REST API handling
implementation(libs.retrofit) { exclude(module = libs.okhttp.asProvider().get().name) }
implementation(libs.retrofit2.converter.moshi)
implementation(libs.moshi.kotlin)
implementation(libs.coordinatorlayout)
implementation(libs.constraintlayout)
ksp(libs.moshi.kotlin.codegen)
implementation(platform(libs.firebase.bom))
implementation(libs.bundles.google.services)
implementation(libs.lifecycle.viewmodel)
implementation(libs.lifecycle.livedata)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.lifecycle.common)
implementation(libs.kotlinx.coroutine)
implementation(libs.coroutine.android)
implementation(libs.preference)
implementation(libs.navigation.fragment)
implementation(libs.google.play.auth)
implementation(libs.appcompat)
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
implementation(libs.kotlin.jdk7)
implementation(libs.kotlin.reflect)
implementation(libs.core.splashscreen) { exclude(module = libs.core.ktx.get().name) }
testImplementation(libs.bundles.test.implementation)
testImplementation(libs.mockk.android)
testImplementation(libs.turbine)
}

View file

@ -0,0 +1,36 @@
{
"dimension": "buildType",
"flavors": [
{
"name": "dev"
},
{
"name": "staff",
"testingLevel": "\"staff\"",
"appName": "Habitica Staff",
"versionCodeIncrement": 9
},
{
"name": "partners",
"testingLevel": "\"partners\"",
"appName": "Habitica",
"versionCodeIncrement": 7
},
{
"name": "alpha",
"testingLevel": "\"alpha\"",
"appName": "Habitica Alpha",
"versionCodeIncrement": 5
},
{
"name": "beta",
"testingLevel": "\"beta\"",
"versionCodeIncrement": 3
},
{
"name": "prod",
"testingLevel": "\"production\"",
"versionCodeIncrement": 1
}
]
}

View file

@ -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