diff --git a/.gitignore b/.gitignore index 8f91d5adb..3f9ffd26f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,46 @@ .DS_Store /build /captures -.idea/ -app/src/main/res/raw/third_party_licenses -app/src/main/res/raw/third_party_license_metadata -app/release/ +.externalNativeBuild +.idea +/*.iml + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ +out/ +build/ + +# Eclipse project files +.classpath +.project + +# Windows thumbnail db +.DS_Store + +# IDEA/Android Studio project files, because +# the project can be imported from settings.gradle.kts +*.iml +.idea/* +!.idea/copyright +# Keep the code styles. +!/.idea/codeStyles +/.idea/codeStyles/* +!/.idea/codeStyles/Project.xml +!/.idea/codeStyles/codeStyleConfig.xml + +# Gradle cache +.gradle + +# Android Studio captures folder +captures/ + +/app/app-release.apk +app/app.iml +app/manifest-merger-release-report.txt diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 2eece5c3e..000000000 --- a/app/build.gradle +++ /dev/null @@ -1,210 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'com.google.firebase.crashlytics' -apply plugin: 'com.google.android.gms.oss-licenses-plugin' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-parcelize' -apply plugin: 'kotlin-kapt' -apply plugin: 'com.google.dagger.hilt.android' - -apply from: '../config/quality/quality.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - - defaultConfig { - applicationId "org.mifos.mobile" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - // A test runner provided by https://code.google.com/p/android-test-kit/ - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables.useSupportLibrary = true - - ndk { - abiFilters "armeabi-v7a", "x86", "x86_64", "arm64-v8a" - } - - multiDexEnabled true - } - - signingConfigs { - release { - storeFile file("../default_key_store.jks") - storePassword "mifos1234" - keyAlias "mifos-mobile" - keyPassword "mifos1234" - } - } - - buildTypes { - release { - minifyEnabled false - signingConfig signingConfigs.release - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - - - sourceSets { - def commonTestDir = 'src/commonTest/java' - main { - java.srcDir commonTestDir - } - androidTest { - java.srcDir commonTestDir - } - test { - java.srcDir commonTestDir - } - } - - compileOptions { - incremental = false - coreLibraryDesugaringEnabled true - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" - } - - lintOptions { - abortOnError false - disable 'InvalidPackage' - } - - buildFeatures { - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion "1.4.4" - } - - buildFeatures { - viewBinding = true - } - - kapt { - correctErrorTypes = true - } -} - -dependencies { - - implementation project(':ui') - implementation fileTree(dir: 'libs', include: ['*.jar']) - - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.lifecycleExtensionsVersion" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion" - kapt "com.github.Raizlabs.DBFlow:dbflow-processor:$rootProject.dbflowVersion" - implementation "com.github.Raizlabs.DBFlow:dbflow-core:$rootProject.dbflowVersion" - implementation "com.github.Raizlabs.DBFlow:dbflow:$rootProject.dbflowVersion" - implementation "androidx.appcompat:appcompat:$rootProject.supportLibraryVersion" - implementation "com.google.android.material:material:$rootProject.designLibraryVersion" - implementation "androidx.preference:preference:1.0.0" - implementation "com.google.android.gms:play-services-maps:$rootProject.playServicesVersion" - implementation "com.google.firebase:firebase-messaging:$rootProject.firebaseMessagingVersion" - implementation "androidx.recyclerview:recyclerview:$rootProject.recyclerViewVersion" - implementation "androidx.vectordrawable:vectordrawable:$rootProject.vectorDrawablesVersion" - implementation "com.google.android.gms:play-services-oss-licenses:$rootProject.oss_licenses" - implementation "com.isseiaoki:simplecropview:$rootProject.cropviewVersion" - implementation "androidx.activity:activity-ktx:$activity_version" - implementation "androidx.fragment:fragment-ktx:$fragment_version" - - //Country Code picker - implementation "com.hbb20:ccp:$rootProject.countryCodePicker" - implementation 'com.github.ParveshSandila:CountryCodeChooser:1.0' - - //Square dependencies - implementation("com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion") { - // exclude Retrofit’s OkHttp peer-dependency module and define your own module import - exclude module: 'okhttp' - } - implementation "com.squareup.retrofit2:converter-gson:$rootProject.retrofitVersion" - implementation "com.squareup.retrofit2:adapter-rxjava2:$rootProject.retrofitVersion" - implementation "com.squareup.okhttp3:okhttp:$rootProject.okHttp3Version" - implementation "com.squareup.okhttp3:logging-interceptor:$rootProject.okHttp3Version" - - //rxjava Dependencies - implementation "io.reactivex.rxjava2:rxandroid:$rootProject.rxandroidVersion" - implementation "io.reactivex.rxjava2:rxjava:$rootProject.rxjavaVersion" - - //Butter Knife - implementation "com.jakewharton:butterknife:$butterKnifeVersion" - kapt "com.jakewharton:butterknife-compiler:$butterKnifeVersion" - - // Firebase Crashlytics dependency - implementation "com.google.firebase:firebase-crashlytics:$firebaseCrashlyticsVersion" - - //Annotation library - implementation "androidx.annotation:annotation:$rootProject.annotationLibraryVersion" - - //qr code - implementation "com.google.zxing:core:$rootProject.zxingcoreVersion" - implementation "me.dm7.barcodescanner:zxing:$rootProject.zxingbarcodescannerVersion" - - //sweet error dependency - implementation "com.github.therajanmaurya:Sweet-Error:$rootProject.sweeterrorVersion" - - //mifos passcode - implementation "com.mifos.mobile:mifos-passcode:$mifosPasscodeVersion" - - //multidex - implementation "androidx.multidex:multidex:$rootProject.multiDexVersion" - - //TableView - implementation "com.evrencoskun.library:tableview:$rootProject.tableViewVersion" - - //Biometric Authentication - implementation "androidx.biometric:biometric:$rootProject.biometric" - - // Coroutines - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines" - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$rootProject.coroutinesTest" - - // Unit tests dependencies - testImplementation "junit:junit:$rootProject.jUnitVersion" - testImplementation "org.mockito:mockito-core:$rootProject.mockitoVersion" - implementation "org.mockito:mockito-core:$rootProject.mockitoVersion" - implementation "org.mockito:mockito-android:$rootProject.mockitoVersion" - androidTestImplementation "junit:junit:$rootProject.jUnitVersion" - androidTestImplementation "org.mockito:mockito-core:$rootProject.mockitoVersion" - androidTestImplementation "org.mockito:mockito-android:$rootProject.mockitoVersion" - androidTestImplementation "androidx.annotation:annotation:1.0.0" - implementation "androidx.arch.core:core-testing:$rootProject.archCoreVersion" - androidTestImplementation("androidx.test.espresso:espresso-contrib:$rootProject.espressoVersion") { - exclude group: 'com.android.support', module: 'appcompat' - exclude group: 'com.android.support', module: 'support-v4' - exclude group: 'com.android.support', module: 'recyclerview-v7' - exclude group: 'com.android.support', module: 'design' - exclude group: 'com.android.support', module: 'support-annotations' - } - androidTestImplementation "androidx.test.espresso:espresso-core:$rootProject.espressoVersion" - androidTestImplementation "androidx.test:runner:$rootProject.runnerVersion" - androidTestImplementation "androidx.test:rules:$rootProject.rulesVersion" - - implementation 'com.github.rahul-gill.mifos-ui-library:uihouse:alpha-2.1' - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - - // Hilt - implementation("com.google.dagger:hilt-android:2.48") - kapt("com.google.dagger:hilt-android-compiler:2.47") - - // Compose BOM - implementation platform('androidx.compose:compose-bom:2023.08.00') - - // Jetpack Compose - implementation "androidx.compose.material:material:$rootProject.composeVersion" - implementation "androidx.compose.compiler:compiler:$rootProject.composeCompiler" - implementation "androidx.compose.ui:ui-tooling-preview:$rootProject.composeVersion" - implementation "androidx.activity:activity-compose:$rootProject.composeActivity" - debugImplementation "androidx.compose.ui:ui-tooling:$rootProject.composeVersion" - implementation "androidx.compose.material3:material3:$rootProject.materialVersion" - implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$rootProject.lifecycleVersion" - implementation "androidx.compose.material:material-icons-extended:$rootProject.composeVersion" - -} -apply plugin: 'com.google.gms.google-services' diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 000000000..242a1cf0c --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,174 @@ +plugins { + alias(libs.plugins.mifos.android.application) + alias(libs.plugins.mifos.android.application.compose) + alias(libs.plugins.mifos.android.hilt) + alias(libs.plugins.mifos.android.application.firebase) + id("com.google.android.gms.oss-licenses-plugin") + id("kotlin-parcelize") + alias(libs.plugins.roborazzi) +} + +apply(from = "../config/quality/quality.gradle") + +android { + namespace = "org.mifos.mobile" + defaultConfig { + applicationId = "org.mifos.mobile" + versionCode = 1 + versionName = "1.0" + vectorDrawables.useSupportLibrary = true + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + ndk { + abiFilters.addAll(arrayOf("armeabi-v7a", "x86", "x86_64", "arm64-v8a")) + } + multiDexEnabled = true + } + + signingConfigs { + create("release") { + storeFile = file("../default_key_store.jks") + storePassword = "mifos1234" + keyAlias = "mifos-mobile" + keyPassword = "mifos1234" + } + } + + buildTypes { + release { + isMinifyEnabled = false + isDebuggable = false + signingConfig = signingConfigs.getByName("release") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + sourceSets { + val commonTestDir = "src/commonTest/java" + getByName("main"){ + java.srcDir(commonTestDir) + } + getByName("androidTest"){ + java.srcDir(commonTestDir) + } + getByName("test"){ + java.srcDir(commonTestDir) + } + } + + buildFeatures { + dataBinding = true + viewBinding = true + compose = true + buildConfig = true + } + + lint { + abortOnError = false + disable.add("InvalidPackage") + } +} + +dependencies { + implementation(projects.ui) + + implementation("androidx.legacy:legacy-support-v4:1.0.0") + implementation(libs.androidx.lifecycle.ktx) + implementation(libs.androidx.lifecycle.extensions) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.preference) + implementation(libs.play.services.maps) + + // DBFlow + implementation(libs.dbflow) + kapt(libs.dbflow.processor) + implementation(libs.dbflow.core) + + implementation("androidx.recyclerview:recyclerview:1.2.1") + implementation("androidx.vectordrawable:vectordrawable:1.1.0") + implementation(libs.google.oss.licenses) + implementation("com.isseiaoki:simplecropview:1.1.8") + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.fragment.ktx) + + //Country Code picker + implementation("com.hbb20:ccp:2.7.2") + implementation("com.github.ParveshSandila:CountryCodeChooser:1.0") + + //Square dependencies + implementation(libs.squareup.retrofit2) { + // exclude Retrofit’s OkHttp peer-dependency module and define your own module import + exclude(module = "okhttp") + } + implementation(libs.squareup.retrofit.adapter.rxjava) + implementation(libs.squareup.retrofit.converter.gson) + implementation(libs.squareup.okhttp) + implementation(libs.squareup.logging.interceptor) + + //rxjava Dependencies + implementation(libs.reactivex.rxjava2.android) + implementation(libs.reactivex.rxjava2) + + //Butter Knife + implementation(libs.jakewharton.butterknife) + implementation(libs.jakewharton.compiler) + + //Annotation library + implementation("androidx.annotation:annotation:1.1.0") + + //qr code + implementation("com.google.zxing:core:3.5.2") + implementation("me.dm7.barcodescanner:zxing:1.9.13") + + //sweet error dependency + implementation("com.github.therajanmaurya:Sweet-Error:1.0.0") + + //mifos passcode + implementation("com.mifos.mobile:mifos-passcode:1.0.0") + + //multidex + implementation("androidx.multidex:multidex:2.0.1") + + //TableView + implementation("com.evrencoskun.library:tableview:0.8.9.4") + + //Biometric Authentication + implementation("androidx.biometric:biometric:1.1.0") + + // Coroutines + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") + + // Unit tests dependencies + testImplementation(libs.junit) + testImplementation("org.mockito:mockito-core:5.4.0") + implementation("org.mockito:mockito-core:5.4.0") + implementation("org.mockito:mockito-android:5.4.0") + androidTestImplementation((libs.junit)) + androidTestImplementation("org.mockito:mockito-core:5.4.0") + androidTestImplementation("org.mockito:mockito-android:5.4.0") + androidTestImplementation("androidx.annotation:annotation:1.0.0") + implementation("androidx.arch.core:core-testing:2.2.0") + androidTestImplementation("androidx.test.espresso:espresso-contrib:3.5.1") { + + } + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.test:runner:1.6.0-alpha04") + androidTestImplementation("androidx.test:rules:1.6.0-alpha01") + + implementation("com.github.rahul-gill.mifos-ui-library:uihouse:alpha-2.1") + + // Jetpack Compose + api(libs.androidx.activity.compose) + api(libs.androidx.compose.material3) + api(libs.androidx.compose.foundation) + api(libs.androidx.compose.foundation.layout) + api(libs.androidx.compose.material.iconsExtended) + api(libs.androidx.compose.runtime) + api(libs.androidx.compose.ui.tooling.preview) + api(libs.androidx.compose.ui.util) + + debugApi(libs.androidx.compose.ui.tooling) +} + + diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 0178ce76d..83bf0ff34 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -2,7 +2,7 @@ # By default, the flags in this file are appended to flags specified # in /Users/ishan/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# directive in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/app/src/debug/java/org/mifos/mobile/api/SelfServiceInterceptor.kt b/app/src/debug/java/org/mifos/mobile/api/SelfServiceInterceptor.kt index 739003e09..bb645bfd5 100644 --- a/app/src/debug/java/org/mifos/mobile/api/SelfServiceInterceptor.kt +++ b/app/src/debug/java/org/mifos/mobile/api/SelfServiceInterceptor.kt @@ -20,10 +20,10 @@ class SelfServiceInterceptor(private val preferencesHelper: PreferencesHelper) : override fun intercept(chain: Interceptor.Chain): Response { val chainRequest = chain.request() val builder = chainRequest.newBuilder() - .header(HEADER_TENANT, preferencesHelper.tenant) + .header(HEADER_TENANT, preferencesHelper.tenant!!) .header(CONTENT_TYPE, "application/json") if (!TextUtils.isEmpty(preferencesHelper.token)) { - builder.header(HEADER_AUTH, preferencesHelper.token) + builder.header(HEADER_AUTH, preferencesHelper.token!!) } val request = builder.build() return chain.proceed(request) diff --git a/app/src/main/java/org/mifos/mobile/ui/about/AboutUsScreen.kt b/app/src/main/java/org/mifos/mobile/ui/about/AboutUsScreen.kt index b5d800a28..5d9aef698 100644 --- a/app/src/main/java/org/mifos/mobile/ui/about/AboutUsScreen.kt +++ b/app/src/main/java/org/mifos/mobile/ui/about/AboutUsScreen.kt @@ -3,16 +3,13 @@ package org.mifos.mobile.ui.about import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import org.mifos.mobile.core.ui.component.AboutUsItemCard import org.mifos.mobile.core.ui.component.MifosItemCard import org.mifos.mobile.ui.enums.AboutUsListItemId -import java.util.* @Composable fun AboutUsScreen(viewModel: AboutUsViewModel) { diff --git a/app/src/main/java/org/mifos/mobile/ui/activities/HomeActivity.kt b/app/src/main/java/org/mifos/mobile/ui/activities/HomeActivity.kt index ea5b6c47e..f8bed8dd8 100644 --- a/app/src/main/java/org/mifos/mobile/ui/activities/HomeActivity.kt +++ b/app/src/main/java/org/mifos/mobile/ui/activities/HomeActivity.kt @@ -437,8 +437,7 @@ class HomeActivity : val resultCode = apiAvailability.isGooglePlayServicesAvailable(this) if (resultCode != ConnectionResult.SUCCESS) { if (apiAvailability.isUserResolvableError(resultCode)) { - apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST) - .show() + apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST)?.show() } else { Log.i(HomeActivity::class.java.name, "This device is not supported.") finish() diff --git a/app/src/main/java/org/mifos/mobile/ui/login/LoginScreen.kt b/app/src/main/java/org/mifos/mobile/ui/login/LoginScreen.kt index c81d04aec..af1eaa45d 100644 --- a/app/src/main/java/org/mifos/mobile/ui/login/LoginScreen.kt +++ b/app/src/main/java/org/mifos/mobile/ui/login/LoginScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput @@ -46,6 +47,7 @@ import org.mifos.mobile.R import org.mifos.mobile.core.ui.component.MifosMobileIcon import org.mifos.mobile.core.ui.component.MifosOutlinedTextField +@OptIn(ExperimentalComposeUiApi::class) @Composable fun LoginScreen( login: (username: String, password: String) -> Unit, diff --git a/app/src/main/java/org/mifos/mobile/ui/registration/RegistrationScreen.kt b/app/src/main/java/org/mifos/mobile/ui/registration/RegistrationScreen.kt index 55f6b0f1e..da1acd358 100644 --- a/app/src/main/java/org/mifos/mobile/ui/registration/RegistrationScreen.kt +++ b/app/src/main/java/org/mifos/mobile/ui/registration/RegistrationScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput @@ -49,6 +50,7 @@ import org.mifos.mobile.core.ui.component.MifosOutlinedTextField * @since 28/12/2023 */ +@OptIn(ExperimentalComposeUiApi::class) @Composable fun RegistrationScreen( register: (accountNumber: String, username: String, firstName: String, lastName: String, phoneNumber: String, email: String, password: String, authMode: String, countryCode: String) -> Unit, diff --git a/app/src/main/java/org/mifos/mobile/ui/user_profile/UserProfileScreen.kt b/app/src/main/java/org/mifos/mobile/ui/user_profile/UserProfileScreen.kt index 2c804913b..ad31f0ba3 100644 --- a/app/src/main/java/org/mifos/mobile/ui/user_profile/UserProfileScreen.kt +++ b/app/src/main/java/org/mifos/mobile/ui/user_profile/UserProfileScreen.kt @@ -11,7 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Divider +import androidx.compose.material3.Divider import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color diff --git a/app/src/main/java/org/mifos/mobile/utils/fcm/RegistrationIntentService.kt b/app/src/main/java/org/mifos/mobile/utils/fcm/RegistrationIntentService.kt index 63f7ece41..31baa5157 100644 --- a/app/src/main/java/org/mifos/mobile/utils/fcm/RegistrationIntentService.kt +++ b/app/src/main/java/org/mifos/mobile/utils/fcm/RegistrationIntentService.kt @@ -20,12 +20,12 @@ import android.content.Intent import android.util.Log import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.google.android.gms.tasks.OnCompleteListener -import com.google.firebase.iid.FirebaseInstanceId +import com.google.firebase.messaging.FirebaseMessaging import org.mifos.mobile.utils.Constants class RegistrationIntentService : IntentService(TAG) { override fun onHandleIntent(intent: Intent?) { - FirebaseInstanceId.getInstance().instanceId + FirebaseMessaging.getInstance().token .addOnCompleteListener( OnCompleteListener { task -> if (!task.isSuccessful) { @@ -34,7 +34,7 @@ class RegistrationIntentService : IntentService(TAG) { } // Get new Instance ID token - val token = task.result?.token + val token = task.result sendRegistrationToServer(token) }, ) diff --git a/build-logic/README.md b/build-logic/README.md new file mode 100644 index 000000000..f0ea8ede9 --- /dev/null +++ b/build-logic/README.md @@ -0,0 +1,38 @@ +# Convention Plugins + +The `build-logic` folder defines project-specific convention plugins, used to keep a single +source of truth for common module configurations. + +This approach is heavily based on +[https://developer.squareup.com/blog/herding-elephants/](https://developer.squareup.com/blog/herding-elephants/) +and +[https://github.com/jjohannes/idiomatic-gradle](https://github.com/jjohannes/idiomatic-gradle). + +By setting up convention plugins in `build-logic`, we can avoid duplicated build script setup, +messy `subproject` configurations, without the pitfalls of the `buildSrc` directory. + +`build-logic` is an included build, as configured in the root +[`settings.gradle.kts`](../settings.gradle.kts). + +Inside `build-logic` is a `convention` module, which defines a set of plugins that all normal +modules can use to configure themselves. + +`build-logic` also includes a set of `Kotlin` files used to share logic between plugins themselves, +which is most useful for configuring Android components (libraries vs applications) with shared +code. + +These plugins are *additive* and *composable*, and try to only accomplish a single responsibility. +Modules can then pick and choose the configurations they need. +If there is one-off logic for a module without shared code, it's preferable to define that directly +in the module's `build.gradle`, as opposed to creating a convention plugin with module-specific +setup. + +Current list of convention plugins: + +- [`mifos.android.application`](convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt), + [`mifos.android.library`](convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt), + [`mifos.android.test`](convention/src/main/kotlin/AndroidTestConventionPlugin.kt): + Configures common Android and Kotlin options. +- [`mifos.android.application.compose`](convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt), + [`mifos.android.library.compose`](convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt): + Configures Jetpack Compose options diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts new file mode 100644 index 000000000..fce3f645e --- /dev/null +++ b/build-logic/convention/build.gradle.kts @@ -0,0 +1,86 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` +} + +group = "org.mifos.mobile.buildlogic" + +// Configure the build-logic plugins to target JDK 17 +// This matches the JDK used to build the project, and is not related to what is running on device. +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} +tasks.withType().configureEach { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } +} + +dependencies { + compileOnly(libs.android.gradlePlugin) + compileOnly(libs.android.tools.common) + compileOnly(libs.firebase.crashlytics.gradlePlugin) + compileOnly(libs.firebase.performance.gradlePlugin) + compileOnly(libs.kotlin.gradlePlugin) + compileOnly(libs.ksp.gradlePlugin) + compileOnly(libs.room.gradlePlugin) + implementation(libs.truth) +} + +tasks { + validatePlugins { + enableStricterValidation = true + failOnWarning = true + } +} + +gradlePlugin { + plugins { + register("androidApplicationCompose") { + id = "mifos.android.application.compose" + implementationClass = "AndroidApplicationComposeConventionPlugin" + } + register("androidApplication") { + id = "mifos.android.application" + implementationClass = "AndroidApplicationConventionPlugin" + } + register("androidHilt") { + id = "mifos.android.hilt" + implementationClass = "AndroidHiltConventionPlugin" + } + register("androidLibraryCompose") { + id = "mifos.android.library.compose" + implementationClass = "AndroidLibraryComposeConventionPlugin" + } + register("androidLibrary") { + id = "mifos.android.library" + implementationClass = "AndroidLibraryConventionPlugin" + } + register("androidFeature") { + id = "mifos.android.feature" + implementationClass = "AndroidFeatureConventionPlugin" + } + register("androidTest") { + id = "mifos.android.test" + implementationClass = "AndroidTestConventionPlugin" + } + register("androidRoom") { + id = "mifos.android.room" + implementationClass = "AndroidRoomConventionPlugin" + } + register("androidFirebase") { + id = "mifos.android.application.firebase" + implementationClass = "AndroidApplicationFirebaseConventionPlugin" + } + register("androidLint") { + id = "mifos.android.lint" + implementationClass = "AndroidLintConventionPlugin" + } + register("jvmLibrary") { + id = "mifos.jvm.library" + implementationClass = "JvmLibraryConventionPlugin" + } + } +} diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt new file mode 100644 index 000000000..c4a4f0bb2 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt @@ -0,0 +1,16 @@ +import com.android.build.api.dsl.ApplicationExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.getByType +import org.mifos.mobile.configureAndroidCompose + +class AndroidApplicationComposeConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply("com.android.application") + + val extension = extensions.getByType() + configureAndroidCompose(extension) + } + } +} diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt new file mode 100644 index 000000000..14897278b --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt @@ -0,0 +1,32 @@ +import com.android.build.api.dsl.ApplicationExtension +import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.gradle.BaseExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.getByType +import org.mifos.mobile.configureBadgingTasks +import org.mifos.mobile.configureKotlinAndroid +import org.mifos.mobile.configurePrintApksTask + +class AndroidApplicationConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.application") + apply("org.jetbrains.kotlin.android") + apply("mifos.android.lint") + apply("com.dropbox.dependency-guard") + } + + extensions.configure { + configureKotlinAndroid(this) + defaultConfig.targetSdk = 34 + } + extensions.configure { + configurePrintApksTask(this) + configureBadgingTasks(extensions.getByType(), this) + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt new file mode 100644 index 000000000..3820afca3 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt @@ -0,0 +1,39 @@ +import com.android.build.api.dsl.ApplicationExtension +import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.mifos.mobile.libs + +class AndroidApplicationFirebaseConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.google.gms.google-services") + apply("com.google.firebase.firebase-perf") + apply("com.google.firebase.crashlytics") + } + + dependencies { + val bom = libs.findLibrary("firebase-bom").get() + add("implementation", platform(bom)) + "implementation"(libs.findLibrary("firebase.analytics").get()) + "implementation"(libs.findLibrary("firebase.performance").get()) + "implementation"(libs.findLibrary("firebase.crashlytics").get()) + "implementation"(libs.findLibrary("firebase.cloud.messaging").get()) + } + + extensions.configure { + buildTypes.configureEach { + // Disable the Crashlytics mapping file upload. This feature should only be + // enabled if a Firebase backend is available and configured in + // google-services.json. + configure { + mappingFileUploadEnabled = false + } + } + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt new file mode 100644 index 000000000..3b445cf1f --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -0,0 +1,32 @@ +import com.android.build.gradle.LibraryExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.mifos.mobile.libs + +class AndroidFeatureConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply { + apply("mifos.android.library") + apply("mifos.android.hilt") + } + extensions.configure { + defaultConfig { + // set custom test runner + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + } + + dependencies { + add("implementation", project(":ui")) + //add("implementation", project(":core:designsystem")) + + add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get()) + add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get()) + add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get()) + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt new file mode 100644 index 000000000..a0d1fb1d3 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt @@ -0,0 +1,25 @@ + +import org.mifos.mobile.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies + +class AndroidHiltConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("dagger.hilt.android.plugin") + // KAPT must go last to avoid build warnings. + // See: https://stackoverflow.com/questions/70550883/warning-the-following-options-were-not-recognized-by-any-processor-dagger-f + apply("org.jetbrains.kotlin.kapt") + } + + dependencies { + "implementation"(libs.findLibrary("hilt.android").get()) + "kapt"(libs.findLibrary("hilt.compiler").get()) + "kaptAndroidTest"(libs.findLibrary("hilt.compiler").get()) + "kaptTest"(libs.findLibrary("hilt.compiler").get()) + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt new file mode 100644 index 000000000..8230f220a --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt @@ -0,0 +1,17 @@ +import com.android.build.gradle.LibraryExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.getByType +import org.mifos.mobile.configureAndroidCompose + +class AndroidLibraryComposeConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply("com.android.library") + + val extension = extensions.getByType() + configureAndroidCompose(extension) + } + } + +} diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt new file mode 100644 index 000000000..61fa7ed2d --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -0,0 +1,37 @@ +import com.android.build.api.variant.LibraryAndroidComponentsExtension +import com.android.build.gradle.LibraryExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.kotlin +import org.mifos.mobile.configureKotlinAndroid +import org.mifos.mobile.configurePrintApksTask +import org.mifos.mobile.disableUnnecessaryAndroidTests + +class AndroidLibraryConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.library") + apply("org.jetbrains.kotlin.android") + apply("mifos.android.lint") + } + + extensions.configure { + configureKotlinAndroid(this) + defaultConfig.targetSdk = 34 + // The resource prefix is derived from the module name, + // so resources inside ":core:module1" must be prefixed with "core_module1_" + resourcePrefix = path.split("""\W""".toRegex()).drop(1).distinct().joinToString(separator = "_").lowercase() + "_" + } + extensions.configure { + configurePrintApksTask(this) + disableUnnecessaryAndroidTests(target) + } + dependencies { + add("testImplementation", kotlin("test")) + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt new file mode 100644 index 000000000..54246d61e --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt @@ -0,0 +1,30 @@ +import com.android.build.api.dsl.ApplicationExtension +import com.android.build.api.dsl.LibraryExtension +import com.android.build.api.dsl.Lint +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure + +class AndroidLintConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + when { + pluginManager.hasPlugin("com.android.application") -> + configure { lint(Lint::configure) } + + pluginManager.hasPlugin("com.android.library") -> + configure { lint(Lint::configure) } + + else -> { + pluginManager.apply("com.android.lint") + configure(Lint::configure) + } + } + } + } +} + +private fun Lint.configure() { + xmlReport = true + checkDependencies = true +} diff --git a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt new file mode 100644 index 000000000..332cd7c20 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt @@ -0,0 +1,29 @@ +import androidx.room.gradle.RoomExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.mifos.mobile.libs + +class AndroidRoomConventionPlugin : Plugin { + + override fun apply(target: Project) { + with(target) { + pluginManager.apply("androidx.room") + pluginManager.apply("com.google.devtools.ksp") + + extensions.configure { + // The schemas directory contains a schema file for each version of the Room database. + // This is required to enable Room auto migrations. + // See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration. + schemaDirectory("$projectDir/schemas") + } + + dependencies { + add("implementation", libs.findLibrary("room.runtime").get()) + add("implementation", libs.findLibrary("room.ktx").get()) + add("ksp", libs.findLibrary("room.compiler").get()) + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt new file mode 100644 index 000000000..4f01e5dbd --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt @@ -0,0 +1,21 @@ +import com.android.build.gradle.TestExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.mifos.mobile.configureKotlinAndroid + +class AndroidTestConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.test") + apply("org.jetbrains.kotlin.android") + } + + extensions.configure { + configureKotlinAndroid(this) + defaultConfig.targetSdk = 34 + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt new file mode 100644 index 000000000..d5045d8ee --- /dev/null +++ b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt @@ -0,0 +1,15 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.mifos.mobile.configureKotlinJvm + +class JvmLibraryConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("org.jetbrains.kotlin.jvm") + apply("mifos.android.lint") + } + configureKotlinJvm() + } + } +} diff --git a/build-logic/convention/src/main/kotlin/org/mifos/mobile/AndroidCompose.kt b/build-logic/convention/src/main/kotlin/org/mifos/mobile/AndroidCompose.kt new file mode 100644 index 000000000..1836ba3a4 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/mobile/AndroidCompose.kt @@ -0,0 +1,69 @@ +package org.mifos.mobile + +import com.android.build.api.dsl.CommonExtension +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +/** + * Configure Compose-specific options + */ +internal fun Project.configureAndroidCompose( + commonExtension: CommonExtension<*, *, *, *, *>, +) { + commonExtension.apply { + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.findVersion("androidxComposeCompiler").get().toString() + } + + dependencies { + val bom = libs.findLibrary("androidx-compose-bom").get() + add("implementation", platform(bom)) + add("androidTestImplementation", platform(bom)) + } + + testOptions { + unitTests { + // For Robolectric + isIncludeAndroidResources = true + } + } + } + + tasks.withType().configureEach { + kotlinOptions { + freeCompilerArgs = freeCompilerArgs + buildComposeMetricsParameters() + } + } +} + +private fun Project.buildComposeMetricsParameters(): List { + val metricParameters = mutableListOf() + val enableMetricsProvider = project.providers.gradleProperty("enableComposeCompilerMetrics") + val relativePath = projectDir.relativeTo(rootDir) + val buildDir = layout.buildDirectory.get().asFile + val enableMetrics = (enableMetricsProvider.orNull == "true") + if (enableMetrics) { + val metricsFolder = buildDir.resolve("compose-metrics").resolve(relativePath) + metricParameters.add("-P") + metricParameters.add( + "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + metricsFolder.absolutePath + ) + } + + val enableReportsProvider = project.providers.gradleProperty("enableComposeCompilerReports") + val enableReports = (enableReportsProvider.orNull == "true") + if (enableReports) { + val reportsFolder = buildDir.resolve("compose-reports").resolve(relativePath) + metricParameters.add("-P") + metricParameters.add( + "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + reportsFolder.absolutePath + ) + } + return metricParameters.toList() +} diff --git a/build-logic/convention/src/main/kotlin/org/mifos/mobile/AndroidInstrumentedTests.kt b/build-logic/convention/src/main/kotlin/org/mifos/mobile/AndroidInstrumentedTests.kt new file mode 100644 index 000000000..1e93dec84 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/mobile/AndroidInstrumentedTests.kt @@ -0,0 +1,19 @@ +package org.mifos.mobile + +import com.android.build.api.variant.LibraryAndroidComponentsExtension +import org.gradle.api.Project + +/** + * Disable unnecessary Android instrumented tests for the [project] if there is no `androidTest` folder. + * Otherwise, these projects would be compiled, packaged, installed and ran only to end-up with the following message: + * + * > Starting 0 tests on AVD + * + * Note: this could be improved by checking other potential sourceSets based on buildTypes and flavors. + */ +internal fun LibraryAndroidComponentsExtension.disableUnnecessaryAndroidTests( + project: Project, +) = beforeVariants { + it.enableAndroidTest = it.enableAndroidTest + && project.projectDir.resolve("src/androidTest").exists() +} diff --git a/build-logic/convention/src/main/kotlin/org/mifos/mobile/Badging.kt b/build-logic/convention/src/main/kotlin/org/mifos/mobile/Badging.kt new file mode 100644 index 000000000..3d52f8123 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/mobile/Badging.kt @@ -0,0 +1,144 @@ +package org.mifos.mobile + +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.gradle.BaseExtension +import com.android.SdkConstants +import com.google.common.truth.Truth.assertWithMessage +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.configurationcache.extensions.capitalized +import org.gradle.kotlin.dsl.register +import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.process.ExecOperations +import java.io.File +import javax.inject.Inject + +@CacheableTask +abstract class GenerateBadgingTask : DefaultTask() { + + @get:OutputFile + abstract val badging: RegularFileProperty + + @get:PathSensitive(PathSensitivity.NONE) + @get:InputFile + abstract val apk: RegularFileProperty + + @get:PathSensitive(PathSensitivity.NONE) + @get:InputFile + abstract val aapt2Executable: RegularFileProperty + + @get:Inject + abstract val execOperations: ExecOperations + + @TaskAction + fun taskAction() { + execOperations.exec { + commandLine( + aapt2Executable.get().asFile.absolutePath, + "dump", + "badging", + apk.get().asFile.absolutePath, + ) + standardOutput = badging.asFile.get().outputStream() + } + } +} + +@CacheableTask +abstract class CheckBadgingTask : DefaultTask() { + + // In order for the task to be up-to-date when the inputs have not changed, + // the task must declare an output, even if it's not used. Tasks with no + // output are always run regardless of whether the inputs changed + @get:OutputDirectory + abstract val output: DirectoryProperty + + @get:PathSensitive(PathSensitivity.NONE) + @get:InputFile + abstract val goldenBadging: RegularFileProperty + + @get:PathSensitive(PathSensitivity.NONE) + @get:InputFile + abstract val generatedBadging: RegularFileProperty + + @get:Input + abstract val updateBadgingTaskName: Property + + override fun getGroup(): String = LifecycleBasePlugin.VERIFICATION_GROUP + + @TaskAction + fun taskAction() { + assertWithMessage( + "Generated badging is different from golden badging! " + + "If this change is intended, run ./gradlew ${updateBadgingTaskName.get()}", + ) + .that(generatedBadging.get().asFile.readText()) + .isEqualTo(goldenBadging.get().asFile.readText()) + } +} + +fun Project.configureBadgingTasks( + baseExtension: BaseExtension, + componentsExtension: ApplicationAndroidComponentsExtension, +) { + // Registers a callback to be called, when a new variant is configured + componentsExtension.onVariants { variant -> + // Registers a new task to verify the app bundle. + val capitalizedVariantName = variant.name.capitalized() + val generateBadgingTaskName = "generate${capitalizedVariantName}Badging" + val generateBadging = + tasks.register(generateBadgingTaskName) { + apk.set( + variant.artifacts.get(SingleArtifact.APK_FROM_BUNDLE), + ) + aapt2Executable.set( + File( + baseExtension.sdkDirectory, + "${SdkConstants.FD_BUILD_TOOLS}/" + + "${baseExtension.buildToolsVersion}/" + + SdkConstants.FN_AAPT2, + ), + ) + + badging.set( + project.layout.buildDirectory.file( + "outputs/apk_from_bundle/${variant.name}/${variant.name}-badging.txt", + ), + ) + } + + val updateBadgingTaskName = "update${capitalizedVariantName}Badging" + tasks.register(updateBadgingTaskName) { + from(generateBadging.get().badging) + into(project.layout.projectDirectory) + } + + val checkBadgingTaskName = "check${capitalizedVariantName}Badging" + tasks.register(checkBadgingTaskName) { + goldenBadging.set( + project.layout.projectDirectory.file("${variant.name}-badging.txt"), + ) + generatedBadging.set( + generateBadging.get().badging, + ) + this.updateBadgingTaskName.set(updateBadgingTaskName) + + output.set( + project.layout.buildDirectory.dir("intermediates/$checkBadgingTaskName"), + ) + } + } +} diff --git a/build-logic/convention/src/main/kotlin/org/mifos/mobile/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/org/mifos/mobile/KotlinAndroid.kt new file mode 100644 index 000000000..35720b5ce --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/mobile/KotlinAndroid.kt @@ -0,0 +1,75 @@ +package org.mifos.mobile + +import com.android.build.api.dsl.CommonExtension +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +/** + * Configure base Kotlin with Android options + */ +internal fun Project.configureKotlinAndroid( + commonExtension: CommonExtension<*, *, *, *, *>, +) { + commonExtension.apply { + compileSdk = 34 + + defaultConfig { + minSdk = 24 + } + + compileOptions { + // Up to Java 11 APIs are available through desugaring + // https://developer.android.com/studio/write/java11-minimal-support-table + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + isCoreLibraryDesugaringEnabled = true + } + } + + configureKotlin() + + dependencies { + add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get()) + } +} + +/** + * Configure base Kotlin options for JVM (non-Android) + */ +internal fun Project.configureKotlinJvm() { + extensions.configure { + // Up to Java 11 APIs are available through desugaring + // https://developer.android.com/studio/write/java11-minimal-support-table + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + configureKotlin() +} + +/** + * Configure base Kotlin options + */ +private fun Project.configureKotlin() { + // Use withType to workaround https://youtrack.jetbrains.com/issue/KT-55947 + tasks.withType().configureEach { + kotlinOptions { + // Set JVM target to 11 + jvmTarget = JavaVersion.VERSION_17.toString() + // Treat all Kotlin warnings as errors (disabled by default) + // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties + val warningsAsErrors: String? by project + allWarningsAsErrors = warningsAsErrors.toBoolean() + freeCompilerArgs = freeCompilerArgs + listOf( + // Enable experimental coroutines APIs, including Flow + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + ) + } + } +} diff --git a/build-logic/convention/src/main/kotlin/org/mifos/mobile/PrintTestApks.kt b/build-logic/convention/src/main/kotlin/org/mifos/mobile/PrintTestApks.kt new file mode 100644 index 000000000..3e0ce77e0 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/mobile/PrintTestApks.kt @@ -0,0 +1,87 @@ +package org.mifos.mobile + +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.BuiltArtifactsLoader +import com.android.build.api.variant.HasAndroidTest +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault +import java.io.File + +internal fun Project.configurePrintApksTask(extension: AndroidComponentsExtension<*, *, *>) { + extension.onVariants { variant -> + if (variant is HasAndroidTest) { + val loader = variant.artifacts.getBuiltArtifactsLoader() + val artifact = variant.androidTest?.artifacts?.get(SingleArtifact.APK) + val javaSources = variant.androidTest?.sources?.java?.all + val kotlinSources = variant.androidTest?.sources?.kotlin?.all + + val testSources = if (javaSources != null && kotlinSources != null) { + javaSources.zip(kotlinSources) { javaDirs, kotlinDirs -> + javaDirs + kotlinDirs + } + } else javaSources ?: kotlinSources + + if (artifact != null && testSources != null) { + tasks.register( + "${variant.name}PrintTestApk", + PrintApkLocationTask::class.java + ) { + apkFolder.set(artifact) + builtArtifactsLoader.set(loader) + variantName.set(variant.name) + sources.set(testSources) + } + } + } + } +} + +@DisableCachingByDefault(because = "Prints output") +internal abstract class PrintApkLocationTask : DefaultTask() { + + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:InputDirectory + abstract val apkFolder: DirectoryProperty + + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:InputFiles + abstract val sources: ListProperty + + @get:Internal + abstract val builtArtifactsLoader: Property + + @get:Input + abstract val variantName: Property + + @TaskAction + fun taskAction() { + val hasFiles = sources.orNull?.any { directory -> + directory.asFileTree.files.any { + it.isFile && "build${File.separator}generated" !in it.parentFile.path + } + } ?: throw RuntimeException("Cannot check androidTest sources") + + // Don't print APK location if there are no androidTest source files + if (!hasFiles) return + + val builtArtifacts = builtArtifactsLoader.get().load(apkFolder.get()) + ?: throw RuntimeException("Cannot load APKs") + if (builtArtifacts.elements.size != 1) + throw RuntimeException("Expected one APK !") + val apk = File(builtArtifacts.elements.single().outputFile).toPath() + println(apk) + } +} \ No newline at end of file diff --git a/build-logic/convention/src/main/kotlin/org/mifos/mobile/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/org/mifos/mobile/ProjectExtensions.kt new file mode 100644 index 000000000..797c2a5be --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/mobile/ProjectExtensions.kt @@ -0,0 +1,9 @@ +package org.mifos.mobile + +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +val Project.libs + get(): VersionCatalog = extensions.getByType().named("libs") diff --git a/build-logic/gradle.properties b/build-logic/gradle.properties new file mode 100644 index 000000000..1c9073eb9 --- /dev/null +++ b/build-logic/gradle.properties @@ -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 diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 000000000..de9224e22 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" +include(":convention") diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 7645375df..000000000 --- a/build.gradle +++ /dev/null @@ -1,98 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - repositories { - google() - mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" - } - jcenter() - } - - ext { - gradleVersion = '7.2.0' - crashlyticsGradleVersion = '2.9.9' - ossLicensesVersion = '0.10.5' - googleServicesVersion = '4.3.15' - kotlinGradlePluginversion = '1.6.21' - } - - dependencies { - classpath "com.android.tools.build:gradle:$gradleVersion" - classpath "com.google.firebase:firebase-crashlytics-gradle:$crashlyticsGradleVersion" - classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6' - classpath "com.google.gms:google-services:$googleServicesVersion" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinGradlePluginversion" - classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.1.2" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} -plugins { - id("com.google.dagger.hilt.android") version "2.48" apply false - id 'org.jetbrains.kotlin.android' version '1.8.10' apply false -} -allprojects { - repositories { - google() - jcenter() - mavenCentral() - maven { url "https://www.jitpack.io" } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} - -ext { - // Sdk and tools - minSdkVersion = 24 - targetSdkVersion = 34 - compileSdkVersion = 34 - - // App dependencies - countryCodePicker = '2.7.2' - supportLibraryVersion = '1.4.2' - designLibraryVersion = '1.9.0' - recyclerViewVersion = '1.2.1' - vectorDrawablesVersion = '1.1.0' - lifecycleVersion = '2.4.1' - lifecycleExtensionsVersion = '2.2.0' - retrofitVersion = '2.9.0' - coroutinesTest = '1.7.3' - okHttp3Version = '3.14.9' - butterKnifeVersion = '8.0.1' - dbflowVersion = '4.2.4' - playServicesVersion = '17.0.1' - firebaseMessagingVersion = '21.1.0' - oss_licenses = '17.0.0' - kotlinVersion = '1.6.10' - tableViewVersion = '0.8.9.4' - biometric = '1.1.0' - archCoreVersion = '2.2.0' - coroutines = '1.6.4' - jUnitVersion = '4.13.2' - mockitoVersion = '5.4.0' - runnerVersion = '1.6.0-alpha04' - rulesVersion = '1.6.0-alpha01' - espressoVersion = '3.5.1' - zxingcoreVersion = '3.5.2' - zxingbarcodescannerVersion = '1.9.13' - rxjavaVersion = '2.2.21' - rxandroidVersion = '2.1.1' - sweeterrorVersion = '1.0.0' - mifosPasscodeVersion = '1.0.0' - cropviewVersion = '1.1.8' - multiDexVersion = '2.0.1' - annotationLibraryVersion ='1.1.0' - firebaseCrashlyticsVersion = '18.4.1' - activity_version = '1.5.0' - fragment_version = '1.5.0' - composeVersion = '1.6.0-alpha05' - composeCompiler = '1.3.2' - composeActivity = '1.7.2' - materialVersion = '1.1.0' - lifecycleVersion = '2.6.1' -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..86cf1f78e --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,32 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath(libs.google.oss.licenses.plugin) { + exclude(group = "com.google.protobuf") + } + } +} + +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.android.test) apply false + alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.kotlin.parcelize) apply false + alias(libs.plugins.dependencyGuard) apply false + alias(libs.plugins.firebase.crashlytics) apply false + alias(libs.plugins.firebase.perf) apply false + alias(libs.plugins.gms) apply false + alias(libs.plugins.hilt) apply false + alias(libs.plugins.ksp) apply false + alias(libs.plugins.roborazzi) apply false + alias(libs.plugins.secrets) apply false + alias(libs.plugins.room) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.spotbugs) apply false +} \ No newline at end of file diff --git a/config/quality/quality.gradle b/config/quality/quality.gradle index 943851a71..23bdf8423 100755 --- a/config/quality/quality.gradle +++ b/config/quality/quality.gradle @@ -32,7 +32,7 @@ task checkstyle(type: Checkstyle, group: 'Verification', description: 'Runs code include '**/*.java' reports { - xml.enabled = true + //xml.enabled = true xml { destination file("$reportsDir/checkstyle/checkstyle.xml") } @@ -41,37 +41,36 @@ task checkstyle(type: Checkstyle, group: 'Verification', description: 'Runs code classpath = files( ) } - - -tasks.matching {task -> task.name.startsWith('spotbugs')}.forEach { - check.dependsOn it - it.dependsOn(['compileDebugSources','compileReleaseSources']) - it.group('Verification') - it.description('Inspect java bytecode for bugs') - - it.ignoreFailures = false - it.effort = "max" - it.reportLevel = "high" - it.excludeFilter = new File("$qualityConfigDir/findbugs/android-exclude-filter.xml") - it.classes = files("$project.rootDir/app/build/intermediates/javac") - - it.source 'src' - it.include '**/*.java' - it.exclude '**/gen/**' - - it.reports { - xml.enabled = false - html.enabled = true - xml { - destination file("$reportsDir/findbugs/findbugs.xml") - } - html { - destination file("$reportsDir/findbugs/findbugs.html") - } - } - - it.classpath = files() -} +// TODO un comment after fixing the plugin +//tasks.matching {task -> task.name.startsWith('spotbugs')}.forEach { +// check.dependsOn it +// it.dependsOn(['compileDebugSources','compileReleaseSources']) +// it.group('Verification') +// it.description('Inspect java bytecode for bugs') +// +// it.ignoreFailures = false +// it.effort = "max" +// it.reportLevel = "high" +// it.excludeFilter = new File("$qualityConfigDir/findbugs/android-exclude-filter.xml") +// it.classes = files("$project.rootDir/app/build/intermediates/javac") +// +// it.source 'src' +// it.include '**/*.java' +// it.exclude '**/gen/**' +// +// it.reports { +// // xml.enabled = false +// // html.enabled = true +// xml { +// destination file("$reportsDir/findbugs/findbugs.xml") +// } +// html { +// destination file("$reportsDir/findbugs/findbugs.html") +// } +// } +// +// it.classpath = files() +//} task pmd(type: Pmd, group: 'Verification', description: 'Inspect sourcecode for bugs') { @@ -84,8 +83,8 @@ task pmd(type: Pmd, group: 'Verification', description: 'Inspect sourcecode for exclude '**/gen/**' reports { - xml.enabled = true - html.enabled = true + // xml.enabled = true + // html.enabled = true xml { destination file("$reportsDir/pmd/pmd.xml") } diff --git a/gradle.properties b/gradle.properties index 70564ff8b..14537dca4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,20 +9,40 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx10248m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit org.gradle.parallel=true -# When set to true the Gradle daemon is used to run the build. For local developer builds this is our favorite property. -# The developer environment is optimized for speed and feedback so we nearly always run Gradle jobs with the daemon. -org.gradle.daemon=true + +# Not encouraged by Gradle and can produce weird results. Wait for isolated projects instead. +org.gradle.configureondemand=false + +# Enable caching between builds. +org.gradle.caching=true + +# Enable configuration caching between builds. +org.gradle.configuration-cache=true +# This option is set because of https://github.com/google/play-services-plugins/issues/246 +# to generate the Configuration Cache regardless of incompatible tasks. +# See https://github.com/android/nowinandroid/issues/1022 before using it. +org.gradle.configuration-cache.problems=warn + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file + +# Remove after jetpack compose implementation +android.enableJetifier=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false + +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official + +# Disable build features that are enabled by default, +# https://developer.android.com/build/releases/gradle-plugin#default-changes +android.defaults.buildfeatures.resvalues=false +android.defaults.buildfeatures.shaders=false \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..403619a67 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,148 @@ +[versions] +appcompatVersion = "1.6.1" +compileSdk = "34" +constraintlayoutVersion = "2.1.4" +coreKtxVersion = "1.12.0" +minSdk = "24" +targetSdk = "34" +androidGradlePlugin = "8.2.1" +androidTools = "31.2.1" +androidxActivity = "1.7.2" +androidxComposeBom = "2023.10.01" +androidxComposeCompiler = "1.5.8" +androidxCore = "1.10.1" +androidxHilt = "1.1.0-alpha01" +androidxLifecycle = "2.6.1" +hilt = "2.50" +junit = "4.13.2" +kotlin = "1.9.22" +ksp = "1.9.21-1.0.16" +firebaseCrashlyticsPlugin = "2.9.2" +gmsPlugin = "4.3.14" +butterknifePlugin = "10.2.3" +secrets = "2.0.1" +lifecycleVersion = "2.6.2" +lifecycleExtensionsVersion = "2.2.0" +activityVersion = "1.5.0" +fragmentVersion = "1.5.0" +retrofitVersion = "2.9.0" +okHttp3Version = "3.14.9" +butterKnifeVersion = "10.2.3" +rxandroidVersion = "2.1.1" +rxjavaVersion = "2.2.21" +junitVersion = "4.12" +firebasePerfPlugin = "1.4.2" +truth = "1.1.5" +dependencyGuard = "0.4.3" +room = "2.6.1" +roborazzi = "1.6.0" +googleOss = "17.0.1" +googleOssPlugin = "0.10.6" +firebaseBom = "32.7.1" +androidDesugarJdkLibs = "2.0.4" +androidx-test-ext-junit = "1.1.5" +espresso-core = "3.5.1" +material = "1.11.0" +dbflowVersion = "4.2.4" +preference = "1.0.0" +playServicesVersion = "17.0.1" +spotbugs = "4.8.0" + +[libraries] +androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityVersion" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompatVersion" } +androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayoutVersion" } +androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtxVersion" } +androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentVersion" } +android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidxActivity" } +androidx-compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "androidxComposeCompiler" } +androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } +androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3"} +androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" } +androidx-compose-ui = { group = "androidx.compose.ui", name = "ui"} +androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4"} +androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest"} +androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling"} +androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } +hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } +androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview"} +androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util"} +androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidxHilt" } +androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" } +androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" } +androidx-lifecycle-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleVersion" } +androidx-lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "lifecycleExtensionsVersion" } +jakewharton-butterknife = { module = "com.jakewharton:butterknife", version.ref = "butterKnifeVersion" } +jakewharton-compiler = { module = "com.jakewharton:butterknife-compiler", version.ref = "butterKnifeVersion" } +squareup-retrofit2 = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofitVersion" } +squareup-retrofit-adapter-rxjava = { module = "com.squareup.retrofit2:adapter-rxjava2", version.ref = "retrofitVersion" } +squareup-retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofitVersion" } +squareup-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp3Version" } +squareup-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okHttp3Version" } +reactivex-rxjava2-android = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxandroidVersion" } +reactivex-rxjava2 = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjavaVersion" } +truth = { group = "com.google.truth", name = "truth", version.ref = "truth" } +junit = { module = "junit:junit", version.ref = "junitVersion"} +google-oss-licenses = { group = "com.google.android.gms", name = "play-services-oss-licenses", version.ref = "googleOss" } +google-oss-licenses-plugin = { group = "com.google.android.gms", name = "oss-licenses-plugin", version.ref = "googleOssPlugin" } +jetbrains-kotlin-jdk7 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" } +firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" } +firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx" } +firebase-cloud-messaging = { group = "com.google.firebase", name = "firebase-messaging-ktx" } +firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" } +firebase-performance = { group = "com.google.firebase", name = "firebase-perf-ktx" } +dbflow-processor = { group = "com.github.Raizlabs.DBFlow", name = "dbflow-processor", version.ref = "dbflowVersion" } +dbflow-core = { group = "com.github.Raizlabs.DBFlow", name = "dbflow-core", version.ref = "dbflowVersion" } +dbflow = { group = "com.github.Raizlabs.DBFlow", name = "dbflow", version.ref = "dbflowVersion" } +androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" } +play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesVersion" } + +# Dependencies of the included build-logic +android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } +android-tools-common = { group = "com.android.tools", name = "common", version.ref = "androidTools" } +firebase-crashlytics-gradlePlugin = { group = "com.google.firebase", name = "firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsPlugin" } +firebase-performance-gradlePlugin = { group = "com.google.firebase", name = "perf-plugin", version.ref = "firebasePerfPlugin" } +kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } +ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } +room-gradlePlugin = { group = "androidx.room", name = "room-gradle-plugin", version.ref = "room" } +work-testing = { group = "androidx.work", name = "work-testing", version = "2.8.1" } +androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } +espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } +gms = { id = "com.google.gms.google-services", version.ref = "gmsPlugin" } +hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlyticsPlugin" } +firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "firebasePerfPlugin" } +dependencyGuard = { id = "com.dropbox.dependency-guard", version.ref = "dependencyGuard" } +secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } +kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp"} +room = { id = "androidx.room", version.ref = "room" } +spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugs" } + +# Plugins defined by this project +mifos-android-application = { id = "mifos.android.application", version = "unspecified" } +mifos-android-application-compose = { id = "mifos.android.application.compose", version = "unspecified" } +mifos-android-application-firebase = { id = "mifos.android.application.firebase", version = "unspecified" } +mifos-android-feature = { id = "mifos.android.feature", version = "unspecified" } +mifos-android-hilt = { id = "mifos.android.hilt", version = "unspecified" } +mifos-android-library = { id = "mifos.android.library", version = "unspecified" } +mifos-android-library-compose = { id = "mifos.android.library.compose", version = "unspecified" } +mifos-android-lint = { id = "mifos.android.lint", version = "unspecified" } +mifos-android-room = { id = "mifos.android.room", version = "unspecified" } +mifos-android-test = { id = "mifos.android.test", version = "unspecified" } +mifos-jvm-library = { id = "mifos.jvm.library", version = "unspecified" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5fabd8ddc..60b08099b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 87d38612e..000000000 --- a/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -include ':app' -include ':ui' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 000000000..90c45123e --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,24 @@ +pluginManagement { + includeBuild("build-logic") + repositories { + gradlePluginPortal() + google() + mavenCentral() + maven("https://www.jitpack.io") + maven("https://plugins.gradle.org/m2/") + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven("https://www.jitpack.io") + maven("https://plugins.gradle.org/m2/") + } +} +rootProject.name = "mifos-mobile" + +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +include(":app") +include(":ui") diff --git a/ui/build.gradle b/ui/build.gradle deleted file mode 100644 index f318656db..000000000 --- a/ui/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -android { - namespace 'org.mifos.mobile.core' - compileSdk 32 - - defaultConfig { - minSdk 21 - targetSdk 32 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - - buildFeatures { - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion "1.4.4" - } - -} - -dependencies { - - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.9.0' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - - // Jetpack Compose - implementation "androidx.compose.material:material:$rootProject.composeVersion" - implementation "androidx.compose.compiler:compiler:$rootProject.composeCompiler" - implementation "androidx.compose.ui:ui-tooling-preview:$rootProject.composeVersion" - implementation "androidx.activity:activity-compose:$rootProject.composeActivity" - debugImplementation "androidx.compose.ui:ui-tooling:$rootProject.composeVersion" - implementation "androidx.compose.material3:material3:$rootProject.materialVersion" - implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$rootProject.lifecycleVersion" -} \ No newline at end of file diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts new file mode 100644 index 000000000..1ab8acab0 --- /dev/null +++ b/ui/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + alias(libs.plugins.mifos.android.library) + alias(libs.plugins.mifos.android.library.compose) +} + +android { + defaultConfig { + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + namespace = "org.mifos.mobile.core" +} + +dependencies { + api(libs.androidx.compose.foundation) + api(libs.androidx.compose.foundation.layout) + api(libs.androidx.compose.material.iconsExtended) + api(libs.androidx.compose.material3) + api(libs.androidx.compose.runtime) + api(libs.androidx.compose.ui.tooling.preview) + api(libs.androidx.compose.ui.util) + + debugApi(libs.androidx.compose.ui.tooling) + + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.9.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} diff --git a/ui/proguard-rules.pro b/ui/proguard-rules.pro index 481bb4348..ff59496d8 100644 --- a/ui/proguard-rules.pro +++ b/ui/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 diff --git a/ui/src/main/java/org/mifos/mobile/core/ui/component/EmptyDataView.kt b/ui/src/main/java/org/mifos/mobile/core/ui/component/EmptyDataView.kt index 91d771546..1225da9cd 100644 --- a/ui/src/main/java/org/mifos/mobile/core/ui/component/EmptyDataView.kt +++ b/ui/src/main/java/org/mifos/mobile/core/ui/component/EmptyDataView.kt @@ -3,11 +3,10 @@ package org.mifos.mobile.core.ui.component import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/ui/src/main/java/org/mifos/mobile/core/ui/component/MifosTopBar.kt b/ui/src/main/java/org/mifos/mobile/core/ui/component/MifosTopBar.kt index 41b142daf..e4a7033f6 100644 --- a/ui/src/main/java/org/mifos/mobile/core/ui/component/MifosTopBar.kt +++ b/ui/src/main/java/org/mifos/mobile/core/ui/component/MifosTopBar.kt @@ -2,7 +2,7 @@ package org.mifos.mobile.core.ui.component import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -28,7 +28,7 @@ fun MifosTopBar( onClick = { navigateBack.invoke() } ) { Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, + imageVector = Icons.Filled.ArrowBack, contentDescription = "Back Arrow", tint = if (isSystemInDarkTheme()) Color.White else Color.Black, ) diff --git a/ui/src/main/java/org/mifos/mobile/core/ui/component/NoInternet.kt b/ui/src/main/java/org/mifos/mobile/core/ui/component/NoInternet.kt index ffa83bf9d..0e048fd9a 100644 --- a/ui/src/main/java/org/mifos/mobile/core/ui/component/NoInternet.kt +++ b/ui/src/main/java/org/mifos/mobile/core/ui/component/NoInternet.kt @@ -6,8 +6,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/ui/src/main/java/org/mifos/mobile/core/ui/component/UserProfileField.kt b/ui/src/main/java/org/mifos/mobile/core/ui/component/UserProfileField.kt index 6720fd44c..feb51ecea 100644 --- a/ui/src/main/java/org/mifos/mobile/core/ui/component/UserProfileField.kt +++ b/ui/src/main/java/org/mifos/mobile/core/ui/component/UserProfileField.kt @@ -6,8 +6,8 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.Divider -import androidx.compose.material.Icon +import androidx.compose.material3.Divider +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier