Use Anvil KSP instead of the Square KAPT one (#3564)
* Use Anvil KSP instead of the Square KAPT one * Fix several configuration cache, lint and test issues * Allow incremental kotlin compilation in the CI * Workaround Robolectric + Compose issue that caused `AppNotIdleException` * Update the `enterprise` commit hash * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
5fcc80a383
commit
4a43fcb69a
54 changed files with 463 additions and 348 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -9,7 +9,7 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
2
.github/workflows/build_enterprise.yml
vendored
2
.github/workflows/build_enterprise.yml
vendored
|
|
@ -9,7 +9,7 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
2
.github/workflows/maestro.yml
vendored
2
.github/workflows/maestro.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
||||||
- cron: "0 4 * * *"
|
- cron: "0 4 * * *"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
2
.github/workflows/nightlyReports.yml
vendored
2
.github/workflows/nightlyReports.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
2
.github/workflows/nightly_enterprise.yml
vendored
2
.github/workflows/nightly_enterprise.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
||||||
- cron: "0 4 * * *"
|
- cron: "0 4 * * *"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
2
.github/workflows/quality.yml
vendored
2
.github/workflows/quality.yml
vendored
|
|
@ -9,7 +9,7 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
2
.github/workflows/recordScreenshots.yml
vendored
2
.github/workflows/recordScreenshots.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false -Dsonar.gradle.skipCompile=true
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
record:
|
record:
|
||||||
|
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
2
.github/workflows/sonar.yml
vendored
2
.github/workflows/sonar.yml
vendored
|
|
@ -9,7 +9,7 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true
|
||||||
GROUP: ${{ format('sonar-{0}', github.ref) }}
|
GROUP: ${{ format('sonar-{0}', github.ref) }}
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -9,7 +9,7 @@ on:
|
||||||
|
|
||||||
# Enrich gradle.properties for CI/CD
|
# Enrich gradle.properties for CI/CD
|
||||||
env:
|
env:
|
||||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.incremental=false -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC
|
||||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
alias(libs.plugins.kapt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
@ -16,6 +15,6 @@ dependencies {
|
||||||
implementation(libs.anvil.compiler.utils)
|
implementation(libs.anvil.compiler.utils)
|
||||||
implementation(libs.kotlinpoet)
|
implementation(libs.kotlinpoet)
|
||||||
implementation(libs.dagger)
|
implementation(libs.dagger)
|
||||||
compileOnly(libs.google.autoservice.annotations)
|
implementation(libs.ksp.plugin)
|
||||||
kapt(libs.google.autoservice)
|
implementation(libs.kotlinpoet.ksp)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@file:OptIn(ExperimentalAnvilApi::class)
|
|
||||||
|
|
||||||
package io.element.android.anvilcodegen
|
|
||||||
|
|
||||||
import com.google.auto.service.AutoService
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
|
||||||
import com.squareup.anvil.annotations.ExperimentalAnvilApi
|
|
||||||
import com.squareup.anvil.compiler.api.AnvilCompilationException
|
|
||||||
import com.squareup.anvil.compiler.api.AnvilContext
|
|
||||||
import com.squareup.anvil.compiler.api.CodeGenerator
|
|
||||||
import com.squareup.anvil.compiler.api.GeneratedFile
|
|
||||||
import com.squareup.anvil.compiler.api.createGeneratedFile
|
|
||||||
import com.squareup.anvil.compiler.internal.asClassName
|
|
||||||
import com.squareup.anvil.compiler.internal.buildFile
|
|
||||||
import com.squareup.anvil.compiler.internal.fqName
|
|
||||||
import com.squareup.anvil.compiler.internal.reference.ClassReference
|
|
||||||
import com.squareup.anvil.compiler.internal.reference.asClassName
|
|
||||||
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
|
|
||||||
import com.squareup.kotlinpoet.AnnotationSpec
|
|
||||||
import com.squareup.kotlinpoet.ClassName
|
|
||||||
import com.squareup.kotlinpoet.FileSpec
|
|
||||||
import com.squareup.kotlinpoet.FunSpec
|
|
||||||
import com.squareup.kotlinpoet.KModifier
|
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
|
||||||
import com.squareup.kotlinpoet.STAR
|
|
||||||
import com.squareup.kotlinpoet.TypeSpec
|
|
||||||
import dagger.Binds
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.assisted.Assisted
|
|
||||||
import dagger.assisted.AssistedFactory
|
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import dagger.multibindings.IntoMap
|
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
|
||||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an anvil plugin that allows Node to use [ContributesNode] alone and let this plugin automatically
|
|
||||||
* handle the rest of the Dagger wiring required for constructor injection.
|
|
||||||
*/
|
|
||||||
@AutoService(CodeGenerator::class)
|
|
||||||
class ContributesNodeCodeGenerator : CodeGenerator {
|
|
||||||
override fun isApplicable(context: AnvilContext): Boolean = true
|
|
||||||
|
|
||||||
override fun generateCode(codeGenDir: File, module: ModuleDescriptor, projectFiles: Collection<KtFile>): Collection<GeneratedFile> {
|
|
||||||
return projectFiles.classAndInnerClassReferences(module)
|
|
||||||
.filter { it.isAnnotatedWith(ContributesNode::class.fqName) }
|
|
||||||
.flatMap { listOf(generateModule(it, codeGenDir, module), generateAssistedFactory(it, codeGenDir, module)) }
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateModule(nodeClass: ClassReference.Psi, codeGenDir: File, module: ModuleDescriptor): GeneratedFile {
|
|
||||||
val generatedPackage = nodeClass.packageFqName.toString()
|
|
||||||
val moduleClassName = "${nodeClass.shortName}_Module"
|
|
||||||
val scope = nodeClass.annotations.single { it.fqName == ContributesNode::class.fqName }.scope()
|
|
||||||
val content = FileSpec.buildFile(generatedPackage, moduleClassName) {
|
|
||||||
addType(
|
|
||||||
TypeSpec.classBuilder(moduleClassName)
|
|
||||||
.addModifiers(KModifier.ABSTRACT)
|
|
||||||
.addAnnotation(Module::class)
|
|
||||||
.addAnnotation(AnnotationSpec.builder(ContributesTo::class).addMember("%T::class", scope.asClassName()).build())
|
|
||||||
.addFunction(
|
|
||||||
FunSpec.builder("bind${nodeClass.shortName}Factory")
|
|
||||||
.addModifiers(KModifier.ABSTRACT)
|
|
||||||
.addParameter("factory", ClassName(generatedPackage, "${nodeClass.shortName}_AssistedFactory"))
|
|
||||||
.returns(assistedNodeFactoryFqName.asClassName(module).parameterizedBy(STAR))
|
|
||||||
.addAnnotation(Binds::class)
|
|
||||||
.addAnnotation(IntoMap::class)
|
|
||||||
.addAnnotation(
|
|
||||||
AnnotationSpec.Companion.builder(nodeKeyFqName.asClassName(module)).addMember(
|
|
||||||
"%T::class",
|
|
||||||
nodeClass.asClassName()
|
|
||||||
).build()
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return createGeneratedFile(codeGenDir, generatedPackage, moduleClassName, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateAssistedFactory(nodeClass: ClassReference.Psi, codeGenDir: File, module: ModuleDescriptor): GeneratedFile {
|
|
||||||
val generatedPackage = nodeClass.packageFqName.toString()
|
|
||||||
val assistedFactoryClassName = "${nodeClass.shortName}_AssistedFactory"
|
|
||||||
val constructor = nodeClass.constructors.singleOrNull { it.isAnnotatedWith(AssistedInject::class.fqName) }
|
|
||||||
val assistedParameters = constructor?.parameters?.filter { it.isAnnotatedWith(Assisted::class.fqName) }.orEmpty()
|
|
||||||
if (constructor == null || assistedParameters.size != 2) {
|
|
||||||
throw AnvilCompilationException(
|
|
||||||
"${nodeClass.fqName} must have an @AssistedInject constructor with 2 @Assisted parameters",
|
|
||||||
element = nodeClass.clazz,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val contextAssistedParam = assistedParameters[0]
|
|
||||||
if (contextAssistedParam.name != "buildContext") {
|
|
||||||
throw AnvilCompilationException(
|
|
||||||
"${nodeClass.fqName} @Assisted parameter must be named buildContext",
|
|
||||||
element = contextAssistedParam.parameter,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val pluginsAssistedParam = assistedParameters[1]
|
|
||||||
if (pluginsAssistedParam.name != "plugins") {
|
|
||||||
throw AnvilCompilationException(
|
|
||||||
"${nodeClass.fqName} @Assisted parameter must be named plugins",
|
|
||||||
element = pluginsAssistedParam.parameter,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val nodeClassName = nodeClass.asClassName()
|
|
||||||
val buildContextClassName = contextAssistedParam.type().asTypeName()
|
|
||||||
val pluginsClassName = pluginsAssistedParam.type().asTypeName()
|
|
||||||
val content = FileSpec.buildFile(generatedPackage, assistedFactoryClassName) {
|
|
||||||
addType(
|
|
||||||
TypeSpec.interfaceBuilder(assistedFactoryClassName)
|
|
||||||
.addSuperinterface(assistedNodeFactoryFqName.asClassName(module).parameterizedBy(nodeClassName))
|
|
||||||
.addAnnotation(AssistedFactory::class)
|
|
||||||
.addFunction(
|
|
||||||
FunSpec.builder("create")
|
|
||||||
.addModifiers(KModifier.OVERRIDE, KModifier.ABSTRACT)
|
|
||||||
.addParameter("buildContext", buildContextClassName)
|
|
||||||
.addParameter("plugins", pluginsClassName)
|
|
||||||
.returns(nodeClassName)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return createGeneratedFile(codeGenDir, generatedPackage, assistedFactoryClassName, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val assistedNodeFactoryFqName = FqName("io.element.android.libraries.architecture.AssistedNodeFactory")
|
|
||||||
private val nodeKeyFqName = FqName("io.element.android.libraries.architecture.NodeKey")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.anvilcodegen
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.KspExperimental
|
||||||
|
import com.google.devtools.ksp.getConstructors
|
||||||
|
import com.google.devtools.ksp.isAnnotationPresent
|
||||||
|
import com.google.devtools.ksp.processing.CodeGenerator
|
||||||
|
import com.google.devtools.ksp.processing.Dependencies
|
||||||
|
import com.google.devtools.ksp.processing.KSPLogger
|
||||||
|
import com.google.devtools.ksp.processing.Resolver
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||||
|
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||||
|
import com.google.devtools.ksp.symbol.KSType
|
||||||
|
import com.google.devtools.ksp.validate
|
||||||
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
|
import com.squareup.kotlinpoet.ClassName
|
||||||
|
import com.squareup.kotlinpoet.FileSpec
|
||||||
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
|
import com.squareup.kotlinpoet.KModifier
|
||||||
|
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||||
|
import com.squareup.kotlinpoet.STAR
|
||||||
|
import com.squareup.kotlinpoet.TypeSpec
|
||||||
|
import com.squareup.kotlinpoet.ksp.toTypeName
|
||||||
|
import com.squareup.kotlinpoet.ksp.writeTo
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import dagger.multibindings.IntoMap
|
||||||
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
|
||||||
|
class ContributesNodeProcessor(
|
||||||
|
private val logger: KSPLogger,
|
||||||
|
private val codeGenerator: CodeGenerator,
|
||||||
|
private val config: Config,
|
||||||
|
) : SymbolProcessor {
|
||||||
|
data class Config(
|
||||||
|
val enableLogging: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||||
|
val annotatedSymbols = resolver.getSymbolsWithAnnotation(ContributesNode::class.qualifiedName!!)
|
||||||
|
.filterIsInstance<KSClassDeclaration>()
|
||||||
|
|
||||||
|
val (validSymbols, invalidSymbols) = annotatedSymbols.partition { it.validate() }
|
||||||
|
|
||||||
|
if (validSymbols.isEmpty()) return invalidSymbols
|
||||||
|
|
||||||
|
for (ksClass in validSymbols) {
|
||||||
|
if (config.enableLogging) {
|
||||||
|
logger.warn("Processing ${ksClass.qualifiedName?.asString()}")
|
||||||
|
}
|
||||||
|
generateModule(ksClass)
|
||||||
|
generateFactory(ksClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
return invalidSymbols
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateModule(ksClass: KSClassDeclaration) {
|
||||||
|
val annotation = ksClass.annotations.find { it.shortName.asString() == "ContributesNode" }!!
|
||||||
|
val scope = annotation.arguments.find { it.name?.asString() == "scope" }!!.value as KSType
|
||||||
|
val modulePackage = ksClass.packageName.asString()
|
||||||
|
val moduleClassName = "${ksClass.simpleName.asString()}_Module"
|
||||||
|
val content = FileSpec.builder(
|
||||||
|
packageName = modulePackage,
|
||||||
|
fileName = moduleClassName,
|
||||||
|
)
|
||||||
|
.addType(
|
||||||
|
TypeSpec.classBuilder(moduleClassName)
|
||||||
|
.addModifiers(KModifier.ABSTRACT)
|
||||||
|
.addAnnotation(Module::class)
|
||||||
|
.addAnnotation(AnnotationSpec.builder(ContributesTo::class).addMember("%T::class", scope.toTypeName()).build())
|
||||||
|
.addFunction(
|
||||||
|
FunSpec.builder("bind${ksClass.simpleName.asString()}Factory")
|
||||||
|
.addModifiers(KModifier.ABSTRACT)
|
||||||
|
.addParameter("factory", ClassName(modulePackage, "${ksClass.simpleName.asString()}_AssistedFactory"))
|
||||||
|
.returns(ClassName.bestGuess(assistedNodeFactoryFqName.asString()).parameterizedBy(STAR))
|
||||||
|
.addAnnotation(Binds::class)
|
||||||
|
.addAnnotation(IntoMap::class)
|
||||||
|
.addAnnotation(
|
||||||
|
AnnotationSpec.Companion.builder(ClassName.bestGuess(nodeKeyFqName.asString())).addMember(
|
||||||
|
"%T::class",
|
||||||
|
ClassName.bestGuess(ksClass.qualifiedName!!.asString())
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
content.writeTo(
|
||||||
|
codeGenerator = codeGenerator,
|
||||||
|
dependencies = Dependencies(
|
||||||
|
aggregating = true,
|
||||||
|
ksClass.containingFile!!
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(KspExperimental::class)
|
||||||
|
private fun generateFactory(ksClass: KSClassDeclaration) {
|
||||||
|
val generatedPackage = ksClass.packageName.asString()
|
||||||
|
val assistedFactoryClassName = "${ksClass.simpleName.asString()}_AssistedFactory"
|
||||||
|
val constructor = ksClass.getConstructors().singleOrNull { it.isAnnotationPresent(AssistedInject::class) }
|
||||||
|
val assistedParameters = constructor?.parameters?.filter { it.isAnnotationPresent(Assisted::class) }.orEmpty()
|
||||||
|
if (constructor == null || assistedParameters.size != 2) {
|
||||||
|
error(
|
||||||
|
"${ksClass.qualifiedName} must have an @AssistedInject constructor with 2 @Assisted parameters",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val contextAssistedParam = assistedParameters[0]
|
||||||
|
if (contextAssistedParam.name?.asString() != "buildContext") {
|
||||||
|
error(
|
||||||
|
"${ksClass.qualifiedName} @Assisted parameter must be named buildContext",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val pluginsAssistedParam = assistedParameters[1]
|
||||||
|
if (pluginsAssistedParam.name?.asString() != "plugins") {
|
||||||
|
error(
|
||||||
|
"${ksClass.qualifiedName} @Assisted parameter must be named plugins",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val nodeClassName = ClassName.bestGuess(ksClass.qualifiedName!!.asString())
|
||||||
|
val buildContextClassName = contextAssistedParam.type.toTypeName()
|
||||||
|
val pluginsClassName = pluginsAssistedParam.type.toTypeName()
|
||||||
|
val content = FileSpec.builder(generatedPackage, assistedFactoryClassName)
|
||||||
|
.addType(
|
||||||
|
TypeSpec.interfaceBuilder(assistedFactoryClassName)
|
||||||
|
.addSuperinterface(ClassName.bestGuess(assistedNodeFactoryFqName.asString()).parameterizedBy(nodeClassName))
|
||||||
|
.addAnnotation(AssistedFactory::class)
|
||||||
|
.addFunction(
|
||||||
|
FunSpec.builder("create")
|
||||||
|
.addModifiers(KModifier.OVERRIDE, KModifier.ABSTRACT)
|
||||||
|
.addParameter("buildContext", buildContextClassName)
|
||||||
|
.addParameter("plugins", pluginsClassName)
|
||||||
|
.returns(nodeClassName)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
content.writeTo(
|
||||||
|
codeGenerator = codeGenerator,
|
||||||
|
dependencies = Dependencies(
|
||||||
|
aggregating = true,
|
||||||
|
ksClass.containingFile!!
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val assistedNodeFactoryFqName = FqName("io.element.android.libraries.architecture.AssistedNodeFactory")
|
||||||
|
private val nodeKeyFqName = FqName("io.element.android.libraries.architecture.NodeKey")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022-2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.anvilcodegen
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||||
|
|
||||||
|
class ContributesNodeProcessorProvider : SymbolProcessorProvider {
|
||||||
|
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||||
|
val enableLogging = environment.options["enableLogging"]?.toBoolean() ?: false
|
||||||
|
return ContributesNodeProcessor(
|
||||||
|
logger = environment.logger,
|
||||||
|
codeGenerator = environment.codeGenerator,
|
||||||
|
config = ContributesNodeProcessor.Config(enableLogging = enableLogging),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
io.element.android.anvilcodegen.ContributesNodeProcessorProvider
|
||||||
|
|
@ -11,6 +11,7 @@ import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
|
||||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||||
import com.android.build.gradle.tasks.GenerateBuildConfig
|
import com.android.build.gradle.tasks.GenerateBuildConfig
|
||||||
import extension.AssetCopyTask
|
import extension.AssetCopyTask
|
||||||
|
import extension.ComponentMergingStrategy
|
||||||
import extension.GitBranchNameValueSource
|
import extension.GitBranchNameValueSource
|
||||||
import extension.GitRevisionValueSource
|
import extension.GitRevisionValueSource
|
||||||
import extension.allEnterpriseImpl
|
import extension.allEnterpriseImpl
|
||||||
|
|
@ -235,22 +236,24 @@ knit {
|
||||||
setupAnvil(
|
setupAnvil(
|
||||||
generateDaggerCode = true,
|
generateDaggerCode = true,
|
||||||
generateDaggerFactoriesUsingAnvil = false,
|
generateDaggerFactoriesUsingAnvil = false,
|
||||||
|
componentMergingStrategy = ComponentMergingStrategy.KSP,
|
||||||
)
|
)
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
allLibrariesImpl()
|
allLibrariesImpl()
|
||||||
allServicesImpl()
|
allServicesImpl()
|
||||||
if (isEnterpriseBuild) {
|
if (isEnterpriseBuild) {
|
||||||
allEnterpriseImpl(rootDir, logger)
|
allEnterpriseImpl(project)
|
||||||
implementation(projects.appicon.enterprise)
|
implementation(projects.appicon.enterprise)
|
||||||
} else {
|
} else {
|
||||||
implementation(projects.appicon.element)
|
implementation(projects.appicon.element)
|
||||||
}
|
}
|
||||||
allFeaturesImpl(rootDir, logger)
|
allFeaturesImpl(project)
|
||||||
implementation(projects.features.migration.api)
|
implementation(projects.features.migration.api)
|
||||||
implementation(projects.appnav)
|
implementation(projects.appnav)
|
||||||
implementation(projects.appconfig)
|
implementation(projects.appconfig)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
|
implementation(projects.services.analytics.compose)
|
||||||
|
|
||||||
if (ModulesConfig.pushProvidersConfig.includeFirebase) {
|
if (ModulesConfig.pushProvidersConfig.includeFirebase) {
|
||||||
"gplayImplementation"(projects.libraries.pushproviders.firebase)
|
"gplayImplementation"(projects.libraries.pushproviders.firebase)
|
||||||
|
|
@ -275,6 +278,8 @@ dependencies {
|
||||||
implementation(libs.serialization.json)
|
implementation(libs.serialization.json)
|
||||||
|
|
||||||
implementation(libs.matrix.emojibase.bindings)
|
implementation(libs.matrix.emojibase.bindings)
|
||||||
|
// Needed for UtdTracker
|
||||||
|
implementation(libs.matrix.sdk)
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.test.robolectric)
|
testImplementation(libs.test.robolectric)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ package io.element.android.x.di
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.squareup.anvil.annotations.MergeComponent
|
import com.squareup.anvil.annotations.MergeComponent
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Component
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
|
@ -19,7 +18,7 @@ import io.element.android.libraries.di.SingleIn
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
@MergeComponent(AppScope::class)
|
@MergeComponent(AppScope::class)
|
||||||
interface AppComponent : NodeFactoriesBindings {
|
interface AppComponent : NodeFactoriesBindings {
|
||||||
@Component.Factory
|
@MergeComponent.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(
|
fun create(
|
||||||
@ApplicationContext @BindsInstance
|
@ApplicationContext @BindsInstance
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ package io.element.android.x.di
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Subcomponent
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
@ -20,7 +19,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
@SingleIn(RoomScope::class)
|
@SingleIn(RoomScope::class)
|
||||||
@MergeSubcomponent(RoomScope::class)
|
@MergeSubcomponent(RoomScope::class)
|
||||||
interface RoomComponent : NodeFactoriesBindings {
|
interface RoomComponent : NodeFactoriesBindings {
|
||||||
@Subcomponent.Builder
|
@MergeSubcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
fun room(room: MatrixRoom): Builder
|
fun room(room: MatrixRoom): Builder
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ package io.element.android.x.di
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Subcomponent
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
@ -20,7 +19,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
@SingleIn(SessionScope::class)
|
@SingleIn(SessionScope::class)
|
||||||
@MergeSubcomponent(SessionScope::class)
|
@MergeSubcomponent(SessionScope::class)
|
||||||
interface SessionComponent : NodeFactoriesBindings {
|
interface SessionComponent : NodeFactoriesBindings {
|
||||||
@Subcomponent.Builder
|
@MergeSubcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
fun client(matrixClient: MatrixClient): Builder
|
fun client(matrixClient: MatrixClient): Builder
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ android {
|
||||||
setupAnvil()
|
setupAnvil()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
allFeaturesApi(rootDir, logger)
|
allFeaturesApi(project)
|
||||||
|
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,9 @@ allprojects {
|
||||||
// To have XML report for Danger
|
// To have XML report for Danger
|
||||||
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
|
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
|
||||||
}
|
}
|
||||||
|
val generatedPath = "${layout.buildDirectory.asFile.get()}/generated/"
|
||||||
filter {
|
filter {
|
||||||
exclude { element -> element.file.path.contains("${layout.buildDirectory.asFile.get()}/generated/") }
|
exclude { element -> element.file.path.contains(generatedPath) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Dependency check
|
// Dependency check
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit ceb65e32d95052c028a37654a4a0410639f69053
|
Subproject commit b4f0427e3595049d39846aabcdc06e818f2e96ea
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import extension.ComponentMergingStrategy
|
||||||
import extension.setupAnvil
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -22,7 +23,7 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAnvil()
|
setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ package io.element.android.features.createroom.impl.di
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.Subcomponent
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
|
@ -17,7 +16,7 @@ import io.element.android.libraries.di.SingleIn
|
||||||
@SingleIn(CreateRoomScope::class)
|
@SingleIn(CreateRoomScope::class)
|
||||||
@MergeSubcomponent(CreateRoomScope::class)
|
@MergeSubcomponent(CreateRoomScope::class)
|
||||||
interface CreateRoomComponent : NodeFactoriesBindings {
|
interface CreateRoomComponent : NodeFactoriesBindings {
|
||||||
@Subcomponent.Builder
|
@MergeSubcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
fun build(): CreateRoomComponent
|
fun build(): CreateRoomComponent
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@
|
||||||
|
|
||||||
package io.element.android.features.lockscreen.impl.unlock
|
package io.element.android.features.lockscreen.impl.unlock
|
||||||
|
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock
|
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock
|
||||||
|
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockError
|
||||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
@ -23,6 +25,9 @@ open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
|
||||||
aPinUnlockState(showBiometricUnlock = false),
|
aPinUnlockState(showBiometricUnlock = false),
|
||||||
aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0),
|
aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0),
|
||||||
aPinUnlockState(signOutAction = AsyncAction.Loading),
|
aPinUnlockState(signOutAction = AsyncAction.Loading),
|
||||||
|
aPinUnlockState(biometricUnlockResult = BiometricUnlock.AuthenticationResult.Failure(
|
||||||
|
BiometricUnlockError(BiometricPrompt.ERROR_LOCKOUT, "Biometric auth disabled")
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import extension.ComponentMergingStrategy
|
||||||
import extension.setupAnvil
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -23,7 +24,7 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAnvil()
|
setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.appconfig)
|
implementation(projects.appconfig)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ package io.element.android.features.login.impl.di
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||||
import dagger.Subcomponent
|
|
||||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
|
@ -17,7 +16,7 @@ import io.element.android.libraries.di.SingleIn
|
||||||
@SingleIn(QrCodeLoginScope::class)
|
@SingleIn(QrCodeLoginScope::class)
|
||||||
@MergeSubcomponent(QrCodeLoginScope::class)
|
@MergeSubcomponent(QrCodeLoginScope::class)
|
||||||
interface QrCodeLoginComponent : NodeFactoriesBindings {
|
interface QrCodeLoginComponent : NodeFactoriesBindings {
|
||||||
@Subcomponent.Builder
|
@MergeSubcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
fun build(): QrCodeLoginComponent
|
fun build(): QrCodeLoginComponent
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,15 @@ import io.element.android.features.login.impl.qrcode.QrCodeLoginManager
|
||||||
import io.element.android.libraries.architecture.AssistedNodeFactory
|
import io.element.android.libraries.architecture.AssistedNodeFactory
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
|
||||||
internal class FakeQrCodeLoginComponent(private val qrCodeLoginManager: QrCodeLoginManager) :
|
internal class FakeMergedQrCodeLoginComponent(private val qrCodeLoginManager: QrCodeLoginManager) :
|
||||||
QrCodeLoginComponent {
|
MergedQrCodeLoginComponent {
|
||||||
// Ignore this error, it does override a method once code generation is done
|
// Ignore this error, it does override a method once code generation is done
|
||||||
override fun qrCodeLoginManager(): QrCodeLoginManager = qrCodeLoginManager
|
override fun qrCodeLoginManager(): QrCodeLoginManager = qrCodeLoginManager
|
||||||
|
|
||||||
class Builder(private val qrCodeLoginManager: QrCodeLoginManager = FakeQrCodeLoginManager()) :
|
class Builder(private val qrCodeLoginManager: QrCodeLoginManager = FakeQrCodeLoginManager()) :
|
||||||
QrCodeLoginComponent.Builder {
|
QrCodeLoginComponent.Builder {
|
||||||
override fun build(): QrCodeLoginComponent {
|
override fun build(): QrCodeLoginComponent {
|
||||||
return FakeQrCodeLoginComponent(qrCodeLoginManager)
|
return FakeMergedQrCodeLoginComponent(qrCodeLoginManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl
|
import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||||
import io.element.android.features.login.impl.di.FakeQrCodeLoginComponent
|
import io.element.android.features.login.impl.di.FakeMergedQrCodeLoginComponent
|
||||||
import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationStep
|
import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationStep
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||||
|
|
@ -200,7 +200,7 @@ class QrCodeLoginFlowNodeTest {
|
||||||
return QrCodeLoginFlowNode(
|
return QrCodeLoginFlowNode(
|
||||||
buildContext = buildContext,
|
buildContext = buildContext,
|
||||||
plugins = emptyList(),
|
plugins = emptyList(),
|
||||||
qrCodeLoginComponentBuilder = FakeQrCodeLoginComponent.Builder(qrCodeLoginManager),
|
qrCodeLoginComponentBuilder = FakeMergedQrCodeLoginComponent.Builder(qrCodeLoginManager),
|
||||||
defaultLoginUserStory = defaultLoginUserStory,
|
defaultLoginUserStory = defaultLoginUserStory,
|
||||||
coroutineDispatchers = coroutineDispatchers,
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.session
|
|
||||||
|
|
||||||
data class SessionState(
|
|
||||||
val isSessionVerified: Boolean,
|
|
||||||
val isKeyBackupEnabled: Boolean,
|
|
||||||
)
|
|
||||||
|
|
@ -28,6 +28,7 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
|
import io.element.android.tests.testutils.setSafeContent
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
|
|
@ -100,7 +101,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setPinne
|
||||||
onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onLinkClick: (String) -> Unit = EnsureNeverCalledWithParam(),
|
onLinkClick: (String) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
) {
|
) {
|
||||||
setContent {
|
setSafeContent {
|
||||||
PinnedMessagesListView(
|
PinnedMessagesListView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams
|
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
import io.element.android.tests.testutils.setSafeContent
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -151,7 +152,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimel
|
||||||
onJoinCallClick: () -> Unit = EnsureNeverCalled(),
|
onJoinCallClick: () -> Unit = EnsureNeverCalled(),
|
||||||
forceJumpToBottomVisibility: Boolean = false,
|
forceJumpToBottomVisibility: Boolean = false,
|
||||||
) {
|
) {
|
||||||
setContent {
|
setSafeContent {
|
||||||
TimelineView(
|
TimelineView(
|
||||||
state = state,
|
state = state,
|
||||||
typingNotificationState = typingNotificationState,
|
typingNotificationState = typingNotificationState,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
* Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.roomlist.impl
|
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|
||||||
|
|
||||||
open class InvitesStateProvider : PreviewParameterProvider<InvitesState> {
|
|
||||||
override val values: Sequence<InvitesState>
|
|
||||||
get() = sequenceOf(
|
|
||||||
InvitesState.SeenInvites,
|
|
||||||
InvitesState.NewInvites,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -51,12 +51,6 @@ data class RoomListState(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class InvitesState {
|
|
||||||
NoInvites,
|
|
||||||
SeenInvites,
|
|
||||||
NewInvites,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class SecurityBannerState {
|
enum class SecurityBannerState {
|
||||||
None,
|
None,
|
||||||
SetUpRecovery,
|
SetUpRecovery,
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ class ResetIdentityFlowManagerTest {
|
||||||
var result: AsyncData.Success<IdentityResetHandle>? = null
|
var result: AsyncData.Success<IdentityResetHandle>? = null
|
||||||
flowManager.getResetHandle().test {
|
flowManager.getResetHandle().test {
|
||||||
assertThat(awaitItem().isLoading()).isTrue()
|
assertThat(awaitItem().isLoading()).isTrue()
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
result = awaitItem() as? AsyncData.Success<IdentityResetHandle>
|
result = awaitItem() as? AsyncData.Success<IdentityResetHandle>
|
||||||
assertThat(result).isNotNull()
|
assertThat(result).isNotNull()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,13 @@ kotlin.code.style=official
|
||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
|
|
||||||
org.gradle.caching=true
|
|
||||||
org.gradle.configureondemand=true
|
org.gradle.configureondemand=true
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
# Check here for the reasons https://github.com/square/anvil/issues/693
|
|
||||||
# useClasspathSnapshot=false is not enough in most cases.
|
# Caching
|
||||||
kotlin.incremental=false
|
org.gradle.caching=true
|
||||||
|
org.gradle.configuration-cache=true
|
||||||
|
kotlin.incremental=true
|
||||||
|
|
||||||
# Dummy values for signing secrets / nightly
|
# Dummy values for signing secrets / nightly
|
||||||
signing.element.nightly.storePassword=Secret
|
signing.element.nightly.storePassword=Secret
|
||||||
|
|
@ -46,3 +47,9 @@ android.experimental.enableTestFixtures=true
|
||||||
|
|
||||||
# Create BuildConfig files as bytecode to avoid Java compilation phase
|
# Create BuildConfig files as bytecode to avoid Java compilation phase
|
||||||
android.enableBuildConfigAsBytecode=true
|
android.enableBuildConfigAsBytecode=true
|
||||||
|
|
||||||
|
# Add the KSP code generation annotations to the list of contributing annotations for Anvil
|
||||||
|
com.squareup.anvil.kspContributingAnnotations=io.element.android.anvilannotations.ContributesNode
|
||||||
|
|
||||||
|
# Only apply KSP to main sources
|
||||||
|
ksp.allow.all.target.configuration=false
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
# Project
|
# Project
|
||||||
android_gradle_plugin = "8.6.1"
|
android_gradle_plugin = "8.6.1"
|
||||||
kotlin = "1.9.25"
|
kotlin = "1.9.25"
|
||||||
|
kotlinpoetKsp = "1.17.0"
|
||||||
ksp = "1.9.25-1.0.20"
|
ksp = "1.9.25-1.0.20"
|
||||||
firebaseAppDistribution = "5.0.0"
|
firebaseAppDistribution = "5.0.0"
|
||||||
|
|
||||||
|
|
@ -49,7 +50,7 @@ telephoto = "0.13.0"
|
||||||
|
|
||||||
# DI
|
# DI
|
||||||
dagger = "2.52"
|
dagger = "2.52"
|
||||||
anvil = "2.4.9"
|
anvil = "0.3.1"
|
||||||
|
|
||||||
# Auto service
|
# Auto service
|
||||||
autoservice = "1.1.1"
|
autoservice = "1.1.1"
|
||||||
|
|
@ -63,14 +64,17 @@ kover = "0.8.3"
|
||||||
android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref = "android_gradle_plugin" }
|
android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref = "android_gradle_plugin" }
|
||||||
# https://developer.android.com/studio/write/java8-support#library-desugaring-versions
|
# https://developer.android.com/studio/write/java8-support#library-desugaring-versions
|
||||||
android_desugar = "com.android.tools:desugar_jdk_libs:2.1.2"
|
android_desugar = "com.android.tools:desugar_jdk_libs:2.1.2"
|
||||||
anvil_gradle_plugin = { module = "com.squareup.anvil:gradle-plugin", version.ref = "anvil" }
|
anvil_gradle_plugin = { module = "dev.zacsweers.anvil:gradle-plugin", version.ref = "anvil" }
|
||||||
kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
|
kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinpoetKsp" }
|
||||||
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
|
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
|
||||||
|
ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
||||||
gms_google_services = "com.google.gms:google-services:4.4.2"
|
gms_google_services = "com.google.gms:google-services:4.4.2"
|
||||||
# https://firebase.google.com/docs/android/setup#available-libraries
|
# https://firebase.google.com/docs/android/setup#available-libraries
|
||||||
google_firebase_bom = "com.google.firebase:firebase-bom:33.4.0"
|
google_firebase_bom = "com.google.firebase:firebase-bom:33.4.0"
|
||||||
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
||||||
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
||||||
|
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||||
|
|
||||||
# AndroidX
|
# AndroidX
|
||||||
androidx_core = { module = "androidx.core:core", version.ref = "core" }
|
androidx_core = { module = "androidx.core:core", version.ref = "core" }
|
||||||
|
|
@ -198,8 +202,8 @@ sigpwned_emoji4j = "com.sigpwned:emoji4j-core:15.1.2"
|
||||||
inject = "javax.inject:javax.inject:1"
|
inject = "javax.inject:javax.inject:1"
|
||||||
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
|
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
|
||||||
dagger_compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
|
dagger_compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
|
||||||
anvil_compiler_api = { module = "com.squareup.anvil:compiler-api", version.ref = "anvil" }
|
anvil_compiler_api = { module = "dev.zacsweers.anvil:compiler-api", version.ref = "anvil" }
|
||||||
anvil_compiler_utils = { module = "com.squareup.anvil:compiler-utils", version.ref = "anvil" }
|
anvil_compiler_utils = { module = "dev.zacsweers.anvil:compiler-utils", version.ref = "anvil" }
|
||||||
|
|
||||||
# Auto services
|
# Auto services
|
||||||
google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" }
|
google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" }
|
||||||
|
|
@ -222,7 +226,7 @@ kotlin_jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
kotlin_serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
kotlin_serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
||||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||||
anvil = { id = "com.squareup.anvil", version.ref = "anvil" }
|
anvil = { id = "dev.zacsweers.anvil", version.ref = "anvil" }
|
||||||
detekt = "io.gitlab.arturbosch.detekt:1.23.7"
|
detekt = "io.gitlab.arturbosch.detekt:1.23.7"
|
||||||
ktlint = "org.jlleitschuh.gradle.ktlint:12.1.1"
|
ktlint = "org.jlleitschuh.gradle.ktlint:12.1.1"
|
||||||
dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
|
dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
package io.element.android.libraries.designsystem.component.async
|
package io.element.android.libraries.designsystem.component.async
|
||||||
|
|
||||||
import androidx.compose.animation.core.Transition
|
import androidx.compose.animation.core.Transition
|
||||||
import androidx.compose.animation.core.updateTransition
|
import androidx.compose.animation.core.rememberTransition
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
|
@ -237,7 +237,7 @@ class AsyncIndicatorTest {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val transition = state.currentItem.value?.let {
|
val transition = state.currentItem.value?.let {
|
||||||
// If there is an item, update its transition state to simulate an animation
|
// If there is an item, update its transition state to simulate an animation
|
||||||
updateTransition(state.currentAnimationState, label = "")
|
rememberTransition(state.currentAnimationState, label = "")
|
||||||
}
|
}
|
||||||
if (state.currentAnimationState.hasEntered() && state.currentItem.value?.durationMs != null) {
|
if (state.currentAnimationState.hasEntered() && state.currentItem.value?.durationMs != null) {
|
||||||
SideEffect {
|
SideEffect {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import extension.setupAnvil
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
*
|
*
|
||||||
|
|
@ -7,16 +9,17 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAnvil()
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.libraries.mediapickers.impl"
|
namespace = "io.element.android.libraries.mediapickers.impl"
|
||||||
|
}
|
||||||
dependencies {
|
|
||||||
implementation(projects.libraries.core)
|
dependencies {
|
||||||
implementation(projects.libraries.di)
|
implementation(projects.libraries.core)
|
||||||
implementation(libs.inject)
|
implementation(projects.libraries.di)
|
||||||
api(projects.libraries.mediapickers.api)
|
implementation(libs.inject)
|
||||||
}
|
api(projects.libraries.mediapickers.api)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import extension.setupAnvil
|
import extension.setupAnvil
|
||||||
|
import org.gradle.internal.extensions.stdlib.capitalized
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2023, 2024 New Vector Ltd.
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
|
@ -51,3 +53,15 @@ sqldelight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workaround for KSP not picking up the generated files from SqlDelight
|
||||||
|
androidComponents {
|
||||||
|
onVariants(selector().all()) { variant ->
|
||||||
|
afterEvaluate {
|
||||||
|
val variantName = variant.name.capitalized()
|
||||||
|
tasks.getByName<KotlinCompile>("ksp${variantName}Kotlin") {
|
||||||
|
setSource(tasks.getByName("generate${variantName}SessionDatabaseInterface").outputs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,5 @@ dependencies {
|
||||||
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
|
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
|
||||||
implementation(libs.autonomousapps.dependencyanalysis.plugin)
|
implementation(libs.autonomousapps.dependencyanalysis.plugin)
|
||||||
implementation(libs.anvil.gradle.plugin)
|
implementation(libs.anvil.gradle.plugin)
|
||||||
|
implementation(libs.ksp.gradle.plugin)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,40 @@ import org.gradle.plugin.use.PluginDependency
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup Anvil plugin with the given configuration.
|
* Setup Anvil plugin with the given configuration.
|
||||||
* @param generateDaggerCode whether to enable general Dagger code generation using Kapt
|
* @param generateDaggerCode whether to enable general Dagger code generation using Kapt. `false` by default.
|
||||||
* @param generateDaggerFactoriesUsingAnvil whether to generate Dagger factories using Anvil instead of Kapt
|
* @param generateDaggerFactoriesUsingAnvil whether to generate Dagger factories using Anvil instead of Kapt. `true` by default.
|
||||||
|
* @param componentMergingStrategy how to perform component merging. This is `ComponentMergingStrategy.NONE` by default, which will prevent component merging
|
||||||
|
* from running.
|
||||||
*/
|
*/
|
||||||
fun Project.setupAnvil(
|
fun Project.setupAnvil(
|
||||||
generateDaggerCode: Boolean = false,
|
generateDaggerCode: Boolean = false,
|
||||||
generateDaggerFactoriesUsingAnvil: Boolean = true,
|
generateDaggerFactoriesUsingAnvil: Boolean = true,
|
||||||
|
componentMergingStrategy: ComponentMergingStrategy = ComponentMergingStrategy.NONE,
|
||||||
) {
|
) {
|
||||||
val libs = the<LibrariesForLibs>()
|
val libs = the<LibrariesForLibs>()
|
||||||
// Apply plugins and dependencies
|
|
||||||
|
// Add dagger dependency, needed for generated code
|
||||||
|
dependencies.implementation(libs.dagger)
|
||||||
|
|
||||||
|
// Apply Anvil plugin and configure it
|
||||||
applyPluginIfNeeded(libs.plugins.anvil)
|
applyPluginIfNeeded(libs.plugins.anvil)
|
||||||
|
|
||||||
|
project.pluginManager.withPlugin(libs.plugins.anvil.get().pluginId) {
|
||||||
|
// Setup extension
|
||||||
|
extensions.configure(AnvilExtension::class.java) {
|
||||||
|
this.generateDaggerFactories.set(generateDaggerFactoriesUsingAnvil)
|
||||||
|
this.disableComponentMerging.set(componentMergingStrategy == ComponentMergingStrategy.NONE)
|
||||||
|
|
||||||
|
useKsp(
|
||||||
|
contributesAndFactoryGeneration = true,
|
||||||
|
componentMerging = componentMergingStrategy == ComponentMergingStrategy.KSP,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (generateDaggerCode) {
|
if (generateDaggerCode) {
|
||||||
applyPluginIfNeeded(libs.plugins.kapt)
|
// Needed at the top level since dagger code should be generated at a single point for performance reasons
|
||||||
// Needed at the top level since dagger code should be generated at a single point for performance
|
dependencies.add("ksp", libs.dagger.compiler)
|
||||||
dependencies.implementation(libs.dagger)
|
|
||||||
dependencies.add("kapt", libs.dagger.compiler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// These dependencies are only needed for compose library or application modules
|
// These dependencies are only needed for compose library or application modules
|
||||||
|
|
@ -40,14 +58,7 @@ fun Project.setupAnvil(
|
||||||
// Annotations to generate DI code for Appyx nodes
|
// Annotations to generate DI code for Appyx nodes
|
||||||
dependencies.implementation(project.project(":anvilannotations"))
|
dependencies.implementation(project.project(":anvilannotations"))
|
||||||
// Code generator for the annotations above
|
// Code generator for the annotations above
|
||||||
dependencies.add("anvil", project.project(":anvilcodegen"))
|
dependencies.add("ksp", project.project(":anvilcodegen"))
|
||||||
}
|
|
||||||
|
|
||||||
project.pluginManager.withPlugin(libs.plugins.anvil.get().pluginId) {
|
|
||||||
// Setup extension
|
|
||||||
extensions.configure(AnvilExtension::class.java) {
|
|
||||||
this.generateDaggerFactories.set(generateDaggerFactoriesUsingAnvil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,3 +68,9 @@ private fun Project.applyPluginIfNeeded(plugin: Provider<PluginDependency>) {
|
||||||
pluginManager.apply(pluginId)
|
pluginManager.apply(pluginId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class ComponentMergingStrategy {
|
||||||
|
NONE,
|
||||||
|
KAPT,
|
||||||
|
KSP
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,16 @@
|
||||||
|
|
||||||
package extension
|
package extension
|
||||||
|
|
||||||
import config.AnalyticsConfig
|
|
||||||
import ModulesConfig
|
import ModulesConfig
|
||||||
|
import config.AnalyticsConfig
|
||||||
import org.gradle.accessors.dm.LibrariesForLibs
|
import org.gradle.accessors.dm.LibrariesForLibs
|
||||||
import org.gradle.api.Action
|
import org.gradle.api.Action
|
||||||
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.artifacts.ExternalModuleDependency
|
import org.gradle.api.artifacts.ExternalModuleDependency
|
||||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||||
import org.gradle.api.logging.Logger
|
|
||||||
import org.gradle.kotlin.dsl.DependencyHandlerScope
|
import org.gradle.kotlin.dsl.DependencyHandlerScope
|
||||||
import org.gradle.kotlin.dsl.closureOf
|
import org.gradle.kotlin.dsl.closureOf
|
||||||
import org.gradle.kotlin.dsl.project
|
import org.gradle.kotlin.dsl.project
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
private fun DependencyHandlerScope.implementation(dependency: Any) = dependencies.add("implementation", dependency)
|
private fun DependencyHandlerScope.implementation(dependency: Any) = dependencies.add("implementation", dependency)
|
||||||
internal fun DependencyHandler.implementation(dependency: Any) = add("implementation", dependency)
|
internal fun DependencyHandler.implementation(dependency: Any) = add("implementation", dependency)
|
||||||
|
|
@ -26,7 +25,7 @@ internal fun DependencyHandler.implementation(dependency: Any) = add("implementa
|
||||||
private fun DependencyHandlerScope.implementation(
|
private fun DependencyHandlerScope.implementation(
|
||||||
dependency: Any,
|
dependency: Any,
|
||||||
config: Action<ExternalModuleDependency>
|
config: Action<ExternalModuleDependency>
|
||||||
) = dependencies.add("implementation", dependency, closureOf<ExternalModuleDependency> { config.execute(this) })
|
) = dependencies.add("implementation", dependency, closureOf<ExternalModuleDependency> { config.execute(this) })
|
||||||
|
|
||||||
private fun DependencyHandlerScope.androidTestImplementation(dependency: Any) = dependencies.add("androidTestImplementation", dependency)
|
private fun DependencyHandlerScope.androidTestImplementation(dependency: Any) = dependencies.add("androidTestImplementation", dependency)
|
||||||
|
|
||||||
|
|
@ -58,26 +57,6 @@ fun DependencyHandlerScope.composeDependencies(libs: LibrariesForLibs) {
|
||||||
implementation(libs.kotlinx.collections.immutable)
|
implementation(libs.kotlinx.collections.immutable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DependencyHandlerScope.addImplementationProjects(
|
|
||||||
directory: File,
|
|
||||||
path: String,
|
|
||||||
nameFilter: String,
|
|
||||||
logger: Logger,
|
|
||||||
) {
|
|
||||||
directory.listFiles().orEmpty().also { it.sort() }.forEach { file ->
|
|
||||||
if (file.isDirectory) {
|
|
||||||
val newPath = "$path:${file.name}"
|
|
||||||
val buildFile = File(file, "build.gradle.kts")
|
|
||||||
if (buildFile.exists() && file.name == nameFilter) {
|
|
||||||
implementation(project(newPath))
|
|
||||||
logger.lifecycle("Added implementation(project($newPath))")
|
|
||||||
} else {
|
|
||||||
addImplementationProjects(file, newPath, nameFilter, logger)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DependencyHandlerScope.allLibrariesImpl() {
|
fun DependencyHandlerScope.allLibrariesImpl() {
|
||||||
implementation(project(":libraries:androidutils"))
|
implementation(project(":libraries:androidutils"))
|
||||||
implementation(project(":libraries:deeplink"))
|
implementation(project(":libraries:deeplink"))
|
||||||
|
|
@ -128,22 +107,21 @@ fun DependencyHandlerScope.allServicesImpl() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation(project(":services:apperror:impl"))
|
implementation(project(":services:apperror:impl"))
|
||||||
implementation(project(":services:appnavstate:impl"))
|
implementation(project(":services:appnavstate:impl"))
|
||||||
implementation(project(":services:toolbox:impl"))
|
implementation(project(":services:toolbox:impl"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DependencyHandlerScope.allEnterpriseImpl(rootDir: File, logger: Logger) {
|
fun DependencyHandlerScope.allEnterpriseImpl(project: Project) = addAll(project, "enterprise", "impl")
|
||||||
val enterpriseDir = File(rootDir, "enterprise")
|
|
||||||
addImplementationProjects(enterpriseDir, ":enterprise", "impl", logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DependencyHandlerScope.allFeaturesApi(rootDir: File, logger: Logger) {
|
fun DependencyHandlerScope.allFeaturesImpl(project: Project) = addAll(project, "features", "impl")
|
||||||
val featuresDir = File(rootDir, "features")
|
|
||||||
addImplementationProjects(featuresDir, ":features", "api", logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DependencyHandlerScope.allFeaturesImpl(rootDir: File, logger: Logger) {
|
fun DependencyHandlerScope.allFeaturesApi(project: Project) = addAll(project, "features", "api")
|
||||||
val featuresDir = File(rootDir, "features")
|
|
||||||
addImplementationProjects(featuresDir, ":features", "impl", logger)
|
private fun DependencyHandlerScope.addAll(project: Project, prefix: String, suffix: String) {
|
||||||
|
val subProjects = project.rootProject.subprojects.filter { it.path.startsWith(":$prefix") && it.path.endsWith(":$suffix") }
|
||||||
|
for (p in subProjects) {
|
||||||
|
add("implementation", p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension
|
||||||
import kotlinx.kover.gradle.plugin.dsl.KoverVariantCreateConfig
|
import kotlinx.kover.gradle.plugin.dsl.KoverVariantCreateConfig
|
||||||
import org.gradle.api.Action
|
import org.gradle.api.Action
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.configurationcache.extensions.capitalized
|
|
||||||
import org.gradle.kotlin.dsl.apply
|
import org.gradle.kotlin.dsl.apply
|
||||||
import org.gradle.kotlin.dsl.assign
|
import org.gradle.kotlin.dsl.assign
|
||||||
|
|
||||||
|
|
@ -142,24 +141,18 @@ fun Project.setupKover() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filters {
|
filters {
|
||||||
includes {
|
excludes.classes(
|
||||||
classes(
|
"*Fake*Presenter*",
|
||||||
"*Presenter",
|
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
|
||||||
)
|
// Some options can't be tested at the moment
|
||||||
}
|
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
|
||||||
excludes {
|
// Need an Activity to use rememberMultiplePermissionsState
|
||||||
classes(
|
"io.element.android.features.location.impl.common.permissions.DefaultPermissionsPresenter",
|
||||||
"*Fake*Presenter*",
|
"*Presenter\$present\$*",
|
||||||
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
|
// Too small to be > 85% tested
|
||||||
// Some options can't be tested at the moment
|
"io.element.android.libraries.fullscreenintent.impl.DefaultFullScreenIntentPermissionsPresenter",
|
||||||
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
|
)
|
||||||
// Need an Activity to use rememberMultiplePermissionsState
|
includes.inheritedFrom("io.element.android.libraries.architecture.Presenter")
|
||||||
"io.element.android.features.location.impl.common.permissions.DefaultPermissionsPresenter",
|
|
||||||
"*Presenter\$present\$*",
|
|
||||||
// Too small to be > 85% tested
|
|
||||||
"io.element.android.libraries.fullscreenintent.impl.DefaultFullScreenIntentPermissionsPresenter",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
variant(KoverVariant.States.variantName) {
|
variant(KoverVariant.States.variantName) {
|
||||||
|
|
@ -175,33 +168,31 @@ fun Project.setupKover() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filters {
|
filters {
|
||||||
includes {
|
excludes.classes(
|
||||||
classes(
|
"*State$*", // Exclude inner classes
|
||||||
"^*State$",
|
"io.element.android.appnav.root.RootNavState*",
|
||||||
)
|
"io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*",
|
||||||
}
|
"io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*",
|
||||||
excludes {
|
"io.element.android.libraries.matrix.api.room.RoomMembershipState*",
|
||||||
classes(
|
"io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*",
|
||||||
"io.element.android.appnav.root.RootNavState*",
|
"io.element.android.libraries.push.impl.notifications.NotificationState*",
|
||||||
"io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*",
|
"io.element.android.features.messages.impl.media.local.pdf.PdfViewerState",
|
||||||
"io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*",
|
"io.element.android.features.messages.impl.media.local.LocalMediaViewState",
|
||||||
"io.element.android.libraries.matrix.api.room.RoomMembershipState*",
|
"io.element.android.features.location.impl.map.MapState*",
|
||||||
"io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*",
|
"io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*",
|
||||||
"io.element.android.libraries.push.impl.notifications.NotificationState*",
|
"io.element.android.libraries.designsystem.swipe.SwipeableActionsState*",
|
||||||
"io.element.android.features.messages.impl.media.local.pdf.PdfViewerState",
|
"io.element.android.features.messages.impl.timeline.components.ExpandableState*",
|
||||||
"io.element.android.features.messages.impl.media.local.LocalMediaViewState",
|
"io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*",
|
||||||
"io.element.android.features.location.impl.map.MapState*",
|
"io.element.android.libraries.maplibre.compose.CameraPositionState*",
|
||||||
"io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*",
|
"io.element.android.libraries.maplibre.compose.SaveableCameraPositionState",
|
||||||
"io.element.android.libraries.designsystem.swipe.SwipeableActionsState*",
|
"io.element.android.libraries.maplibre.compose.SymbolState*",
|
||||||
"io.element.android.features.messages.impl.timeline.components.ExpandableState*",
|
"io.element.android.features.ftue.api.state.*",
|
||||||
"io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*",
|
"io.element.android.features.ftue.impl.welcome.state.*",
|
||||||
"io.element.android.libraries.maplibre.compose.CameraPositionState*",
|
"io.element.android.libraries.designsystem.theme.components.bottomsheet.CustomSheetState",
|
||||||
"io.element.android.libraries.maplibre.compose.SaveableCameraPositionState",
|
"io.element.android.libraries.mediaviewer.api.local.pdf.PdfViewerState",
|
||||||
"io.element.android.libraries.maplibre.compose.SymbolState*",
|
"io.element.android.libraries.textcomposer.model.TextEditorState",
|
||||||
"io.element.android.features.ftue.api.state.*",
|
)
|
||||||
"io.element.android.features.ftue.impl.welcome.state.*",
|
includes.classes("*State")
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
variant(KoverVariant.Views.variantName) {
|
variant(KoverVariant.Views.variantName) {
|
||||||
|
|
@ -218,11 +209,8 @@ fun Project.setupKover() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filters {
|
filters {
|
||||||
includes {
|
excludes.classes("*ViewKt$*") // Exclude inner classes
|
||||||
classes(
|
includes.classes("*ViewKt")
|
||||||
"*ViewKt",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -236,16 +224,31 @@ fun Project.applyKoverPluginToAllSubProjects() = rootProject.subprojects {
|
||||||
currentProject {
|
currentProject {
|
||||||
for (variant in koverVariants) {
|
for (variant in koverVariants) {
|
||||||
createVariant(variant) {
|
createVariant(variant) {
|
||||||
defaultVariants()
|
defaultVariants(project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project.afterEvaluate {
|
||||||
|
for (variant in koverVariants) {
|
||||||
|
// Using the cache for coverage verification seems to be flaky, so we disable it for now.
|
||||||
|
val taskName = "koverCachedVerify${variant.replaceFirstChar(Char::titlecase)}"
|
||||||
|
val cachedTask = project.tasks.findByName(taskName)
|
||||||
|
cachedTask?.let {
|
||||||
|
it.outputs.upToDateWhen { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KoverVariantCreateConfig.defaultVariants() {
|
fun KoverVariantCreateConfig.defaultVariants(project: Project) {
|
||||||
addWithDependencies("gplayDebug", "debug", optional = true)
|
if (project.name == "app") {
|
||||||
|
addWithDependencies("gplayDebug")
|
||||||
|
} else {
|
||||||
|
addWithDependencies("debug", "jvm", optional = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Project.koverSubprojects() = project.rootProject.subprojects
|
fun Project.koverSubprojects() = project.rootProject.subprojects
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
@ContributesBinding(AppScope::class, boundType = AnalyticsService::class, priority = ContributesBinding.Priority.HIGHEST)
|
@ContributesBinding(AppScope::class, boundType = AnalyticsService::class, rank = ContributesBinding.RANK_HIGHEST)
|
||||||
class DefaultAnalyticsService @Inject constructor(
|
class DefaultAnalyticsService @Inject constructor(
|
||||||
private val analyticsProviders: Set<@JvmSuppressWildcards AnalyticsProvider>,
|
private val analyticsProviders: Set<@JvmSuppressWildcards AnalyticsProvider>,
|
||||||
private val analyticsStore: AnalyticsStore,
|
private val analyticsStore: AnalyticsStore,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ dependencies {
|
||||||
// - Add every single module as a dependency of this one.
|
// - Add every single module as a dependency of this one.
|
||||||
// - Move the Konsist tests to the `app` module, but the `app` module does not need to know about Konsist.
|
// - Move the Konsist tests to the `app` module, but the `app` module does not need to know about Konsist.
|
||||||
tasks.withType<Test>().configureEach {
|
tasks.withType<Test>().configureEach {
|
||||||
outputs.upToDateWhen {
|
val isNotCheckTask = gradle.startParameter.taskNames.any { it.contains("check", ignoreCase = true).not() }
|
||||||
gradle.startParameter.taskNames.any { it.contains("check", ignoreCase = true).not() }
|
outputs.upToDateWhen { isNotCheckTask }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.tests.testutils
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
object RobolectricDispatcherCleaner {
|
||||||
|
// HACK: Workaround for https://github.com/robolectric/robolectric/issues/7055#issuecomment-1551119229
|
||||||
|
fun clearAndroidUiDispatcher(pkg: String = "androidx.compose.ui.platform") {
|
||||||
|
val clazz = javaClass.classLoader!!.loadClass("$pkg.AndroidUiDispatcher")
|
||||||
|
val combinedContextClass = javaClass.classLoader!!.loadClass("kotlin.coroutines.CombinedContext")
|
||||||
|
val companionClazz = clazz.getDeclaredField("Companion").get(clazz)
|
||||||
|
val combinedContext = companionClazz.javaClass.getDeclaredMethod("getMain")
|
||||||
|
.invoke(companionClazz) as CoroutineContext
|
||||||
|
val androidUiDispatcher = combinedContextClass.getDeclaredField("element")
|
||||||
|
.apply { isAccessible = true }
|
||||||
|
.get(combinedContext)
|
||||||
|
.let { clazz.cast(it) }
|
||||||
|
var scheduledFrameDispatch = clazz.getDeclaredField("scheduledFrameDispatch")
|
||||||
|
.apply { isAccessible = true }
|
||||||
|
.getBoolean(androidUiDispatcher)
|
||||||
|
var scheduledTrampolineDispatch = clazz.getDeclaredField("scheduledTrampolineDispatch")
|
||||||
|
.apply { isAccessible = true }
|
||||||
|
.getBoolean(androidUiDispatcher)
|
||||||
|
val dispatchCallback = clazz.getDeclaredField("dispatchCallback")
|
||||||
|
.apply { isAccessible = true }
|
||||||
|
.get(androidUiDispatcher) as Runnable
|
||||||
|
if (scheduledFrameDispatch || scheduledTrampolineDispatch) {
|
||||||
|
dispatchCallback.run()
|
||||||
|
scheduledFrameDispatch = clazz.getDeclaredField("scheduledFrameDispatch")
|
||||||
|
.apply { isAccessible = true }
|
||||||
|
.getBoolean(androidUiDispatcher)
|
||||||
|
scheduledTrampolineDispatch = clazz.getDeclaredField("scheduledTrampolineDispatch")
|
||||||
|
.apply { isAccessible = true }
|
||||||
|
.getBoolean(androidUiDispatcher)
|
||||||
|
}
|
||||||
|
assertFalse(scheduledFrameDispatch)
|
||||||
|
assertFalse(scheduledTrampolineDispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <R : TestRule, A : ComponentActivity> AndroidComposeTestRule<R, A>.setSafeContent(content: @Composable () -> Unit) {
|
||||||
|
RobolectricDispatcherCleaner.clearAndroidUiDispatcher()
|
||||||
|
setContent(content)
|
||||||
|
}
|
||||||
|
|
@ -52,7 +52,7 @@ dependencies {
|
||||||
implementation(projects.appnav)
|
implementation(projects.appnav)
|
||||||
allLibrariesImpl()
|
allLibrariesImpl()
|
||||||
allServicesImpl()
|
allServicesImpl()
|
||||||
allFeaturesImpl(rootDir, logger)
|
allFeaturesImpl(project)
|
||||||
implementation(projects.appicon.element)
|
implementation(projects.appicon.element)
|
||||||
implementation(projects.appicon.enterprise)
|
implementation(projects.appicon.enterprise)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0a12b9a2dfafb1f8cdb329a41c422ebe913b6a8da2f48a217955f43b9f46ac4d
|
||||||
|
size 23505
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:053b62a43c2ebec26c9d32f9cd002e01f90665be7530d281aad302377585dfae
|
||||||
|
size 21420
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:306561dca24390baca77657839f96b63f93114e5bce523764903cf9030185320
|
||||||
|
size 30358
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:c0f023eda3a5fd9ff9584e93a6685010de35fb9464025b8c82634be801ada533
|
||||||
|
size 27476
|
||||||
|
|
@ -122,7 +122,7 @@ const previewAnnotations = [
|
||||||
|
|
||||||
const filesWithPreviews = editedFiles.filter(file => file.endsWith(".kt")).filter(file => {
|
const filesWithPreviews = editedFiles.filter(file => file.endsWith(".kt")).filter(file => {
|
||||||
const content = fs.readFileSync(file);
|
const content = fs.readFileSync(file);
|
||||||
return previewAnnotations.some((ann) => content.includes(ann));
|
return previewAnnotations.some((ann) => content.includes("import " + ann));
|
||||||
})
|
})
|
||||||
|
|
||||||
const composablePreviewProviderContents = fs.readFileSync('tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt');
|
const composablePreviewProviderContents = fs.readFileSync('tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt');
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue