Merge branch 'develop' into renovate/io.nlopez.compose.rules-detekt-0.x
This commit is contained in:
6
.github/renovate.json
vendored
6
.github/renovate.json
vendored
@@ -18,12 +18,6 @@
|
||||
],
|
||||
"groupName" : "kotlin"
|
||||
},
|
||||
{
|
||||
"matchPackageNames" : [
|
||||
"org.jetbrains.kotlinx.kover"
|
||||
],
|
||||
"enabled" : false
|
||||
},
|
||||
{
|
||||
"matchPackagePatterns" : [
|
||||
"^org.maplibre"
|
||||
|
||||
4
.github/workflows/nightlyReports.yml
vendored
4
.github/workflows/nightlyReports.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
- name: 📈 Generate kover report and verify coverage
|
||||
run: ./gradlew koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true
|
||||
run: ./gradlew :app:koverHtmlReport :app:koverXmlReport :app:koverVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true
|
||||
|
||||
- name: ✅ Upload kover report
|
||||
if: always()
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
with:
|
||||
name: kover-results
|
||||
path: |
|
||||
**/build/reports/kover/merged
|
||||
**/build/reports/kover
|
||||
|
||||
- name: 🔊 Publish results to Sonar
|
||||
env:
|
||||
|
||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
- name: 📈Generate kover report and verify coverage
|
||||
run: ./gradlew koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true
|
||||
run: ./gradlew :app:koverHtmlReport :app:koverXmlReport :app:koverVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true
|
||||
|
||||
- name: 🚫 Upload kover failed coverage reports
|
||||
if: failure()
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
with:
|
||||
name: kover-error-report
|
||||
path: |
|
||||
**/kover/merged/verification/errors.txt
|
||||
app/build/reports/kover/verify.err
|
||||
|
||||
- name: ✅ Upload kover report (disabled)
|
||||
if: always()
|
||||
@@ -83,4 +83,4 @@ jobs:
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v3
|
||||
# with:
|
||||
# files: build/reports/kover/merged/xml/report.xml
|
||||
# files: build/reports/kover/xml/report.xml
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
|
||||
import extension.allFeaturesImpl
|
||||
import extension.allLibrariesImpl
|
||||
import extension.allServicesImpl
|
||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-application")
|
||||
@@ -190,6 +191,220 @@ knit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kover configuration
|
||||
*/
|
||||
|
||||
dependencies {
|
||||
// Add all sub projects to kover except some of them
|
||||
project.rootProject.subprojects
|
||||
.filter {
|
||||
it.project.projectDir.resolve("build.gradle.kts").exists()
|
||||
}
|
||||
.map { it.path }
|
||||
.sorted()
|
||||
.filter {
|
||||
it !in listOf(
|
||||
":app",
|
||||
":samples",
|
||||
":anvilannotations",
|
||||
":anvilcodegen",
|
||||
":samples:minimal",
|
||||
":tests:testutils",
|
||||
// Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix
|
||||
// SDK api, so it is not really relevant to unit test it: there is no logic to test.
|
||||
":libraries:matrix:impl",
|
||||
// Exclude modules which are not Android libraries
|
||||
// See https://github.com/Kotlin/kotlinx-kover/issues/312
|
||||
":appconfig",
|
||||
":libraries:core",
|
||||
":libraries:coroutines",
|
||||
":libraries:di",
|
||||
":libraries:rustsdk",
|
||||
":libraries:textcomposer:lib",
|
||||
)
|
||||
}
|
||||
.forEach {
|
||||
// println("Add $it to kover")
|
||||
kover(project(it))
|
||||
}
|
||||
}
|
||||
|
||||
val ciBuildProperty = "ci-build"
|
||||
val isCiBuild = if (project.hasProperty(ciBuildProperty)) {
|
||||
val raw = project.property(ciBuildProperty) as? String
|
||||
raw?.toBooleanLenient() == true || raw?.toIntOrNull() == 1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
kover {
|
||||
// When running on the CI, run only debug test variants
|
||||
if (isCiBuild) {
|
||||
excludeTests {
|
||||
// Disable instrumentation for debug test tasks
|
||||
tasks(
|
||||
"testDebugUnitTest",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://kotlin.github.io/kotlinx-kover/
|
||||
// Run `./gradlew :app:koverHtmlReport` to get report at ./app/build/reports/kover
|
||||
// Run `./gradlew :app:koverXmlReport` to get XML report
|
||||
koverReport {
|
||||
filters {
|
||||
excludes {
|
||||
classes(
|
||||
// Exclude generated classes.
|
||||
"*_ModuleKt",
|
||||
"anvil.hint.binding.io.element.*",
|
||||
"anvil.hint.merge.*",
|
||||
"anvil.hint.multibinding.io.element.*",
|
||||
"anvil.module.*",
|
||||
"com.airbnb.android.showkase*",
|
||||
"io.element.android.libraries.designsystem.showkase.*",
|
||||
"io.element.android.x.di.DaggerAppComponent*",
|
||||
"*_Factory",
|
||||
"*_Factory_Impl",
|
||||
"*_Factory$*",
|
||||
"*_Module",
|
||||
"*_Module$*",
|
||||
"*Module_Provides*",
|
||||
"Dagger*Component*",
|
||||
"*ComposableSingletons$*",
|
||||
"*_AssistedFactory_Impl*",
|
||||
"*BuildConfig",
|
||||
// Generated by Showkase
|
||||
"*Ioelementandroid*PreviewKt$*",
|
||||
"*Ioelementandroid*PreviewKt",
|
||||
// Other
|
||||
// We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro)
|
||||
"*Node",
|
||||
"*Node$*",
|
||||
"*Presenter\$present\$*",
|
||||
// Forked from compose
|
||||
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
|
||||
)
|
||||
annotatedBy(
|
||||
"io.element.android.libraries.designsystem.preview.PreviewsDayNight",
|
||||
"io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
defaults {
|
||||
// add reports of both 'debug' and 'release' Android build variants to default reports
|
||||
mergeWith("debug")
|
||||
mergeWith("release")
|
||||
|
||||
verify {
|
||||
onCheck = true
|
||||
// General rule: minimum code coverage.
|
||||
rule("Global minimum code coverage.") {
|
||||
isEnabled = true
|
||||
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.APPLICATION
|
||||
bound {
|
||||
minValue = 65
|
||||
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
|
||||
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
|
||||
// minValue to 25 and maxValue to 35.
|
||||
maxValue = 75
|
||||
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
|
||||
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
// Rule to ensure that coverage of Presenters is sufficient.
|
||||
rule("Check code coverage of presenters") {
|
||||
isEnabled = true
|
||||
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
|
||||
filters {
|
||||
includes {
|
||||
classes(
|
||||
"*Presenter",
|
||||
)
|
||||
}
|
||||
excludes {
|
||||
classes(
|
||||
"*Fake*Presenter",
|
||||
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
|
||||
// Some options can't be tested at the moment
|
||||
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
|
||||
"*Presenter\$present\$*",
|
||||
)
|
||||
}
|
||||
}
|
||||
bound {
|
||||
minValue = 85
|
||||
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
|
||||
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
// Rule to ensure that coverage of States is sufficient.
|
||||
rule("Check code coverage of states") {
|
||||
isEnabled = true
|
||||
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
|
||||
filters {
|
||||
includes {
|
||||
classes(
|
||||
"^*State$",
|
||||
)
|
||||
}
|
||||
excludes {
|
||||
classes(
|
||||
"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$*",
|
||||
"io.element.android.libraries.matrix.api.room.RoomMembershipState*",
|
||||
"io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*",
|
||||
"io.element.android.libraries.push.impl.notifications.NotificationState*",
|
||||
"io.element.android.features.messages.impl.media.local.pdf.PdfViewerState",
|
||||
"io.element.android.features.messages.impl.media.local.LocalMediaViewState",
|
||||
"io.element.android.features.location.impl.map.MapState*",
|
||||
"io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*",
|
||||
"io.element.android.libraries.designsystem.swipe.SwipeableActionsState*",
|
||||
"io.element.android.features.messages.impl.timeline.components.ExpandableState*",
|
||||
"io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*",
|
||||
"io.element.android.libraries.maplibre.compose.CameraPositionState*",
|
||||
"io.element.android.libraries.maplibre.compose.SaveableCameraPositionState",
|
||||
"io.element.android.libraries.maplibre.compose.SymbolState*",
|
||||
"io.element.android.features.ftue.api.state.*",
|
||||
"io.element.android.features.ftue.impl.welcome.state.*",
|
||||
)
|
||||
}
|
||||
}
|
||||
bound {
|
||||
minValue = 90
|
||||
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
|
||||
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
// Rule to ensure that coverage of Views is sufficient (deactivated for now).
|
||||
rule("Check code coverage of views") {
|
||||
isEnabled = true
|
||||
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
|
||||
filters {
|
||||
includes {
|
||||
classes(
|
||||
"*ViewKt",
|
||||
)
|
||||
}
|
||||
}
|
||||
bound {
|
||||
// TODO Update this value, for now there are too many missing tests.
|
||||
minValue = 0
|
||||
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
|
||||
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
androidReports("release") {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
allLibrariesImpl()
|
||||
allServicesImpl()
|
||||
|
||||
176
build.gradle.kts
176
build.gradle.kts
@@ -1,7 +1,5 @@
|
||||
import com.google.devtools.ksp.gradle.KspTask
|
||||
import kotlinx.kover.api.KoverTaskExtension
|
||||
import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp
|
||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
@@ -41,7 +39,7 @@ plugins {
|
||||
alias(libs.plugins.ktlint)
|
||||
alias(libs.plugins.dependencygraph)
|
||||
alias(libs.plugins.sonarqube)
|
||||
alias(libs.plugins.kover)
|
||||
alias(libs.plugins.kover) apply false
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean").configure {
|
||||
@@ -164,177 +162,7 @@ allprojects {
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply(plugin = "kover")
|
||||
}
|
||||
|
||||
// https://kotlin.github.io/kotlinx-kover/
|
||||
// Run `./gradlew koverMergedHtmlReport` to get report at ./build/reports/kover
|
||||
// Run `./gradlew koverMergedReport` to also get XML report
|
||||
koverMerged {
|
||||
enable()
|
||||
|
||||
filters {
|
||||
classes {
|
||||
excludes.addAll(
|
||||
listOf(
|
||||
// Exclude generated classes.
|
||||
"*_ModuleKt",
|
||||
"anvil.hint.binding.io.element.*",
|
||||
"anvil.hint.merge.*",
|
||||
"anvil.hint.multibinding.io.element.*",
|
||||
"anvil.module.*",
|
||||
"com.airbnb.android.showkase*",
|
||||
"io.element.android.libraries.designsystem.showkase.*",
|
||||
"io.element.android.x.di.DaggerAppComponent*",
|
||||
"*_Factory",
|
||||
"*_Factory_Impl",
|
||||
"*_Factory$*",
|
||||
"*_Module",
|
||||
"*_Module$*",
|
||||
"*Module_Provides*",
|
||||
"Dagger*Component*",
|
||||
"*ComposableSingletons$*",
|
||||
"*_AssistedFactory_Impl*",
|
||||
"*BuildConfig",
|
||||
// Generated by Showkase
|
||||
"*Ioelementandroid*PreviewKt$*",
|
||||
"*Ioelementandroid*PreviewKt",
|
||||
// Other
|
||||
// We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro)
|
||||
"*Node",
|
||||
"*Node$*",
|
||||
// Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix SDK api, so it is not really relevant to unit test it: there is no logic to test.
|
||||
"io.element.android.libraries.matrix.impl.*",
|
||||
"*Presenter\$present\$*",
|
||||
// Forked from compose
|
||||
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
annotations {
|
||||
excludes.addAll(
|
||||
listOf(
|
||||
"*Preview",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
projects {
|
||||
excludes.addAll(
|
||||
listOf(
|
||||
":anvilannotations",
|
||||
":anvilcodegen",
|
||||
":samples:minimal",
|
||||
":tests:testutils",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Run ./gradlew koverMergedVerify to check the rules.
|
||||
verify {
|
||||
// Does not seems to work, so also run the task manually on the workflow.
|
||||
onCheck.set(true)
|
||||
// General rule: minimum code coverage.
|
||||
rule {
|
||||
name = "Global minimum code coverage."
|
||||
target = kotlinx.kover.api.VerificationTarget.ALL
|
||||
bound {
|
||||
minValue = 65
|
||||
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
|
||||
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
|
||||
// minValue to 25 and maxValue to 35.
|
||||
maxValue = 75
|
||||
counter = kotlinx.kover.api.CounterType.INSTRUCTION
|
||||
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
// Rule to ensure that coverage of Presenters is sufficient.
|
||||
rule {
|
||||
name = "Check code coverage of presenters"
|
||||
target = kotlinx.kover.api.VerificationTarget.CLASS
|
||||
overrideClassFilter {
|
||||
includes += "*Presenter"
|
||||
excludes += "*Fake*Presenter"
|
||||
excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*"
|
||||
// Some options can't be tested at the moment
|
||||
excludes += "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*"
|
||||
excludes += "*Presenter\$present\$*"
|
||||
}
|
||||
bound {
|
||||
minValue = 85
|
||||
counter = kotlinx.kover.api.CounterType.INSTRUCTION
|
||||
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
// Rule to ensure that coverage of States is sufficient.
|
||||
rule {
|
||||
name = "Check code coverage of states"
|
||||
target = kotlinx.kover.api.VerificationTarget.CLASS
|
||||
overrideClassFilter {
|
||||
includes += "^*State$"
|
||||
excludes += "io.element.android.appnav.root.RootNavState*"
|
||||
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*"
|
||||
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*"
|
||||
excludes += "io.element.android.libraries.matrix.api.room.RoomMembershipState*"
|
||||
excludes += "io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*"
|
||||
excludes += "io.element.android.libraries.push.impl.notifications.NotificationState*"
|
||||
excludes += "io.element.android.features.messages.impl.media.local.pdf.PdfViewerState"
|
||||
excludes += "io.element.android.features.messages.impl.media.local.LocalMediaViewState"
|
||||
excludes += "io.element.android.features.location.impl.map.MapState*"
|
||||
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*"
|
||||
excludes += "io.element.android.libraries.designsystem.swipe.SwipeableActionsState*"
|
||||
excludes += "io.element.android.features.messages.impl.timeline.components.ExpandableState*"
|
||||
excludes += "io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*"
|
||||
excludes += "io.element.android.libraries.maplibre.compose.CameraPositionState*"
|
||||
excludes += "io.element.android.libraries.maplibre.compose.SaveableCameraPositionState"
|
||||
excludes += "io.element.android.libraries.maplibre.compose.SymbolState*"
|
||||
excludes += "io.element.android.features.ftue.api.state.*"
|
||||
excludes += "io.element.android.features.ftue.impl.welcome.state.*"
|
||||
}
|
||||
bound {
|
||||
minValue = 90
|
||||
counter = kotlinx.kover.api.CounterType.INSTRUCTION
|
||||
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
// Rule to ensure that coverage of Views is sufficient (deactivated for now).
|
||||
rule {
|
||||
name = "Check code coverage of views"
|
||||
target = kotlinx.kover.api.VerificationTarget.CLASS
|
||||
overrideClassFilter {
|
||||
includes += "*ViewKt"
|
||||
}
|
||||
bound {
|
||||
// TODO Update this value, for now there are too many missing tests.
|
||||
minValue = 0
|
||||
counter = kotlinx.kover.api.CounterType.INSTRUCTION
|
||||
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When running on the CI, run only debug test variants
|
||||
val ciBuildProperty = "ci-build"
|
||||
val isCiBuild = if (project.hasProperty(ciBuildProperty)) {
|
||||
val raw = project.property(ciBuildProperty) as? String
|
||||
raw?.toBooleanLenient() == true || raw?.toIntOrNull() == 1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if (isCiBuild) {
|
||||
allprojects {
|
||||
afterEvaluate {
|
||||
tasks.withType<Test>().configureEach {
|
||||
extensions.configure<KoverTaskExtension> {
|
||||
val enabled = name.contains("debug", ignoreCase = true)
|
||||
isDisabled.set(!enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apply(plugin = "org.jetbrains.kotlinx.kover")
|
||||
}
|
||||
|
||||
// Register quality check tasks.
|
||||
|
||||
1
changelog.d/1782.misc
Normal file
1
changelog.d/1782.misc
Normal file
@@ -0,0 +1 @@
|
||||
Migrate to Kover 0.7.X
|
||||
1
changelog.d/2156.bugfix
Normal file
1
changelog.d/2156.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Improve rendering of voice messages in the timeline in large displays
|
||||
@@ -344,26 +344,26 @@ implementation of our interfaces. Mocking can be used to mock Android framework
|
||||
[kover](https://github.com/Kotlin/kotlinx-kover) is used to compute code coverage. Only have unit tests can produce code coverage result. Running Maestro does
|
||||
not participate to the code coverage results.
|
||||
|
||||
Kover configuration is defined in the main [build.gradle.kts](../build.gradle.kts) file.
|
||||
Kover configuration is defined in the app [build.gradle.kts](../app/build.gradle.kts) file.
|
||||
|
||||
To compute the code coverage, run:
|
||||
|
||||
```bash
|
||||
./gradlew koverMergedReport
|
||||
./gradlew :app:koverHtmlReport
|
||||
```
|
||||
|
||||
and open the Html report: [../build/reports/kover/merged/html/index.html](../build/reports/kover/merged/html/index.html)
|
||||
and open the Html report: [../app/build/reports/kover/html/index.html](../app/build/reports/kover/html/index.html)
|
||||
|
||||
To ensure that the code coverage threshold are OK, you can run
|
||||
|
||||
```bash
|
||||
./gradlew koverMergedVerify
|
||||
./gradlew :app:koverVerify
|
||||
```
|
||||
|
||||
Note that the CI performs this check on every pull requests.
|
||||
|
||||
Also, if the rule `Global minimum code coverage.` is in error because code coverage is `> maxValue`, `minValue` and `maxValue` can be updated for this rule in
|
||||
the file [build.gradle.kts](../build.gradle.kts) (you will see further instructions there).
|
||||
the file [build.gradle.kts](../app/build.gradle.kts) (you will see further instructions there).
|
||||
|
||||
### Other points
|
||||
|
||||
|
||||
@@ -91,7 +91,6 @@ dependencies {
|
||||
testImplementation(projects.libraries.mediapickers.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.textcomposer.test)
|
||||
testImplementation(projects.libraries.voicerecorder.test)
|
||||
testImplementation(projects.libraries.mediaplayer.test)
|
||||
testImplementation(projects.libraries.mediaviewer.test)
|
||||
|
||||
@@ -110,9 +110,7 @@ fun TimelineItemVoiceView(
|
||||
showCursor = state.showCursor,
|
||||
playbackProgress = state.progress,
|
||||
waveform = content.waveform,
|
||||
modifier = Modifier
|
||||
.height(34.dp)
|
||||
.weight(1f),
|
||||
modifier = Modifier.height(34.dp),
|
||||
seekEnabled = !context.isScreenReaderEnabled(),
|
||||
onSeek = { state.eventSink(VoiceMessageEvents.Seek(it)) },
|
||||
)
|
||||
|
||||
@@ -215,7 +215,7 @@ dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
|
||||
dependencycheck = "org.owasp.dependencycheck:9.0.8"
|
||||
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" }
|
||||
paparazzi = "app.cash.paparazzi:1.3.1"
|
||||
kover = "org.jetbrains.kotlinx.kover:0.6.1"
|
||||
kover = "org.jetbrains.kotlinx.kover:0.7.5"
|
||||
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
|
||||
firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" }
|
||||
knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.0" }
|
||||
|
||||
1
libraries/coroutines/.gitignore
vendored
1
libraries/coroutines/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
plugins {
|
||||
id("java-library")
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.coroutines.core)
|
||||
}
|
||||
@@ -23,23 +23,22 @@ import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.Fill
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlin.math.max
|
||||
|
||||
fun DrawScope.drawWaveform(
|
||||
waveformData: ImmutableList<Float>,
|
||||
canvasSize: DpSize,
|
||||
canvasSizePx: Size,
|
||||
brush: Brush,
|
||||
minimumGraphAmplitude: Float = 2F,
|
||||
lineWidth: Dp = 2.dp,
|
||||
linePadding: Dp = 2.dp,
|
||||
) {
|
||||
val centerY = canvasSize.height.toPx() / 2
|
||||
val centerY = canvasSizePx.height / 2
|
||||
val cornerRadius = lineWidth / 2
|
||||
waveformData.forEachIndexed { index, amplitude ->
|
||||
val drawingAmplitude = max(minimumGraphAmplitude, amplitude * (canvasSize.height.toPx() - 2))
|
||||
val drawingAmplitude = max(minimumGraphAmplitude, amplitude * (canvasSizePx.height - 2))
|
||||
drawRoundRect(
|
||||
brush = brush,
|
||||
topLeft = Offset(
|
||||
|
||||
@@ -40,12 +40,13 @@ import androidx.compose.ui.graphics.drawscope.Fill
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.RequestDisallowInterceptTouchEvent
|
||||
import androidx.compose.ui.input.pointer.pointerInteropFilter
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
@@ -58,7 +59,7 @@ private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F
|
||||
*
|
||||
* @param playbackProgress The current playback progress, between 0 and 1.
|
||||
* @param showCursor Whether to show the cursor or not.
|
||||
* @param waveform The waveform to display. Use [FakeWaveformFactory] to generate a fake waveform.
|
||||
* @param waveform The waveform to display. Use [createFakeWaveform] to generate a fake waveform.
|
||||
* @param onSeek Callback when the user seeks the waveform. Called with a value between 0 and 1.
|
||||
* @param modifier The modifier to be applied to the view.
|
||||
* @param seekEnabled Whether the user can seek the waveform or not.
|
||||
@@ -103,6 +104,11 @@ fun WaveformPlaybackView(
|
||||
}
|
||||
}
|
||||
|
||||
val density = LocalDensity.current
|
||||
val waveformWidthPx by remember {
|
||||
derivedStateOf { with(density) { normalizedWaveformData.size * (lineWidth + linePadding).roundToPx().toFloat() } }
|
||||
}
|
||||
|
||||
val requestDisallowInterceptTouchEvent = remember { RequestDisallowInterceptTouchEvent() }
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
@@ -110,19 +116,18 @@ fun WaveformPlaybackView(
|
||||
.graphicsLayer(alpha = DEFAULT_GRAPHICS_LAYER_ALPHA)
|
||||
.let {
|
||||
if (!seekEnabled) return@let it
|
||||
|
||||
it.pointerInteropFilter(requestDisallowInterceptTouchEvent = requestDisallowInterceptTouchEvent) { e ->
|
||||
return@pointerInteropFilter when (e.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
if (e.x in 0F..canvasSizePx.width) {
|
||||
if (e.x in 0F..waveformWidthPx) {
|
||||
requestDisallowInterceptTouchEvent.invoke(true)
|
||||
seekProgress.value = e.x / canvasSizePx.width
|
||||
seekProgress.value = e.x / waveformWidthPx
|
||||
true
|
||||
} else false
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (e.x in 0F..canvasSizePx.width) {
|
||||
seekProgress.value = e.x / canvasSizePx.width
|
||||
if (e.x in 0F..waveformWidthPx) {
|
||||
seekProgress.value = e.x / waveformWidthPx
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -140,11 +145,11 @@ fun WaveformPlaybackView(
|
||||
) {
|
||||
canvasSize = size.toDpSize()
|
||||
canvasSizePx = size
|
||||
val centerY = canvasSize.height.toPx() / 2
|
||||
val cornerRadius = lineWidth / 2
|
||||
// Calculate the size of the waveform by summing the width of all the lines and paddings
|
||||
drawWaveform(
|
||||
waveformData = normalizedWaveformData,
|
||||
canvasSize = canvasSize,
|
||||
canvasSizePx = canvasSizePx,
|
||||
brush = brush,
|
||||
lineWidth = lineWidth,
|
||||
linePadding = linePadding
|
||||
@@ -152,8 +157,8 @@ fun WaveformPlaybackView(
|
||||
drawRect(
|
||||
brush = progressBrush,
|
||||
size = Size(
|
||||
width = progressAnimated.value * canvasSize.width.toPx(),
|
||||
height = canvasSize.height.toPx()
|
||||
width = progressAnimated.value * waveformWidthPx,
|
||||
height = canvasSizePx.height
|
||||
),
|
||||
blendMode = BlendMode.SrcAtop
|
||||
)
|
||||
@@ -161,12 +166,12 @@ fun WaveformPlaybackView(
|
||||
drawRoundRect(
|
||||
brush = cursorBrush,
|
||||
topLeft = Offset(
|
||||
x = progressAnimated.value * canvasSize.width.toPx(),
|
||||
y = centerY - (canvasSize.height.toPx() - 2) / 2
|
||||
x = progressAnimated.value * waveformWidthPx,
|
||||
y = 1f
|
||||
),
|
||||
size = Size(
|
||||
width = lineWidth.toPx(),
|
||||
height = canvasSize.height.toPx() - 2
|
||||
height = canvasSizePx.height - 2
|
||||
),
|
||||
cornerRadius = CornerRadius(cornerRadius.toPx(), cornerRadius.toPx()),
|
||||
style = Fill
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
@@ -66,7 +67,6 @@ fun LiveWaveformView(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Box(contentAlignment = Alignment.CenterEnd,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -79,11 +79,12 @@ fun LiveWaveformView(
|
||||
.graphicsLayer(alpha = DEFAULT_GRAPHICS_LAYER_ALPHA)
|
||||
.then(modifier)
|
||||
) {
|
||||
canvasSize = DpSize(Dp(min(waveformWidth, parentWidth.toFloat())), size.height.toDp())
|
||||
val width = min(waveformWidth, parentWidth.toFloat())
|
||||
canvasSize = DpSize(width.dp, size.height.toDp())
|
||||
val countThatFitsWidth = (parentWidth.toFloat() / (lineWidth.toPx() + linePadding.toPx())).toInt()
|
||||
drawWaveform(
|
||||
waveformData = levels.takeLast(countThatFitsWidth).toPersistentList(),
|
||||
canvasSize = canvasSize,
|
||||
canvasSizePx = Size(canvasSize.width.toPx(), size.height),
|
||||
brush = brush,
|
||||
lineWidth = lineWidth,
|
||||
linePadding = linePadding,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.textcomposer.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.libraries.textcomposer.impl)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user