Skip to content

Commit

Permalink
Improved UI - Added Spans - Added Voice Message
Browse files Browse the repository at this point in the history
Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
  • Loading branch information
rapterjet2004 committed Feb 24, 2025
1 parent d895643 commit 2806d40
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import com.nextcloud.talk.chat.data.io.MediaRecorderManager
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
import com.nextcloud.talk.data.database.mappers.asModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.extensions.toIntOrZero
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
Expand Down Expand Up @@ -128,8 +127,8 @@ class ChatViewModel @Inject constructor(
val voiceMessagePlaybackSpeedPreferences: LiveData<Map<String, PlaybackSpeed>>
get() = _voiceMessagePlaybackSpeedPreferences

private val _getContextChatMessages: MutableLiveData<List<ChatMessage>> = MutableLiveData()
val getContextChatMessages: LiveData<List<ChatMessage>>
private val _getContextChatMessages: MutableLiveData<List<ChatMessageJson>> = MutableLiveData()
val getContextChatMessages: LiveData<List<ChatMessageJson>>
get() = _getContextChatMessages

val getMessageFlow = chatRepository.messageFlow
Expand Down Expand Up @@ -810,7 +809,7 @@ class ChatViewModel @Inject constructor(
token,
messageId,
limit
).map(ChatMessageJson::asModel)
)

_getContextChatMessages.value = messages
}
Expand Down
131 changes: 105 additions & 26 deletions app/src/main/java/com/nextcloud/talk/ui/ComposeChatAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,29 @@ package com.nextcloud.talk.ui

import android.content.Context
import android.util.Log
import android.view.View.TEXT_ALIGNMENT_VIEW_START
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.LinearLayout
import androidx.compose.foundation.background
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.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
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.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -31,57 +40,75 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import autodagger.AutoInjector
import coil.compose.AsyncImage
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.contacts.ContactsViewModel
import com.nextcloud.talk.contacts.loadImage
import com.nextcloud.talk.data.database.mappers.asModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.chat.ChatMessageJson
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.message.MessageUtils
import javax.inject.Inject
import kotlin.random.Random

