diff --git a/build-logic/README.md b/build-logic/README.md new file mode 100644 index 00000000000..c5ca32919c5 --- /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: + +- [`mifospay.android.application`](convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt), + [`mifospay.android.library`](convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt), + [`mifospay.android.test`](convention/src/main/kotlin/AndroidTestConventionPlugin.kt): + Configures common Android and Kotlin options. +- [`mifospay.android.application.compose`](convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt), + [`mifospay.android.library.compose`](convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt): + Configures Jetpack Compose options diff --git a/build-logic/convention/bin/main/AndroidApplicationComposeConventionPlugin.kt b/build-logic/convention/bin/main/AndroidApplicationComposeConventionPlugin.kt new file mode 100644 index 00000000000..e2a163f1a41 --- /dev/null +++ b/build-logic/convention/bin/main/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.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/bin/main/AndroidApplicationConventionPlugin.kt b/build-logic/convention/bin/main/AndroidApplicationConventionPlugin.kt new file mode 100644 index 00000000000..cec33d23d55 --- /dev/null +++ b/build-logic/convention/bin/main/AndroidApplicationConventionPlugin.kt @@ -0,0 +1,33 @@ +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.configureBadgingTasks +import org.mifos.configureKotlinAndroid +import org.mifos.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") + } + + extensions.configure { + configureKotlinAndroid(this) + defaultConfig.targetSdk = 34 + @Suppress("UnstableApiUsage") + testOptions.animationsDisabled = true + } + extensions.configure { + configurePrintApksTask(this) + configureBadgingTasks(extensions.getByType(), this) + } + } + } +} diff --git a/build-logic/convention/bin/main/AndroidApplicationFirebaseConventionPlugin.kt b/build-logic/convention/bin/main/AndroidApplicationFirebaseConventionPlugin.kt new file mode 100644 index 00000000000..345f132dc48 --- /dev/null +++ b/build-logic/convention/bin/main/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.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/bin/main/AndroidFeatureConventionPlugin.kt b/build-logic/convention/bin/main/AndroidFeatureConventionPlugin.kt new file mode 100644 index 00000000000..81c423f35b6 --- /dev/null +++ b/build-logic/convention/bin/main/AndroidFeatureConventionPlugin.kt @@ -0,0 +1,40 @@ +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.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" + } + testOptions.animationsDisabled = true + } + + dependencies { + add("implementation", project(":core:data")) + add("implementation", project(":core:designsystem")) + add("implementation", project(":core:common")) + + 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()) + add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) + + add( + "androidTestImplementation", + libs.findLibrary("androidx.lifecycle.runtimeTesting").get() + ) + } + } + } +} diff --git a/build-logic/convention/bin/main/AndroidHiltConventionPlugin.kt b/build-logic/convention/bin/main/AndroidHiltConventionPlugin.kt new file mode 100644 index 00000000000..904718be92d --- /dev/null +++ b/build-logic/convention/bin/main/AndroidHiltConventionPlugin.kt @@ -0,0 +1,20 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies +import org.mifos.libs + +class AndroidHiltConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.google.devtools.ksp") + apply("dagger.hilt.android.plugin") + } + + dependencies { + "implementation"(libs.findLibrary("hilt.android").get()) + "ksp"(libs.findLibrary("hilt.compiler").get()) + } + } + } +} diff --git a/build-logic/convention/bin/main/AndroidLibraryComposeConventionPlugin.kt b/build-logic/convention/bin/main/AndroidLibraryComposeConventionPlugin.kt new file mode 100644 index 00000000000..8acc967d863 --- /dev/null +++ b/build-logic/convention/bin/main/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.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/bin/main/AndroidLibraryConventionPlugin.kt b/build-logic/convention/bin/main/AndroidLibraryConventionPlugin.kt new file mode 100644 index 00000000000..f5439d0edde --- /dev/null +++ b/build-logic/convention/bin/main/AndroidLibraryConventionPlugin.kt @@ -0,0 +1,42 @@ +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.configureKotlinAndroid +import org.mifos.configurePrintApksTask +import org.mifos.disableUnnecessaryAndroidTests +import org.mifos.libs + +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 + testOptions.animationsDisabled = true + // 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")) + add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) + } + } + } +} diff --git a/build-logic/convention/bin/main/AndroidLintConventionPlugin.kt b/build-logic/convention/bin/main/AndroidLintConventionPlugin.kt new file mode 100644 index 00000000000..54246d61ef7 --- /dev/null +++ b/build-logic/convention/bin/main/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/bin/main/AndroidRoomConventionPlugin.kt b/build-logic/convention/bin/main/AndroidRoomConventionPlugin.kt new file mode 100644 index 00000000000..e2b884ba611 --- /dev/null +++ b/build-logic/convention/bin/main/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.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/bin/main/AndroidTestConventionPlugin.kt b/build-logic/convention/bin/main/AndroidTestConventionPlugin.kt new file mode 100644 index 00000000000..4b9f8f078c2 --- /dev/null +++ b/build-logic/convention/bin/main/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.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/bin/main/JvmLibraryConventionPlugin.kt b/build-logic/convention/bin/main/JvmLibraryConventionPlugin.kt new file mode 100644 index 00000000000..f5eeaf4016a --- /dev/null +++ b/build-logic/convention/bin/main/JvmLibraryConventionPlugin.kt @@ -0,0 +1,15 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.mifos.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/bin/main/org/mifos/AndroidCompose.kt b/build-logic/convention/bin/main/org/mifos/AndroidCompose.kt new file mode 100644 index 00000000000..137ff7ca257 --- /dev/null +++ b/build-logic/convention/bin/main/org/mifos/AndroidCompose.kt @@ -0,0 +1,81 @@ +package org.mifos + +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)) + add("implementation", libs.findLibrary("androidx-compose-ui-tooling-preview").get()) + add("debugImplementation", libs.findLibrary("androidx-compose-ui-tooling").get()) + add("debugImplementation", libs.findLibrary("androidx.compose.foundation").get()) + add("debugImplementation", libs.findLibrary("androidx.compose.material3").get()) + add("debugImplementation", libs.findLibrary("androidx.compose.material.iconsExtended").get()) + } + + testOptions { + unitTests { + // For Robolectric + isIncludeAndroidResources = true + } + } + } + + tasks.withType().configureEach { + kotlinOptions { + freeCompilerArgs += buildComposeMetricsParameters() + freeCompilerArgs += stabilityConfiguration() + } + } +} + +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() +} + +private fun Project.stabilityConfiguration() = listOf( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=${project.rootDir.absolutePath}/compose_compiler_config.conf", +) diff --git a/build-logic/convention/bin/main/org/mifos/AndroidInstrumentedTests.kt b/build-logic/convention/bin/main/org/mifos/AndroidInstrumentedTests.kt new file mode 100644 index 00000000000..fa5358e547c --- /dev/null +++ b/build-logic/convention/bin/main/org/mifos/AndroidInstrumentedTests.kt @@ -0,0 +1,19 @@ +package org.mifos + +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/bin/main/org/mifos/Badging.kt b/build-logic/convention/bin/main/org/mifos/Badging.kt new file mode 100644 index 00000000000..aab84c3ce21 --- /dev/null +++ b/build-logic/convention/bin/main/org/mifos/Badging.kt @@ -0,0 +1,144 @@ +package org.mifos + +import com.android.SdkConstants +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.gradle.BaseExtension +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/bin/main/org/mifos/KotlinAndroid.kt b/build-logic/convention/bin/main/org/mifos/KotlinAndroid.kt new file mode 100644 index 00000000000..7aee8c39e7e --- /dev/null +++ b/build-logic/convention/bin/main/org/mifos/KotlinAndroid.kt @@ -0,0 +1,74 @@ +package org.mifos + +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 = 26 + } + + 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 + } + } + + 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/bin/main/org/mifos/PrintTestApks.kt b/build-logic/convention/bin/main/org/mifos/PrintTestApks.kt new file mode 100644 index 00000000000..9e04c59be7f --- /dev/null +++ b/build-logic/convention/bin/main/org/mifos/PrintTestApks.kt @@ -0,0 +1,87 @@ +package org.mifos + +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/bin/main/org/mifos/ProjectExtensions.kt b/build-logic/convention/bin/main/org/mifos/ProjectExtensions.kt new file mode 100644 index 00000000000..c22e9a5b128 --- /dev/null +++ b/build-logic/convention/bin/main/org/mifos/ProjectExtensions.kt @@ -0,0 +1,9 @@ +package org.mifos + +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/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts new file mode 100644 index 00000000000..96898e7d586 --- /dev/null +++ b/build-logic/convention/build.gradle.kts @@ -0,0 +1,86 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` +} + +group = "com.mifos.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 = false + } +} + +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 00000000000..e2a163f1a41 --- /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.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 00000000000..cec33d23d55 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt @@ -0,0 +1,33 @@ +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.configureBadgingTasks +import org.mifos.configureKotlinAndroid +import org.mifos.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") + } + + extensions.configure { + configureKotlinAndroid(this) + defaultConfig.targetSdk = 34 + @Suppress("UnstableApiUsage") + testOptions.animationsDisabled = true + } + 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 00000000000..79f5d220603 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt @@ -0,0 +1,37 @@ +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.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 { + // 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 00000000000..81c423f35b6 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -0,0 +1,40 @@ +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.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" + } + testOptions.animationsDisabled = true + } + + dependencies { + add("implementation", project(":core:data")) + add("implementation", project(":core:designsystem")) + add("implementation", project(":core:common")) + + 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()) + add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) + + add( + "androidTestImplementation", + libs.findLibrary("androidx.lifecycle.runtimeTesting").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 00000000000..904718be92d --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt @@ -0,0 +1,20 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies +import org.mifos.libs + +class AndroidHiltConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.google.devtools.ksp") + apply("dagger.hilt.android.plugin") + } + + dependencies { + "implementation"(libs.findLibrary("hilt.android").get()) + "ksp"(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 00000000000..8acc967d863 --- /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.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 00000000000..f5439d0edde --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -0,0 +1,42 @@ +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.configureKotlinAndroid +import org.mifos.configurePrintApksTask +import org.mifos.disableUnnecessaryAndroidTests +import org.mifos.libs + +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 + testOptions.animationsDisabled = true + // 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")) + add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) + } + } + } +} 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 00000000000..54246d61ef7 --- /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 00000000000..e2b884ba611 --- /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.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 00000000000..4b9f8f078c2 --- /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.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 00000000000..f5eeaf4016a --- /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.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/AndroidCompose.kt b/build-logic/convention/src/main/kotlin/org/mifos/AndroidCompose.kt new file mode 100644 index 00000000000..137ff7ca257 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/AndroidCompose.kt @@ -0,0 +1,81 @@ +package org.mifos + +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)) + add("implementation", libs.findLibrary("androidx-compose-ui-tooling-preview").get()) + add("debugImplementation", libs.findLibrary("androidx-compose-ui-tooling").get()) + add("debugImplementation", libs.findLibrary("androidx.compose.foundation").get()) + add("debugImplementation", libs.findLibrary("androidx.compose.material3").get()) + add("debugImplementation", libs.findLibrary("androidx.compose.material.iconsExtended").get()) + } + + testOptions { + unitTests { + // For Robolectric + isIncludeAndroidResources = true + } + } + } + + tasks.withType().configureEach { + kotlinOptions { + freeCompilerArgs += buildComposeMetricsParameters() + freeCompilerArgs += stabilityConfiguration() + } + } +} + +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() +} + +private fun Project.stabilityConfiguration() = listOf( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=${project.rootDir.absolutePath}/compose_compiler_config.conf", +) diff --git a/build-logic/convention/src/main/kotlin/org/mifos/AndroidInstrumentedTests.kt b/build-logic/convention/src/main/kotlin/org/mifos/AndroidInstrumentedTests.kt new file mode 100644 index 00000000000..fa5358e547c --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/AndroidInstrumentedTests.kt @@ -0,0 +1,19 @@ +package org.mifos + +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/Badging.kt b/build-logic/convention/src/main/kotlin/org/mifos/Badging.kt new file mode 100644 index 00000000000..aab84c3ce21 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/Badging.kt @@ -0,0 +1,144 @@ +package org.mifos + +import com.android.SdkConstants +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.gradle.BaseExtension +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/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/org/mifos/KotlinAndroid.kt new file mode 100644 index 00000000000..7aee8c39e7e --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/KotlinAndroid.kt @@ -0,0 +1,74 @@ +package org.mifos + +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 = 26 + } + + 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 + } + } + + 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/PrintTestApks.kt b/build-logic/convention/src/main/kotlin/org/mifos/PrintTestApks.kt new file mode 100644 index 00000000000..9e04c59be7f --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/PrintTestApks.kt @@ -0,0 +1,87 @@ +package org.mifos + +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/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/org/mifos/ProjectExtensions.kt new file mode 100644 index 00000000000..c22e9a5b128 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/mifos/ProjectExtensions.kt @@ -0,0 +1,9 @@ +package org.mifos + +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 00000000000..1c9073eb98a --- /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 00000000000..de9224e220f --- /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.kts b/build.gradle.kts index 453bacdcced..5dce12bfcba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,5 +6,6 @@ plugins { alias(libs.plugins.hilt) apply false alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.androidx.navigation) apply false + alias(libs.plugins.ksp) apply false alias(libs.plugins.secrets) apply false } diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index 0df26e5186f..0864a7276d5 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -1,71 +1,22 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) - id(libs.plugins.kotlin.kapt.get().pluginId) + alias(libs.plugins.mifos.android.library.compose) + alias(libs.plugins.mifos.android.library) } android { namespace = "com.mifos.core.designsystem" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = "1.5.8" - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.androidx.material) - testImplementation(libs.junit4) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.test.espresso.core) - - // Jetpack Compose -// implementation(libs.androidx.material) - implementation(libs.androidx.compose.material3) - implementation(libs.androidx.compiler) - implementation(libs.androidx.compose.ui.tooling.preview) - implementation(libs.androidx.activity.compose) - debugImplementation(libs.androidx.compose.ui.tooling) - implementation(libs.androidx.lifecycle.viewModelCompose) - implementation(libs.androidx.material.icons.extended) + 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.util) + api(libs.androidx.activity.compose) // coil implementation(libs.coil.kt.compose) - - //DBFlow dependencies - kapt(libs.dbflow.processor) - implementation(libs.dbflow) - kapt(libs.github.dbflow.processor) } \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosMenuDropdown.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosMenuDropdown.kt index 822b1d10bd6..3a4e14160de 100644 --- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosMenuDropdown.kt +++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosMenuDropdown.kt @@ -6,11 +6,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.mifos.core.designsystem.R @Composable fun MifosMenuDropDownItem(option: String, onClick: () -> Unit) { diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosProgressIndicator.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosProgressIndicator.kt index 59a33acb191..1b7520f600d 100644 --- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosProgressIndicator.kt +++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosProgressIndicator.kt @@ -1,30 +1,18 @@ package com.mifos.core.designsystem.component -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Text +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog -import com.mifos.core.designsystem.R import com.mifos.core.designsystem.theme.DarkGray -import com.mifos.core.designsystem.theme.White /** * Created by Aditya Gupta on 21/02/24. diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosSweetError.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosSweetError.kt index a80136752c6..9bd8a91ad5e 100644 --- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosSweetError.kt +++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosSweetError.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight @@ -39,13 +40,13 @@ fun MifosSweetError(message: String, onclick: () -> Unit) { ) { AsyncImage( modifier = Modifier.size(70.dp), - model = R.drawable.ic_error_black_24dp, + model = R.drawable.core_designsystem_ic_error_black_24dp, contentDescription = null, colorFilter = ColorFilter.tint(Color.Gray) ) Spacer(modifier = Modifier.height(20.dp)) Text( - text = "Sorry we weren't able to load", + text = stringResource(id = R.string.core_designsystem_unable_to_load), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Normal, @@ -72,7 +73,7 @@ fun MifosSweetError(message: String, onclick: () -> Unit) { ) { Text( modifier = Modifier.padding(start = 20.dp, end = 20.dp), - text = "Try Again", + text = stringResource(id = R.string.core_designsystem_try_again), fontSize = 15.sp ) } diff --git a/core/designsystem/src/main/res/drawable/ic_error_black_24dp.xml b/core/designsystem/src/main/res/drawable/core_designsystem_ic_error_black_24dp.xml similarity index 64% rename from core/designsystem/src/main/res/drawable/ic_error_black_24dp.xml rename to core/designsystem/src/main/res/drawable/core_designsystem_ic_error_black_24dp.xml index 916282cbdec..05baa0e6e5d 100644 --- a/core/designsystem/src/main/res/drawable/ic_error_black_24dp.xml +++ b/core/designsystem/src/main/res/drawable/core_designsystem_ic_error_black_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z" /> diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml new file mode 100644 index 00000000000..6a600149d1d --- /dev/null +++ b/core/designsystem/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Sorry we weren\'t able to load + Try Again + \ No newline at end of file diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts index fdc2d6dd8c9..58becfacd5f 100644 --- a/feature/auth/build.gradle.kts +++ b/feature/auth/build.gradle.kts @@ -1,56 +1,16 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) - alias(libs.plugins.hilt) - id(libs.plugins.kotlin.kapt.get().pluginId) + alias(libs.plugins.mifos.android.feature) + alias(libs.plugins.mifos.android.library.compose) } android { namespace = "com.mifos.feature.auth" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - compileSdkPreview = "UpsideDownCake" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = "1.5.8" - } - - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - implementation(project(":core:designsystem")) implementation(project(":core:network")) implementation(project(":core:datastore")) - implementation(project(":core:common")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) @@ -59,28 +19,10 @@ dependencies { androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.test.espresso.core) - // Hilt dependency - implementation(libs.hilt.android) - kapt(libs.hilt.compiler) - //rxjava dependencies implementation(libs.rxandroid) implementation(libs.rxjava) - // Jetpack Compose - implementation(libs.androidx.material) - implementation(libs.androidx.compiler) - implementation(libs.androidx.compose.ui.tooling.preview) - implementation(libs.androidx.activity.compose) - debugImplementation(libs.androidx.compose.ui.tooling) - implementation(libs.androidx.compose.material3) - implementation(libs.androidx.lifecycle.viewModelCompose) - implementation(libs.androidx.material.icons.extended) - - // ViewModel utilities for Compose - implementation(libs.androidx.lifecycle.viewModelCompose) - implementation(libs.androidx.hilt.navigation.compose) - // fineract sdk dependencies implementation(libs.mifos.android.sdk.arch) diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/PasswordValidationUseCase.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/PasswordValidationUseCase.kt index a3815c300f5..b72df4a7674 100644 --- a/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/PasswordValidationUseCase.kt +++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/PasswordValidationUseCase.kt @@ -19,7 +19,7 @@ class PasswordValidationUseCase { } else if (password.length < 6) { return ValidationResult( success = false, - R.string.feature_error_password_length + R.string.feature_auth_error_password_length ) } return ValidationResult(success = true) diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/UsernameValidationUseCase.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/UsernameValidationUseCase.kt index 3f846bac05c..d1d3f62a544 100644 --- a/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/UsernameValidationUseCase.kt +++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/UsernameValidationUseCase.kt @@ -18,7 +18,7 @@ class UsernameValidationUseCase { } else if (username.length < 5) { return ValidationResult( success = false, - R.string.feature_error_username_length + R.string.feature_auth_error_username_length ) } return ValidationResult(success = true) diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginScreen.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginScreen.kt index 98827e6727b..8bf57b1c833 100644 --- a/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginScreen.kt +++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginScreen.kt @@ -142,7 +142,7 @@ fun LoginScreen( ) { Spacer(modifier = Modifier.height(80.dp)) - MifosAndroidClientIcon(R.drawable.mifos_logo) + MifosAndroidClientIcon(R.drawable.feature_auth_mifos_logo) Text( modifier = Modifier diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginViewModel.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginViewModel.kt index 00edddfbb49..648cc41df26 100644 --- a/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginViewModel.kt +++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginViewModel.kt @@ -81,7 +81,7 @@ class LoginViewModel @Inject constructor( login(username, password) } else { _loginUiState.value = - LoginUiState.ShowError(R.string.feature_error_not_connected_internet) + LoginUiState.ShowError(R.string.feature_auth_error_not_connected_internet) } } @@ -92,7 +92,7 @@ class LoginViewModel @Inject constructor( when (result) { is Resource.Error -> { _loginUiState.value = - LoginUiState.ShowError(R.string.feature_error_login_failed) + LoginUiState.ShowError(R.string.feature_auth_error_login_failed) } is Resource.Loading -> { diff --git a/feature/auth/src/main/res/drawable/mifos_logo.jpg b/feature/auth/src/main/res/drawable/feature_auth_mifos_logo.jpg similarity index 100% rename from feature/auth/src/main/res/drawable/mifos_logo.jpg rename to feature/auth/src/main/res/drawable/feature_auth_mifos_logo.jpg diff --git a/feature/auth/src/main/res/values/strings.xml b/feature/auth/src/main/res/values/strings.xml index 510eeb5230f..27395229881 100644 --- a/feature/auth/src/main/res/values/strings.xml +++ b/feature/auth/src/main/res/values/strings.xml @@ -4,9 +4,9 @@ Please enter your credentials Password Username - Please Enter the Credentials - Invalid username length - Invalid password length - No Internet Connection - Login Failed, Please Try Again Later. + Please Enter the Credentials + Invalid username length + Invalid password length + No Internet Connection + Login Failed, Please Try Again Later. \ No newline at end of file diff --git a/feature/checker-inbox-task/build.gradle.kts b/feature/checker-inbox-task/build.gradle.kts index f0f5626c643..654735aec1f 100644 --- a/feature/checker-inbox-task/build.gradle.kts +++ b/feature/checker-inbox-task/build.gradle.kts @@ -1,69 +1,19 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) - alias(libs.plugins.hilt) - id(libs.plugins.kotlin.kapt.get().pluginId) + alias(libs.plugins.mifos.android.feature) + alias(libs.plugins.mifos.android.library.compose) } android { - namespace = "com.sparklead.feature.checker_inbox_task" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } - buildFeatures { - compose = true - } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.8" - } - + namespace = "com.mifos.feature.checker_inbox_task" } dependencies { - implementation(project(":core:data")) - implementation(project(":core:designsystem")) - implementation(project(":core:common")) implementation(project(":core:network")) implementation(project(":core:datastore")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) - // Jetpack Compose - implementation(libs.androidx.material) - implementation(libs.androidx.compiler) - implementation(libs.androidx.compose.ui.tooling.preview) - implementation(libs.androidx.activity.compose) - debugImplementation(libs.androidx.compose.ui.tooling) - implementation(libs.androidx.compose.material3) - implementation(libs.androidx.lifecycle.viewModelCompose) - implementation(libs.androidx.material.icons.extended) - - // ViewModel utilities for Compose - implementation(libs.androidx.lifecycle.runtimeCompose) - implementation(libs.androidx.hilt.navigation.compose) // swipe refresh implementation(libs.accompanist.swiperefresh) @@ -71,11 +21,6 @@ dependencies { // coil implementation(libs.coil.kt.compose) - - // Hilt dependency - implementation(libs.hilt.android) - kapt(libs.hilt.compiler) - //rxjava dependencies implementation(libs.rxandroid) implementation(libs.rxjava) diff --git a/feature/checker-inbox-task/src/main/java/com/sparklead/feature/checker_inbox_task/checker_inbox_and_task/ui/CheckerInboxTasksScreen.kt b/feature/checker-inbox-task/src/main/java/com/sparklead/feature/checker_inbox_task/checker_inbox_and_task/ui/CheckerInboxTasksScreen.kt index aba69fee39f..02a9dfa9171 100644 --- a/feature/checker-inbox-task/src/main/java/com/sparklead/feature/checker_inbox_task/checker_inbox_and_task/ui/CheckerInboxTasksScreen.kt +++ b/feature/checker-inbox-task/src/main/java/com/sparklead/feature/checker_inbox_task/checker_inbox_and_task/ui/CheckerInboxTasksScreen.kt @@ -35,7 +35,7 @@ import com.mifos.core.designsystem.component.MifosCircularProgress import com.mifos.core.designsystem.component.MifosScaffold import com.mifos.core.designsystem.component.MifosSweetError import com.mifos.core.designsystem.theme.White -import com.sparklead.feature.checker_inbox_task.R +import com.mifos.feature.checker_inbox_task.R /** * Created by Aditya Gupta on 21/03/24. @@ -59,7 +59,7 @@ fun CheckerInboxTasksScreen( MifosScaffold( icon = Icons.Rounded.ArrowBackIosNew, - title = stringResource(id = R.string.checker_inbox_and_pending_tasks), + title = stringResource(id = R.string.feature_checker_inbox_task_checker_inbox_and_pending_tasks), onBackPressed = { onBackPressed() }, actions = { }, snackbarHostState = null, @@ -71,7 +71,7 @@ fun CheckerInboxTasksScreen( ) { when (state) { is CheckerInboxTasksUiState.Error -> { - MifosSweetError(message = stringResource(id = R.string.failed_to_Load_Check_Inbox)) { + MifosSweetError(message = stringResource(id = R.string.feature_checker_inbox_task_failed_to_Load_Check_Inbox)) { checkerInboxTasksViewModel.loadCheckerTasksBadges() } } @@ -83,36 +83,36 @@ fun CheckerInboxTasksScreen( is CheckerInboxTasksUiState.Success -> { Column(modifier = Modifier.padding(padding)) { TaskOptions( - leadingIcon = R.drawable.ic_mail_outline_24dp, - option = stringResource(id = R.string.checker_Inbox), + leadingIcon = R.drawable.feature_checker_inbox_task_ic_mail_outline_24dp, + option = stringResource(id = R.string.feature_checker_inbox_task_checker_Inbox), badge = state.checkerInboxBadge ) { checkerInbox() } TaskOptions( - leadingIcon = R.drawable.ic_supervisor_account_24dp, - option = stringResource(id = R.string.client_Approval), + leadingIcon = R.drawable.feature_checker_inbox_task_ic_supervisor_account_24dp, + option = stringResource(id = R.string.feature_checker_inbox_task_client_Approval), badge = "0" ) { } TaskOptions( - leadingIcon = R.drawable.ic_assignment_black_24dp, - option = stringResource(id = R.string.loan_Approval), + leadingIcon = R.drawable.feature_checker_inbox_task_ic_assignment_black_24dp, + option = stringResource(id = R.string.feature_checker_inbox_task_loan_Approval), badge = "0" ) { } TaskOptions( - leadingIcon = R.drawable.ic_done_all_24dp, - option = stringResource(id = R.string.loan_Disbursal), + leadingIcon = R.drawable.feature_checker_inbox_task_ic_done_all_24dp, + option = stringResource(id = R.string.feature_checker_inbox_task_loan_Disbursal), badge = "0" ) { } TaskOptions( - leadingIcon = R.drawable.ic_restore_24dp, - option = stringResource(id = R.string.reschedule_Loan), + leadingIcon = R.drawable.feature_checker_inbox_task_ic_restore_24dp, + option = stringResource(id = R.string.feature_checker_inbox_task_reschedule_Loan), badge = state.rescheduleLoanBadge ) { diff --git a/feature/checker-inbox-task/src/main/res/drawable/ic_assignment_black_24dp.xml b/feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_assignment_black_24dp.xml similarity index 100% rename from feature/checker-inbox-task/src/main/res/drawable/ic_assignment_black_24dp.xml rename to feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_assignment_black_24dp.xml diff --git a/feature/checker-inbox-task/src/main/res/drawable/ic_done_all_24dp.xml b/feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_done_all_24dp.xml similarity index 100% rename from feature/checker-inbox-task/src/main/res/drawable/ic_done_all_24dp.xml rename to feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_done_all_24dp.xml diff --git a/feature/checker-inbox-task/src/main/res/drawable/ic_mail_outline_24dp.xml b/feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_mail_outline_24dp.xml similarity index 100% rename from feature/checker-inbox-task/src/main/res/drawable/ic_mail_outline_24dp.xml rename to feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_mail_outline_24dp.xml diff --git a/feature/checker-inbox-task/src/main/res/drawable/ic_restore_24dp.xml b/feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_restore_24dp.xml similarity index 100% rename from feature/checker-inbox-task/src/main/res/drawable/ic_restore_24dp.xml rename to feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_restore_24dp.xml diff --git a/feature/checker-inbox-task/src/main/res/drawable/ic_supervisor_account_24dp.xml b/feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_supervisor_account_24dp.xml similarity index 100% rename from feature/checker-inbox-task/src/main/res/drawable/ic_supervisor_account_24dp.xml rename to feature/checker-inbox-task/src/main/res/drawable/feature_checker_inbox_task_ic_supervisor_account_24dp.xml diff --git a/feature/checker-inbox-task/src/main/res/values/strings.xml b/feature/checker-inbox-task/src/main/res/values/strings.xml index 9d2e2e499df..389cee72690 100644 --- a/feature/checker-inbox-task/src/main/res/values/strings.xml +++ b/feature/checker-inbox-task/src/main/res/values/strings.xml @@ -1,12 +1,12 @@ - Checker Inbox & Tasks - Failed to Load Check Inbox + Checker Inbox & Tasks + Failed to Load Check Inbox - Checker Inbox - Client Approval - Loan Approval - Loan Disbursal - Reschedule Loan + Checker Inbox + Client Approval + Loan Approval + Loan Disbursal + Reschedule Loan \ No newline at end of file diff --git a/feature/client/build.gradle.kts b/feature/client/build.gradle.kts index 11c88739d3a..8d427ea7a5c 100644 --- a/feature/client/build.gradle.kts +++ b/feature/client/build.gradle.kts @@ -1,73 +1,20 @@ plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) - alias(libs.plugins.hilt) - id(libs.plugins.kotlin.kapt.get().pluginId) + alias(libs.plugins.mifos.android.feature) + alias(libs.plugins.mifos.android.library.compose) } android { namespace = "com.mifos.feature.client" - compileSdk = 34 - - defaultConfig { - minSdk = 26 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = "1.5.8" - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = "17" - } } dependencies { - implementation(project(":core:data")) - implementation(project(":core:designsystem")) implementation(project(":core:datastore")) - implementation(project(":core:common")) implementation(project(":core:network")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) - // Jetpack Compose - implementation(libs.androidx.material) - implementation(libs.androidx.compiler) - implementation(libs.androidx.compose.ui.tooling.preview) - implementation(libs.androidx.activity.compose) - debugImplementation(libs.androidx.compose.ui.tooling) - implementation(libs.androidx.compose.material3) - implementation(libs.androidx.lifecycle.viewModelCompose) - implementation(libs.androidx.material.icons.extended) - - // ViewModel utilities for Compose - implementation(libs.androidx.lifecycle.runtimeCompose) - implementation(libs.androidx.hilt.navigation.compose) - // swipe refresh implementation(libs.accompanist.swiperefresh) implementation(libs.accompanist.permission) @@ -82,13 +29,9 @@ dependencies { implementation(libs.fineract.client) //DBFlow dependencies - kapt(libs.dbflow.processor) + ksp(libs.dbflow.processor) implementation(libs.dbflow) - kapt(libs.github.dbflow.processor) - - // Hilt dependency - implementation(libs.hilt.android) - kapt(libs.hilt.compiler) + ksp(libs.github.dbflow.processor) //rxjava dependencies implementation(libs.rxandroid) diff --git a/feature/client/src/main/java/com/mifos/feature/client/clientDetails/ui/ClientDetailsScreen.kt b/feature/client/src/main/java/com/mifos/feature/client/clientDetails/ui/ClientDetailsScreen.kt index abfc70237f6..0ad1fd66b04 100644 --- a/feature/client/src/main/java/com/mifos/feature/client/clientDetails/ui/ClientDetailsScreen.kt +++ b/feature/client/src/main/java/com/mifos/feature/client/clientDetails/ui/ClientDetailsScreen.kt @@ -191,14 +191,14 @@ fun ClientDetailsScreen( when (state) { is ClientDetailsUiState.ShowClientImageDeletedSuccessfully -> { - val message = stringResource(id = R.string.client_image_deleted) + val message = stringResource(id = R.string.feature_client_client_image_deleted) LaunchedEffect(key1 = state) { snackbarHostState.showSnackbar(message = message) } } is ClientDetailsUiState.ShowUploadImageSuccessfully -> { - val message = stringResource(id = R.string.client_image_updated) + val message = stringResource(id = R.string.feature_client_client_image_updated) LaunchedEffect(key1 = state.response) { snackbarHostState.showSnackbar(message = message) } @@ -234,43 +234,43 @@ fun ClientDetailsScreen( expanded = showMenu, onDismissRequest = { showMenu = false } ) { - MifosMenuDropDownItem(option = stringResource(id = R.string.add_loan_account)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_add_loan_account)) { addLoanAccount(clientId) showMenu = false } - MifosMenuDropDownItem(option = stringResource(id = R.string.add_savings_account)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_add_savings_account)) { addSavingsAccount(clientId) showMenu = false } - MifosMenuDropDownItem(option = stringResource(id = R.string.charges)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_charges)) { charges(clientId) showMenu = false } - MifosMenuDropDownItem(option = stringResource(id = R.string.documents)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_documents)) { documents(clientId) showMenu = false } - MifosMenuDropDownItem(option = stringResource(id = R.string.identifiers)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_identifiers)) { identifiers(clientId) showMenu = false } - MifosMenuDropDownItem(option = stringResource(id = R.string.more_client_info)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_more_client_info)) { moreClientInfo(clientId) showMenu = false } - MifosMenuDropDownItem(option = stringResource(id = R.string.notes)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_notes)) { notes(clientId) showMenu = false } - MifosMenuDropDownItem(option = stringResource(id = R.string.pinpoint_location)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_pinpoint_location)) { pinpointLocation(clientId) showMenu = false } - MifosMenuDropDownItem(option = stringResource(id = R.string.survey)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_survey)) { survey(clientId) showMenu = false } - MifosMenuDropDownItem(option = stringResource(id = R.string.upload_signature)) { + MifosMenuDropDownItem(option = stringResource(id = R.string.feature_client_upload_signature)) { uploadSignature(clientId) showMenu = false } @@ -290,7 +290,7 @@ fun ClientDetailsScreen( containerColor = if (isSystemInDarkTheme()) BluePrimaryDark else BluePrimary ) ) { - Text(text = stringResource(id = R.string.activate_client), fontSize = 16.sp) + Text(text = stringResource(id = R.string.feature_client_activate_client), fontSize = 16.sp) } } @@ -326,7 +326,7 @@ fun ClientDetailsScreen( }) } if (clientNotFoundError) { - MifosSweetError(message = stringResource(id = R.string.client_not_found)) { + MifosSweetError(message = stringResource(id = R.string.feature_client_client_not_found)) { clientDetailsViewModel.loadClientDetailsAndClientAccounts(clientId) } } else { @@ -360,7 +360,7 @@ fun ClientDetailsScreen( ) } } - } else R.drawable.ic_launcher, + } else R.drawable.feature_client_ic_launcher, contentDescription = null, contentScale = ContentScale.FillBounds ) @@ -383,42 +383,42 @@ fun ClientDetailsScreen( client?.accountNo?.let { MifosClientDetailsText( icon = Icons.Outlined.Numbers, - field = stringResource(id = R.string.account_number), + field = stringResource(id = R.string.feature_client_account_number), value = it ) } client?.externalId?.let { MifosClientDetailsText( icon = Icons.Outlined.Numbers, - field = stringResource(id = R.string.external_id), + field = stringResource(id = R.string.feature_client_external_id), value = it ) } client?.let { Utils.getStringOfDate(it.activationDate) }?.let { MifosClientDetailsText( icon = Icons.Outlined.DateRange, - field = stringResource(id = R.string.activation_date), + field = stringResource(id = R.string.feature_client_activation_date), value = it ) } client?.officeName?.let { MifosClientDetailsText( icon = Icons.Outlined.HomeWork, - field = stringResource(id = R.string.office), + field = stringResource(id = R.string.feature_client_office), value = it ) } client?.mobileNo?.let { MifosClientDetailsText( icon = Icons.Outlined.MobileFriendly, - field = stringResource(id = R.string.mobile_no), + field = stringResource(id = R.string.feature_client_mobile_no), value = it ) } client?.groupNames?.let { MifosClientDetailsText( icon = Icons.Outlined.Groups, - field = stringResource(id = R.string.group), + field = stringResource(id = R.string.feature_client_group), value = it ) } @@ -426,7 +426,7 @@ fun ClientDetailsScreen( if (loanAccounts != null && savingsAccounts != null) { Text( modifier = Modifier.padding(start = 16.dp, bottom = 6.dp), - text = stringResource(id = R.string.accounts), + text = stringResource(id = R.string.feature_client_accounts), style = TextStyle( fontSize = 21.sp, fontWeight = FontWeight.Medium, @@ -438,14 +438,14 @@ fun ClientDetailsScreen( } loanAccounts?.let { MifosLoanAccountExpendableCard( - stringResource(id = R.string.loan_account), + stringResource(id = R.string.feature_client_loan_account), it, loanAccountSelected ) } savingsAccounts?.let { MifosSavingsAccountExpendableCard( - stringResource(id = R.string.savings_account), + stringResource(id = R.string.feature_client_savings_account), it, savingsAccountSelected ) } @@ -832,7 +832,7 @@ fun MifosSelectImageDialog( horizontalAlignment = Alignment.CenterHorizontally ) { Text( - text = stringResource(id = R.string.please_select), + text = stringResource(id = R.string.feature_client_please_select), modifier = Modifier.fillMaxWidth(), style = TextStyle( fontSize = 18.sp, @@ -849,7 +849,7 @@ fun MifosSelectImageDialog( colors = ButtonDefaults.buttonColors(BlueSecondary) ) { Text( - text = stringResource(id = R.string.take_new_image), + text = stringResource(id = R.string.feature_client_take_new_image), modifier = Modifier.fillMaxWidth(), style = TextStyle( fontSize = 18.sp, @@ -865,7 +865,7 @@ fun MifosSelectImageDialog( colors = ButtonDefaults.buttonColors(BlueSecondary) ) { Text( - text = stringResource(id = R.string.upload_new_image), + text = stringResource(id = R.string.feature_client_upload_new_image), modifier = Modifier.fillMaxWidth(), style = TextStyle( fontSize = 18.sp, @@ -881,7 +881,7 @@ fun MifosSelectImageDialog( colors = ButtonDefaults.buttonColors(BlueSecondary) ) { Text( - text = stringResource(id = R.string.delete_image), + text = stringResource(id = R.string.feature_client_delete_image), modifier = Modifier.fillMaxWidth(), style = TextStyle( fontSize = 18.sp, diff --git a/feature/client/src/main/java/com/mifos/feature/client/clientList/presentation/ClientListScreen.kt b/feature/client/src/main/java/com/mifos/feature/client/clientList/presentation/ClientListScreen.kt index 7da338c7dac..e2095143152 100644 --- a/feature/client/src/main/java/com/mifos/feature/client/clientList/presentation/ClientListScreen.kt +++ b/feature/client/src/main/java/com/mifos/feature/client/clientList/presentation/ClientListScreen.kt @@ -310,7 +310,7 @@ fun LazyColumnForClientListApi( .size(40.dp) .clip(CircleShape) .border(width = 1.dp, LightGray, shape = CircleShape), - model = R.drawable.ic_dp_placeholder, + model = R.drawable.feature_client_ic_dp_placeholder, contentDescription = null ) Column( @@ -342,7 +342,7 @@ fun LazyColumnForClientListApi( if (clientPagingList[index]?.sync == true) { AsyncImage( modifier = Modifier.size(20.dp), - model = R.drawable.ic_done_all_black_24dp, + model = R.drawable.feature_client_ic_done_all_black_24dp, contentDescription = null ) } @@ -410,7 +410,7 @@ fun LazyColumnForClientListDb(clientList: List) { .size(40.dp) .clip(CircleShape) .border(width = 1.dp, LightGray, shape = CircleShape), - model = R.drawable.ic_dp_placeholder, + model = R.drawable.feature_client_ic_dp_placeholder, contentDescription = null ) Column( @@ -442,7 +442,7 @@ fun LazyColumnForClientListDb(clientList: List) { if (client.sync) { AsyncImage( modifier = Modifier.size(20.dp), - model = R.drawable.ic_done_all_black_24dp, + model = R.drawable.feature_client_ic_done_all_black_24dp, contentDescription = null ) } diff --git a/feature/client/src/main/res/drawable/ic_done_all_black_24dp.xml b/feature/client/src/main/res/drawable/feature_client_ic_done_all_black_24dp.xml similarity index 100% rename from feature/client/src/main/res/drawable/ic_done_all_black_24dp.xml rename to feature/client/src/main/res/drawable/feature_client_ic_done_all_black_24dp.xml diff --git a/feature/client/src/main/res/drawable/ic_dp_placeholder.png b/feature/client/src/main/res/drawable/feature_client_ic_dp_placeholder.png similarity index 100% rename from feature/client/src/main/res/drawable/ic_dp_placeholder.png rename to feature/client/src/main/res/drawable/feature_client_ic_dp_placeholder.png diff --git a/feature/client/src/main/res/drawable/ic_launcher.png b/feature/client/src/main/res/drawable/feature_client_ic_launcher.png similarity index 100% rename from feature/client/src/main/res/drawable/ic_launcher.png rename to feature/client/src/main/res/drawable/feature_client_ic_launcher.png diff --git a/feature/client/src/main/res/values/strings.xml b/feature/client/src/main/res/values/strings.xml index 1aa661c89c3..f2287957b56 100644 --- a/feature/client/src/main/res/values/strings.xml +++ b/feature/client/src/main/res/values/strings.xml @@ -5,34 +5,34 @@ Failed to load Database Clients No more Clients Available - Account Number - External ID - Activation Date - Office - Mobile Number - Group - Loan Accounts - Savings Accounts - Accounts + Account Number + External ID + Activation Date + Office + Mobile Number + Group + Loan Accounts + Savings Accounts + Accounts - Upload Signature - Survey - Pinpoint location - Notes - More client info - Identifiers - Documents - Charges - Add Savings Account - Add Loan Account + Upload Signature + Survey + Pinpoint location + Notes + More client info + Identifiers + Documents + Charges + Add Savings Account + Add Loan Account - Client Image Uploaded successfully - Client Image Deleted Successfully - Activate Client - Client Not Found - Please Select - Take new image - Upload new image - Delete Image + Client Image Uploaded successfully + Client Image Deleted Successfully + Activate Client + Client Not Found + Please Select + Take new image + Upload new image + Delete Image \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6740e2a6d6f..6920552f421 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,11 +12,11 @@ androidxActivity = "1.8.2" androidxAppCompat = "1.6.1" androidxArchCore = '2.2.0' androidxComposeBom = "2024.02.01" -androidxComposeCompiler = "1.5.6" +androidxComposeCompiler = "1.5.8" androidxComposeUi = "1.6.2" androidxComposeRuntime = "1.6.2" androidxComposeRuntimeTracing = "1.0.0-beta01" -androidxComposeMaterial3 = "1.2.0" +androidxComposeMaterial3 = "1.2.1" androidxCore = "1.12.0" androidxCoreSplashscreen = "1.0.1" androidxEspresso = "3.5.1" @@ -57,7 +57,7 @@ glide = "4.15.1" gmsPlugin = "4.4.1" googleOss = "17.0.1" googleOssPlugin = "0.10.6" -hilt = "2.50" +hilt = "2.51" hiltExt = "1.2.0" junit4 = "4.13.2" junitJupiter = "5.8.2" @@ -158,9 +158,9 @@ androidx-compose-foundation-layout = { group = "androidx.compose.foundation", na # Androidx Compose Material androidx-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compiler" } androidx-material = { module = "androidx.compose.material:material", version.ref = "materialVersion" } -androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidxComposeMaterial3" } androidx-compose-material3-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "androidxComposeMaterial3" } +androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" } # Android Material material = { module = "com.google.android.material:material", version.ref = "material" } @@ -220,6 +220,7 @@ androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = " androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" } androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "androidxUiAutomator" } androidx-tracing-ktx = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "androidxTracing" } +androidx-lifecycle-runtimeTesting = { group = "androidx.lifecycle", name = "lifecycle-runtime-testing", version.ref = "androidxLifecycle" } androidx-window-manager = { module = "androidx.window:window", version.ref = "androidxWindowManager" } # Androidx Work @@ -350,6 +351,15 @@ timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "tim # Truth truth = { group = "com.google.truth", name = "truth", version.ref = "truthVersion" } +# 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" } + [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } @@ -372,3 +382,16 @@ protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } realm = { id = "io.realm.kotlin", version.ref = "realmVersion" } secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" } +# Plugins defined by this project +mifos-android-application = { id = "mifos.android.application", version = "unspecified" } +mifos-android-application-flavors = { id = "mifos.android.application.flavors", 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" } \ No newline at end of file diff --git a/mifosng-android/build.gradle.kts b/mifosng-android/build.gradle.kts index 4751e361fc8..2a7076bc764 100644 --- a/mifosng-android/build.gradle.kts +++ b/mifosng-android/build.gradle.kts @@ -257,7 +257,7 @@ dependencies { debugImplementation(libs.androidx.compose.ui.tooling) implementation(libs.androidx.compose.material3) implementation(libs.androidx.lifecycle.viewModelCompose) - implementation(libs.androidx.material.icons.extended) + implementation(libs.androidx.compose.material.iconsExtended) // ViewModel utilities for Compose implementation(libs.androidx.lifecycle.viewModelCompose) diff --git a/mifosng-android/src/main/AndroidManifest.xml b/mifosng-android/src/main/AndroidManifest.xml index a14f2c28877..f6aeb6061cf 100755 --- a/mifosng-android/src/main/AndroidManifest.xml +++ b/mifosng-android/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ diff --git a/mifosng-android/src/main/res/drawable/background.xml b/mifosng-android/src/main/res/drawable/background.xml index 7ea5bca03f6..137d02e7605 100644 --- a/mifosng-android/src/main/res/drawable/background.xml +++ b/mifosng-android/src/main/res/drawable/background.xml @@ -4,7 +4,7 @@ - \ No newline at end of file diff --git a/mifosng-android/src/main/res/layout/activity_about.xml b/mifosng-android/src/main/res/layout/activity_about.xml index e69d8f139e7..97a16cc7285 100644 --- a/mifosng-android/src/main/res/layout/activity_about.xml +++ b/mifosng-android/src/main/res/layout/activity_about.xml @@ -33,7 +33,7 @@ android:layout_width="120dp" android:layout_height="120dp" android:layout_marginTop="20dp" - app:srcCompat="@drawable/ic_launcher" /> + app:srcCompat="@drawable/feature_client_ic_launcher" /> + android:src="@drawable/feature_auth_mifos_logo" /> + android:src="@drawable/feature_client_ic_dp_placeholder" /> diff --git a/mifosng-android/src/main/res/layout/checker_inbox_tasks_fragment.xml b/mifosng-android/src/main/res/layout/checker_inbox_tasks_fragment.xml index 25bf770f4d5..b8ed18779d6 100644 --- a/mifosng-android/src/main/res/layout/checker_inbox_tasks_fragment.xml +++ b/mifosng-android/src/main/res/layout/checker_inbox_tasks_fragment.xml @@ -29,7 +29,7 @@ android:layout_height="@dimen/dimension_32_dp" android:layout_centerVertical="true" android:scaleType="centerInside" - app:srcCompat="@drawable/ic_mail_outline_24dp" /> + app:srcCompat="@drawable/feature_checker_inbox_task_ic_mail_outline_24dp" /> + app:srcCompat="@drawable/feature_checker_inbox_task_ic_supervisor_account_24dp" /> + app:srcCompat="@drawable/feature_checker_inbox_task_ic_done_all_24dp" /> + app:srcCompat="@drawable/feature_checker_inbox_task_ic_restore_24dp" /> + tools:src="@drawable/feature_client_ic_launcher" /> diff --git a/mifosng-android/src/main/res/layout/item_individual_collection_sheet.xml b/mifosng-android/src/main/res/layout/item_individual_collection_sheet.xml index cd983c84092..5c23f1eabaa 100644 --- a/mifosng-android/src/main/res/layout/item_individual_collection_sheet.xml +++ b/mifosng-android/src/main/res/layout/item_individual_collection_sheet.xml @@ -30,7 +30,7 @@ android:layout_marginTop="@dimen/layout_padding_16dp" android:layout_marginBottom="@dimen/layout_padding_16dp" android:elevation="2dp" - android:src="@drawable/ic_dp_placeholder" + android:src="@drawable/feature_client_ic_dp_placeholder" app:border="false" /> + tools:src="@drawable/feature_client_ic_launcher" /> + app:srcCompat="@drawable/feature_client_ic_done_all_black_24dp" /> \ No newline at end of file diff --git a/mifosng-android/src/main/res/layout/row_client_name.xml b/mifosng-android/src/main/res/layout/row_client_name.xml index 8cec2f5477d..7f39058d5b1 100755 --- a/mifosng-android/src/main/res/layout/row_client_name.xml +++ b/mifosng-android/src/main/res/layout/row_client_name.xml @@ -32,7 +32,7 @@ android:layout_height="40dp" android:layout_gravity="center_vertical" android:elevation="2dp" - android:src="@drawable/ic_dp_placeholder" + android:src="@drawable/feature_client_ic_dp_placeholder" app:border="false" /> @@ -71,7 +71,7 @@ android:layout_height="20dp" android:layout_gravity="center_vertical" android:gravity="center" - app:srcCompat="@drawable/ic_done_all_black_24dp" + app:srcCompat="@drawable/feature_client_ic_done_all_black_24dp" /> diff --git a/mifosng-android/src/main/res/layout/row_group_name.xml b/mifosng-android/src/main/res/layout/row_group_name.xml index 78465ee2e12..7ffdcf9dd6c 100644 --- a/mifosng-android/src/main/res/layout/row_group_name.xml +++ b/mifosng-android/src/main/res/layout/row_group_name.xml @@ -60,7 +60,7 @@ android:layout_height="20dp" android:layout_gravity="center_vertical" android:gravity="center" - app:srcCompat="@drawable/ic_done_all_black_24dp" /> + app:srcCompat="@drawable/feature_client_ic_done_all_black_24dp" /> diff --git a/mifosng-android/src/main/res/layout/row_surveys_list_item.xml b/mifosng-android/src/main/res/layout/row_surveys_list_item.xml index 9cd5b4482cd..846d93a0efe 100755 --- a/mifosng-android/src/main/res/layout/row_surveys_list_item.xml +++ b/mifosng-android/src/main/res/layout/row_surveys_list_item.xml @@ -41,5 +41,5 @@ android:layout_alignParentBottom="false" android:layout_gravity="center_vertical" android:contentDescription="Done Image" - app:srcCompat="@drawable/ic_done_all_black_24dp" /> + app:srcCompat="@drawable/feature_client_ic_done_all_black_24dp" /> diff --git a/mifosng-android/src/main/res/menu/menu_nav_drawer.xml b/mifosng-android/src/main/res/menu/menu_nav_drawer.xml index 9f2a0609900..8ac1f28c57d 100644 --- a/mifosng-android/src/main/res/menu/menu_nav_drawer.xml +++ b/mifosng-android/src/main/res/menu/menu_nav_drawer.xml @@ -14,14 +14,14 @@ ಹಿಂತೆಗೆದುಕೊಳ್ಳುವಿಕೆ ಒಟ್ಟು ಠೇವಣಿಗಳು ಒಟ್ಟು ವಿತರಣೆಗಳು - ವ್ಯವಹಾರ ಐಡಿ:% 1 $ d, ಸಮತೋಲನವನ್ನು ರನ್ನಿಂಗ್:% 2 $ f + ವ್ಯವಹಾರ ಐಡಿ:% 1$d, ಸಮತೋಲನವನ್ನು ರನ್ನಿಂಗ್:% 2$f ಹುಡುಕಿ ಹುಡುಕಾಟ ಫಲಿತಾಂಶಗಳು ಕೇಂದ್ರವನ್ನು ಆಯ್ಕೆಮಾಡಿ diff --git a/settings.gradle.kts b/settings.gradle.kts index e641c1a0869..38d2a4cfbe5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,5 @@ pluginManagement { + includeBuild("build-logic") repositories { google() mavenCentral()