Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0a50063
Early setup for composable messages
ThomasSession Jun 9, 2025
ed6a32d
Composable message base setup and todos
ThomasSession Jun 9, 2025
9df1f96
Fixed colors
ThomasSession Jun 9, 2025
5123eb9
More work on message composables
ThomasSession Jun 10, 2025
7842d5e
Merge branch 'dev' into feature/compose-conversation
ThomasSession Jun 21, 2025
b8292c2
DocumentView
ThomasSession Jun 21, 2025
03198e5
Quotes
ThomasSession Jun 22, 2025
40ec5e6
quote images
ThomasSession Jun 22, 2025
36b6831
clean up
ThomasSession Jun 22, 2025
379e16e
Merge branch 'dev' into feature/compose-conversation
ThomasSession Jun 28, 2025
b7c64f3
Link data and UI in mesasges
ThomasSession Jun 30, 2025
6ec772d
Merge branch 'dev' into feature/compose-conversation
ThomasSession Jul 2, 2025
1ebd7fb
Using boxwithconstraint scope
ThomasSession Jul 3, 2025
ae7f9d6
Audio UI and using our new composable in the Landing page
ThomasSession Jul 3, 2025
4e7fd2a
More compose messaging ui
ThomasSession Jul 5, 2025
6fb76cf
Merge branch 'dev' into feature/compose-conversation
ThomasSession Jul 23, 2025
fb8ae93
Merge branch 'dev' into feature/compose-conversation
ThomasSession Jul 31, 2025
ebd05e2
WIP media messages
ThomasSession Jul 31, 2025
f5b9cdf
Media item sizing
ThomasSession Aug 4, 2025
8cc8d95
Merge branch 'dev' into feature/compose-conversation
ThomasSession Nov 3, 2025
a6d37f3
Merge branch 'dev' into feature/compose-conversation
ThomasSession Feb 19, 2026
4f4a2fc
Tweaks
ThomasSession Feb 19, 2026
62836fd
new audio player todo
ThomasSession Feb 19, 2026
b6d5557
Update app/src/main/java/org/thoughtcrime/securesms/conversation/v3/c…
ThomasSession Feb 19, 2026
907789d
Update app/src/main/java/org/thoughtcrime/securesms/conversation/v3/c…
ThomasSession Feb 19, 2026
c09bbc9
Update app/src/main/java/org/thoughtcrime/securesms/conversation/v3/c…
ThomasSession Feb 19, 2026
8f4fee1
Update app/src/main/java/org/thoughtcrime/securesms/conversation/v3/c…
ThomasSession Feb 19, 2026
d71c3b9
Update app/src/main/java/org/thoughtcrime/securesms/conversation/v3/c…
ThomasSession Feb 19, 2026
20c6c46
Merge branch 'dev' into feature/compose-conversation
ThomasSession Feb 20, 2026
1fff43b
PR feedback + compose audio player
ThomasSession Feb 20, 2026
570c4bc
Removing Glide from the new Composables
ThomasSession Feb 20, 2026
c0c671a
Merge branch 'dev' into feature/compose-conversation
ThomasSession Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package org.thoughtcrime.securesms.conversation.v3.compose

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.components.SmallCircularProgressIndicator
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.theme.ThemeColors