@Suppress("FunctionNaming")
@AutoInjector(NextcloudTalkApplication::class)
class ComposeChatAdapter {
class ComposeChatAdapter(private var messagesJson: List<ChatMessageJson>?) {

var incomingShape: RoundedCornerShape
var outgoingShape: RoundedCornerShape
private var incomingShape: RoundedCornerShape
private var outgoingShape: RoundedCornerShape
init {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
incomingShape = RoundedCornerShape(2.dp, 20.dp, 20.dp, 20.dp)
outgoingShape = RoundedCornerShape(20.dp, 2.dp, 20.dp, 20.dp)
}

// NOTE: I would like to optimize context and colorScheme by declaring them before hand. In fact, it's best
// practice to minimize new instances inside the view holders.
companion object {
private val REGULAR_TEXT_SIZE = 16.sp
private val TIME_TEXT_SIZE = 12.sp
private val AUTHOR_TEXT_SIZE = 12.sp
}

@Inject
lateinit var userManager: UserManager

@Inject
lateinit var viewThemeUtils: ViewThemeUtils

@Inject
lateinit var messageUtils: MessageUtils

@Inject
lateinit var contactsViewModel: ContactsViewModel

private val currentUser: User = userManager.currentUser.blockingGet()

@Composable
fun GetView(context: Context, messages: List<ChatMessage>) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
// TODO figure out dates
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(16.dp) ) {
// TODO figure out dates, they seem to be handled on the fly in the adapter
// likely, by keeping track of the last day, and generating an appropriately formated date view
// from the messages. Need a boolean to track ifAdd
items(messages) { message ->
when (val type = message.getCalculateMessageType()) {
ChatMessage.MessageType.SYSTEM_MESSAGE -> {
SystemMessage(context, message)
}

// TODO
ChatMessage.MessageType.VOICE_MESSAGE -> {
Log.d("Julius", "Voice Message: ${message.message}")
VoiceMessage(context, message)
}

ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE -> {
Expand Down Expand Up @@ -120,16 +147,25 @@ class ComposeChatAdapter {
}
}

@Composable
private fun GenericDate(context: Context, dateString: String) {
val colorScheme = viewThemeUtils.getColorScheme(context)
val color = colorScheme.onBackground
Row(horizontalArrangement = Arrangement.Absolute.Center, verticalAlignment = Alignment.CenterVertically) {
Spacer(modifier = Modifier.weight(1f))
Text(dateString, fontSize = AUTHOR_TEXT_SIZE, modifier = Modifier.padding(8.dp), color = color)
Spacer(modifier = Modifier.weight(1f))
}
}

@Composable
private fun CommonMessageQuote(context: Context, message: ChatMessage) {
val colorScheme = viewThemeUtils.getColorScheme(context)
val color = Color.Red
Row {
// FIXME get this dumb shape to work
Box(modifier = Modifier.padding(0.dp, 0.dp, 8.dp, 0.dp)) {}
Row(modifier = Modifier.padding(16.dp, 0.dp, 0.dp, 0.dp).background(colorScheme.onBackground)) {
// FIXME get this dumb shape to work, try with a Icon

Column {
Text(message.actorDisplayName!!, fontSize = 8.sp)
Text(message.actorDisplayName!!, fontSize = AUTHOR_TEXT_SIZE)
val imageUri = message.imageUrl
if (imageUri != null) {
val errorPlaceholderImage: Int = R.drawable.ic_mimetype_image
Expand All @@ -142,7 +178,7 @@ class ComposeChatAdapter {
.fillMaxHeight()
)
}
EnrichedText(message.text)
EnrichedText(message)
}
}
}
Expand Down Expand Up @@ -182,48 +218,68 @@ class ComposeChatAdapter {
}

Surface(
modifier = Modifier.widthIn(40.dp, 140.dp),
modifier = Modifier.defaultMinSize(60.dp, 80.dp).widthIn(60.dp, 280.dp),
color = Color(color),
shape = shape
) {
val timeString = DateUtils(context).getLocalTimeStringFromTimestamp(message.timestamp)
Column(modifier = Modifier.padding(8.dp, 4.dp, 8.dp, 4.dp)) {
if (message.parentMessageId != null && !message.isDeleted) {
// TODO get parent message in non foolish way For now to test UI, I use the same message
CommonMessageQuote(context, message)
messagesJson?.let { list -> list.find { it.parentMessage?.id == message.parentMessageId }
?.parentMessage!!.asModel()
.let {
CommonMessageQuote(context, it)
}
}
}

Text(message.actorDisplayName!!, fontSize = 8.sp)
Text(message.actorDisplayName!!, fontSize = AUTHOR_TEXT_SIZE)
Row {
content()
Spacer(modifier = Modifier.size(8.dp))
Text(timeString, fontSize = 8.sp, modifier = Modifier.align(Alignment.Bottom))
Text(timeString, fontSize = TIME_TEXT_SIZE, textAlign = TextAlign.End)
}
}
}
}
}

@Composable
private fun EnrichedText(text: String) {
// TODO -_- google has no out of box solution for converting spannable to annotated string.
Text(text, fontSize = 12.sp)
private fun EnrichedText(message: ChatMessage) {
AndroidView(factory = { ctx ->
val incoming = message.actorId != currentUser.userId
val processedMessageText = messageUtils.enrichChatMessageText(
ctx,
message,
incoming,
viewThemeUtils
)

androidx.emoji2.widget.EmojiTextView(ctx).apply {
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
setLineSpacing(0F, 1.2f)
textAlignment = TEXT_ALIGNMENT_VIEW_START
text = processedMessageText
}
}, modifier = Modifier)
}

@Composable
private fun TextMessage(context: Context, message: ChatMessage) {
CommonMessageBody(context, message) {
EnrichedText(message.text)
EnrichedText(message)
}
}

@Composable
private fun SystemMessage(context: Context, message: ChatMessage) {
// TODO crap I forgot this has wrapping -_- dang it. I'll have to handle that later
val timeString = DateUtils(context).getLocalTimeStringFromTimestamp(message.timestamp)
Row(verticalAlignment = Alignment.CenterVertically) {
Text(message.text, fontSize = 8.sp, modifier = Modifier.padding(8.dp))
Text(timeString, fontSize = 6.sp)
Row(horizontalArrangement = Arrangement.Absolute.Center, verticalAlignment = Alignment.CenterVertically) {
Spacer(modifier = Modifier.weight(1f))
Text(message.text, fontSize = REGULAR_TEXT_SIZE, modifier = Modifier.padding(8.dp))
Text(timeString, fontSize = TIME_TEXT_SIZE)
Spacer(modifier = Modifier.weight(1f))
}
}

Expand All @@ -244,4 +300,27 @@ class ComposeChatAdapter {
Text(message.text, fontSize = 12.sp, modifier = Modifier.widthIn(20.dp, 140.dp))
}
}

@Composable
private fun VoiceMessage(context: Context, message: ChatMessage) {
val colorScheme = viewThemeUtils.getColorScheme(context)
CommonMessageBody(context, message) {
Icon(Icons.Filled.PlayArrow,
"play",
modifier = Modifier
.size(24.dp)
.align(Alignment.CenterVertically))

AndroidView(factory = { ctx ->
WaveformSeekBar(ctx).apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
setWaveData(FloatArray(50) { Random.nextFloat() }) // READ ONLY for now
setColors(colorScheme.inversePrimary.toArgb(), colorScheme.onPrimaryContainer.toArgb())
}
}, modifier = Modifier
.align(Alignment.CenterVertically)
.height(80.dp)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
Expand All @@ -34,6 +35,8 @@ import androidx.lifecycle.asFlow
import autodagger.AutoInjector
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.data.database.mappers.asModel
import com.nextcloud.talk.models.json.chat.ChatMessageJson
import com.nextcloud.talk.ui.ComposeChatAdapter
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
Expand All @@ -44,7 +47,7 @@ import javax.inject.Inject
class ContextChatCompose(val bundle: Bundle) {

companion object {
private const val LIMIT = 10
private const val LIMIT = 12
}

@Inject
Expand Down Expand Up @@ -72,7 +75,6 @@ class ContextChatCompose(val bundle: Bundle) {
return
}

val adapter = ComposeChatAdapter()
val colorScheme = viewThemeUtils.getColorScheme(context)
MaterialTheme(colorScheme) {
Dialog(
Expand All @@ -82,17 +84,16 @@ class ContextChatCompose(val bundle: Bundle) {
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true,
usePlatformDefaultWidth = true
usePlatformDefaultWidth = false
)
) {
Surface(
shape = RoundedCornerShape(8.dp),
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.fillMaxHeight(.9f)
.fillMaxHeight()
) {
val user = userManager.currentUser.blockingGet()
if (!user.hasSpreedFeatureCapability("chat-get-context") ||
Expand All @@ -110,14 +111,27 @@ class ContextChatCompose(val bundle: Bundle) {
)
} else {
Row(modifier = Modifier.align(Alignment.Start)) {
IconButton(onClick = {
shouldDismiss.value = true
}) {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
"Exit",
modifier = Modifier
.align(Alignment.CenterVertically)
.size(48.dp)
)
}
Column {
// TODO get these from the activity
// TODO get these from token from bundle
Text("Conversation 1", fontSize = 24.sp)
Text("This is description", fontSize = 12.sp)
}
}
val contextState = viewModel.getContextChatMessages.asFlow().collectAsState(listOf())
adapter.GetView(context, contextState.value)
val messagesJson = contextState.value
val messages = messagesJson.map(ChatMessageJson::asModel)
ComposeChatAdapter(messagesJson).GetView(context, messages)
}
}
}
Expand Down

0 comments on commit 2806d40

Please sign in to comment.