Skip to content

Commit

Permalink
Merge pull request #3959 from element-hq/feature/bma/videoPlayer
Browse files Browse the repository at this point in the history
Video player controller
  • Loading branch information
bmarty authored Nov 28, 2024
2 parents 67be3f0 + 5686b77 commit 31f9fa2
Show file tree
Hide file tree
Showing 15 changed files with 518 additions and 46 deletions.
5 changes: 5 additions & 0 deletions libraries/dateformatter/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ plugins {

android {
namespace = "io.element.android.libraries.dateformatter.api"

dependencies {
testImplementation(libs.test.junit)
testImplementation(libs.test.truth)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.libraries.dateformatter.api

import java.util.Locale

/**
* Convert milliseconds to human readable duration.
* Hours in 1 digit or more.
* Minutes in 2 digits when hours are available.
* Seconds always on 2 digits.
* Example:
* - when the duration is longer than 1 hour:
* - "10:23:34"
* - "1:23:34"
* - "1:03:04"
* - when the duration is shorter:
* - "4:56"
* - "14:06"
* - Less than one minute:
* - "0:00"
* - "0:01"
* - "0:59"
*/
fun Long.toHumanReadableDuration(): String {
val inSeconds = this / 1_000
val hours = inSeconds / 3_600
val minutes = inSeconds % 3_600 / 60
val seconds = inSeconds % 60
return if (hours > 0) {
String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds)
} else {
String.format(Locale.US, "%d:%02d", minutes, seconds)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.libraries.dateformatter.api

import com.google.common.truth.Truth.assertThat
import org.junit.Test

class DurationFormatterTest {
@Test
fun `format seconds only`() {
assertThat(buildDuration().toHumanReadableDuration()).isEqualTo("0:00")
assertThat(buildDuration(seconds = 1).toHumanReadableDuration()).isEqualTo("0:01")
assertThat(buildDuration(seconds = 59).toHumanReadableDuration()).isEqualTo("0:59")
}

@Test
fun `format minutes and seconds`() {
assertThat(buildDuration(minutes = 1).toHumanReadableDuration()).isEqualTo("1:00")
assertThat(buildDuration(minutes = 1, seconds = 30).toHumanReadableDuration()).isEqualTo("1:30")
assertThat(buildDuration(minutes = 59, seconds = 59).toHumanReadableDuration()).isEqualTo("59:59")
}

@Test
fun `format hours, minutes and seconds`() {
assertThat(buildDuration(hours = 1).toHumanReadableDuration()).isEqualTo("1:00:00")
assertThat(buildDuration(hours = 1, minutes = 1, seconds = 1).toHumanReadableDuration()).isEqualTo("1:01:01")
assertThat(buildDuration(hours = 24, minutes = 59, seconds = 59).toHumanReadableDuration()).isEqualTo("24:59:59")
assertThat(buildDuration(hours = 25, minutes = 0, seconds = 0).toHumanReadableDuration()).isEqualTo("25:00:00")
}

private fun buildDuration(
hours: Int = 0,
minutes: Int = 0,
seconds: Int = 0
): Long {
return (hours * 60 * 60 + minutes * 60 + seconds) * 1000L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,32 @@
* Please see LICENSE in the repository root for full details.
*/

@file:OptIn(ExperimentalMaterial3Api::class)

package io.element.android.libraries.designsystem.theme.components

import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SliderColors
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
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.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup

Expand All @@ -32,8 +45,20 @@ fun Slider(
steps: Int = 0,
onValueChangeFinish: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
useCustomLayout: Boolean = false,
) {
val thumbColor = ElementTheme.colors.iconOnSolidPrimary
var isUserInteracting by remember { mutableStateOf(false) }
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
isUserInteracting = when (interaction) {
is DragInteraction.Start,
is PressInteraction.Press -> true
else -> false
}
}
}
androidx.compose.material3.Slider(
value = value,
onValueChange = onValueChange,
Expand All @@ -43,6 +68,54 @@ fun Slider(
steps = steps,
onValueChangeFinished = onValueChangeFinish,
colors = colors,
thumb = {
if (useCustomLayout) {
SliderDefaults.Thumb(
modifier = Modifier.drawWithContent {
drawContent()
if (isUserInteracting.not()) {
drawCircle(thumbColor, radius = 8.dp.toPx())
}
},
interactionSource = interactionSource,
colors = colors.copy(
thumbColor = ElementTheme.colors.iconPrimary,
),
enabled = enabled,
thumbSize = DpSize(
if (isUserInteracting) 44.dp else 22.dp,
22.dp,
),
)
} else {
SliderDefaults.Thumb(
interactionSource = interactionSource,
colors = colors,
enabled = enabled
)
}
},
track = { sliderState ->
if (useCustomLayout) {
SliderDefaults.Track(
modifier = Modifier.height(8.dp),
colors = colors.copy(
activeTrackColor = Color(0x66E0EDFF),
inactiveTrackColor = Color(0x66E0EDFF),
),
enabled = enabled,
sliderState = sliderState,
thumbTrackGapSize = 0.dp,
drawStopIndicator = { },
)
} else {
SliderDefaults.Track(
colors = colors,
enabled = enabled,
sliderState = sliderState,
)
}
},
interactionSource = interactionSource,
)
}
Expand All @@ -55,5 +128,6 @@ internal fun SlidersPreview() = ElementThemedPreview {
Slider(onValueChange = { value = it }, value = value, enabled = true)
Slider(steps = 10, onValueChange = { value = it }, value = value, enabled = true)
Slider(onValueChange = { value = it }, value = value, enabled = false)
Slider(onValueChange = { value = it }, value = value, enabled = true, useCustomLayout = true)
}
}
1 change: 1 addition & 0 deletions libraries/mediaviewer/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
implementation(projects.libraries.androidutils)
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
implementation(projects.libraries.dateformatter.api)
implementation(projects.libraries.di)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.matrix.api)
Expand Down
Loading

0 comments on commit 31f9fa2

Please sign in to comment.