@Composable
fun AudioMessage(
data: Audio,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(vertical = LocalDimensions.current.smallSpacing),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.xxsSpacing)
) {

val textColor = getTextColor(data.outgoing)

val (color1, color2, trackEmptyColor) = if (data.outgoing) {
arrayOf(
LocalColors.current.backgroundSecondary, // bg secondary
LocalColors.current.text, // text primary
LocalColors.current.backgroundSecondary.copy(alpha = 0.5f)
)
} else {
arrayOf(
LocalColors.current.accent, // accent
LocalColors.current.background, // background primary
LocalColors.current.textSecondary // text secondary

)
}

// Title
Text(
modifier = Modifier
.padding(start = LocalDimensions.current.smallSpacing, end = LocalDimensions.current.smallSpacing),
text = data.title,
style = LocalType.current.small.copy(fontStyle = FontStyle.Italic),
color = textColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)

// play + seek
Row(
modifier = Modifier
.padding(horizontal = LocalDimensions.current.smallSpacing),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)
) {
PlayPauseButton(
isPlaying = data.isPlaying,
showLoader = data.showLoader,
bgColor = color1,
iconColor = color2,
onClick = {
//todo CONVOV3 implement
}
)

// Slider acts like SeekBar
val progress =
if (data.durationMs > 0) (data.positionMs.toFloat() / data.durationMs.toFloat())
else 0f

Slider(
modifier = Modifier.weight(1f),
value = progress.coerceIn(0f, 1f),
onValueChange = {
//todo CONVOV3 implement
},
enabled = !data.showLoader,
valueRange = 0f..1f,
colors = androidx.compose.material3.SliderDefaults.colors(
thumbColor = color1,
activeTrackColor = color1,
inactiveTrackColor = trackEmptyColor
)
)
}

// Bottom: speed chip + remaining
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
start = LocalDimensions.current.smallSpacing + 36.dp + LocalDimensions.current.smallSpacing, // aligns with slider start after play button
end = LocalDimensions.current.smallSpacing
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
PlaybackSpeedButton(
text = data.speedText,
bgColor = if (data.outgoing) color1 else color2,
textColor = if(data.outgoing) color2 else textColor,
onClick = {
//todo CONVOV3 implement
}
)

Text(
text = data.remainingText,
style = LocalType.current.small,
color = textColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}

@Composable
private fun PlayPauseButton(
isPlaying: Boolean,
showLoader: Boolean,
bgColor: Color,
iconColor: Color,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {

Box(
modifier = modifier
.size(36.dp)
.clip(CircleShape)
.background(bgColor)
.clickable(onClick = onClick),
contentAlignment = Alignment.Center
) {
if (showLoader) {
SmallCircularProgressIndicator(color = iconColor)
} else {
Image(
painter = painterResource(
id = if (isPlaying) R.drawable.pause else R.drawable.play
),
contentDescription = null,
colorFilter = ColorFilter.tint(iconColor),
modifier = Modifier.size(16.dp)
)
}
}
}


@Composable
private fun PlaybackSpeedButton(
text: String,
bgColor: Color,
textColor: Color,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.clip(RoundedCornerShape(LocalDimensions.current.shapeXXSmall))
.background(bgColor)
.clickable(onClick = onClick)
.padding(
horizontal = LocalDimensions.current.xxsSpacing,
vertical = LocalDimensions.current.xxxsSpacing
),
contentAlignment = Alignment.Center
) {
Text(
text = text,
style = LocalType.current.small,
color = textColor,
maxLines = 1,
overflow = TextOverflow.Clip
)
}
}

data class Audio(
override val outgoing: Boolean,
override val text: AnnotatedString? = null,
val title: String,
val speedText: String,
val remainingText: String,
val durationMs: Long, // slider max reference
val positionMs: Long, // slider position
val bufferedPositionMs: Long = 0L,
val isPlaying: Boolean,
val showLoader: Boolean,
) : MessageType()

@Preview
@Composable
fun AudioMessagePreview(
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
) {
PreviewTheme(colors) {
Column(
modifier = Modifier.fillMaxWidth().padding(LocalDimensions.current.spacing)

) {
Message(data = MessageViewData(
author = "Toto",
type = PreviewMessageData.audio()
))

Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))

Message(data = MessageViewData(
author = "Toto",
avatar = PreviewMessageData.sampleAvatar,
type = PreviewMessageData.audio(
outgoing = false,
title = "Audio with a really long name that should ellipsize once it reaches the max width",
)
))

Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))

Message(data = MessageViewData(
author = "Toto",
type = PreviewMessageData.audio(
playing = false
)
))
}
}
}
Loading