Merge branch 'develop' into renovate/io.nlopez.compose.rules-detekt-0.x

This commit is contained in:
Benoit Marty
2024-01-10 09:39:28 +01:00
committed by GitHub
43 changed files with 309 additions and 325 deletions

View File

@@ -18,12 +18,6 @@
],
"groupName" : "kotlin"
},
{
"matchPackageNames" : [
"org.jetbrains.kotlinx.kover"
],
"enabled" : false
},
{
"matchPackagePatterns" : [
"^org.maplibre"

View File

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

View File

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

View File

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

View File

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

@@ -0,0 +1 @@
Migrate to Kover 0.7.X

1
changelog.d/2156.bugfix Normal file
View File

@@ -0,0 +1 @@
Improve rendering of voice messages in the timeline in large displays

View File

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

View File

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

View File

@@ -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)) },
)

View File

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

View File

@@ -1 +0,0 @@
/build

View File

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

View File

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

View File

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

View File

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

View File

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