mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 10:28:41 +01:00
* feat: add possibility to edit messages; closes #226 * fix: chat loading messages; closes #202
This commit is contained in:
parent
b878c3d0eb
commit
08c1490502
@ -0,0 +1,14 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core.api.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EditPrivateMessageForm(
|
||||||
|
@SerialName("content")
|
||||||
|
val content: String,
|
||||||
|
@SerialName("private_message_id")
|
||||||
|
val privateMessageId: PrivateMessageId,
|
||||||
|
@SerialName("auth")
|
||||||
|
val auth: String,
|
||||||
|
)
|
@ -1,7 +1,9 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.core.api.service
|
package com.github.diegoberaldin.raccoonforlemmy.core.api.service
|
||||||
|
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePrivateMessageForm
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePrivateMessageForm
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.EditPrivateMessageForm
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.MarkPrivateMessageAsReadForm
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.MarkPrivateMessageAsReadForm
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.PersonId
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.PrivateMessageResponse
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.PrivateMessageResponse
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.PrivateMessagesResponse
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.PrivateMessagesResponse
|
||||||
import de.jensklingenberg.ktorfit.Response
|
import de.jensklingenberg.ktorfit.Response
|
||||||
@ -10,6 +12,7 @@ import de.jensklingenberg.ktorfit.http.GET
|
|||||||
import de.jensklingenberg.ktorfit.http.Header
|
import de.jensklingenberg.ktorfit.http.Header
|
||||||
import de.jensklingenberg.ktorfit.http.Headers
|
import de.jensklingenberg.ktorfit.http.Headers
|
||||||
import de.jensklingenberg.ktorfit.http.POST
|
import de.jensklingenberg.ktorfit.http.POST
|
||||||
|
import de.jensklingenberg.ktorfit.http.PUT
|
||||||
import de.jensklingenberg.ktorfit.http.Query
|
import de.jensklingenberg.ktorfit.http.Query
|
||||||
|
|
||||||
interface PrivateMessageService {
|
interface PrivateMessageService {
|
||||||
@ -18,6 +21,7 @@ interface PrivateMessageService {
|
|||||||
@Header("Authorization") authHeader: String? = null,
|
@Header("Authorization") authHeader: String? = null,
|
||||||
@Query("auth") auth: String? = null,
|
@Query("auth") auth: String? = null,
|
||||||
@Query("page") page: Int? = null,
|
@Query("page") page: Int? = null,
|
||||||
|
@Query("creator_id") creatorId: PersonId? = null,
|
||||||
@Query("limit") limit: Int? = null,
|
@Query("limit") limit: Int? = null,
|
||||||
@Query("unread_only") unreadOnly: Boolean? = null,
|
@Query("unread_only") unreadOnly: Boolean? = null,
|
||||||
): Response<PrivateMessagesResponse>
|
): Response<PrivateMessagesResponse>
|
||||||
@ -29,6 +33,13 @@ interface PrivateMessageService {
|
|||||||
@Body form: CreatePrivateMessageForm,
|
@Body form: CreatePrivateMessageForm,
|
||||||
): Response<PrivateMessageResponse>
|
): Response<PrivateMessageResponse>
|
||||||
|
|
||||||
|
@PUT("private_message")
|
||||||
|
@Headers("Content-Type: application/json")
|
||||||
|
suspend fun editPrivateMessage(
|
||||||
|
@Header("Authorization") authHeader: String? = null,
|
||||||
|
@Body form: EditPrivateMessageForm,
|
||||||
|
): Response<PrivateMessageResponse>
|
||||||
|
|
||||||
@POST("private_message/mark_as_read")
|
@POST("private_message/mark_as_read")
|
||||||
@Headers("Content-Type: application/json")
|
@Headers("Content-Type: application/json")
|
||||||
suspend fun markPrivateMessageAsRead(
|
suspend fun markPrivateMessageAsRead(
|
||||||
|
@ -29,10 +29,12 @@ interface InboxChatMviModel :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class EditMessage(val value: Int) : Intent
|
||||||
data class SubmitNewMessage(val value: String) : Intent
|
data class SubmitNewMessage(val value: String) : Intent
|
||||||
}
|
}
|
||||||
|
|
||||||
data class UiState(
|
data class UiState(
|
||||||
|
val initial: Boolean = true,
|
||||||
val refreshing: Boolean = false,
|
val refreshing: Boolean = false,
|
||||||
val loading: Boolean = false,
|
val loading: Boolean = false,
|
||||||
val canFetchMore: Boolean = true,
|
val canFetchMore: Boolean = true,
|
||||||
@ -41,6 +43,7 @@ interface InboxChatMviModel :
|
|||||||
val otherUserAvatar: String? = null,
|
val otherUserAvatar: String? = null,
|
||||||
val messages: List<PrivateMessageModel> = emptyList(),
|
val messages: List<PrivateMessageModel> = emptyList(),
|
||||||
val autoLoadImages: Boolean = true,
|
val autoLoadImages: Boolean = true,
|
||||||
|
val editedMessageId: Int? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed interface Effect {
|
sealed interface Effect {
|
||||||
|
@ -56,11 +56,14 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
|||||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.toTypography
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.toTypography
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomImage
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomImage
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Option
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.OptionId
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.TextFormattingBar
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.TextFormattingBar
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getInboxChatViewModel
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getInboxChatViewModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
|
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.gallery.getGalleryHelper
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.gallery.getGalleryHelper
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
@ -158,7 +161,14 @@ class InboxChatScreen(
|
|||||||
),
|
),
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(MR.strings.inbox_chat_message),
|
text = buildString {
|
||||||
|
append(stringResource(MR.strings.inbox_chat_message))
|
||||||
|
if (uiState.editedMessageId != null) {
|
||||||
|
append(" (")
|
||||||
|
append(stringResource(MR.strings.post_action_edit))
|
||||||
|
append(")")
|
||||||
|
}
|
||||||
|
},
|
||||||
style = typography.bodyMedium,
|
style = typography.bodyMedium,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -217,6 +227,11 @@ class InboxChatScreen(
|
|||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(Spacing.s))
|
Spacer(modifier = Modifier.height(Spacing.s))
|
||||||
}
|
}
|
||||||
|
if (uiState.messages.isEmpty() && uiState.initial) {
|
||||||
|
items(10) {
|
||||||
|
MessageCardPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
items(uiState.messages) { message ->
|
items(uiState.messages) { message ->
|
||||||
val isMyMessage = message.creator?.id == uiState.currentUserId
|
val isMyMessage = message.creator?.id == uiState.currentUserId
|
||||||
val content = message.content.orEmpty()
|
val content = message.content.orEmpty()
|
||||||
@ -225,13 +240,37 @@ class InboxChatScreen(
|
|||||||
isMyMessage = isMyMessage,
|
isMyMessage = isMyMessage,
|
||||||
content = content,
|
content = content,
|
||||||
date = date,
|
date = date,
|
||||||
|
options = buildList {
|
||||||
|
if (isMyMessage) {
|
||||||
|
this += Option(
|
||||||
|
OptionId.Edit,
|
||||||
|
stringResource(MR.strings.post_action_edit)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOptionSelected = rememberCallbackArgs { optionId ->
|
||||||
|
when (optionId) {
|
||||||
|
OptionId.Edit -> {
|
||||||
|
model.reduce(
|
||||||
|
InboxChatMviModel.Intent.EditMessage(
|
||||||
|
message.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
message.content?.also {
|
||||||
|
textFieldValue = TextFieldValue(text = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
if (!uiState.loading && !uiState.refreshing && uiState.canFetchMore) {
|
if (!uiState.initial && !uiState.loading && !uiState.refreshing && uiState.canFetchMore) {
|
||||||
model.reduce(InboxChatMviModel.Intent.LoadNextPage)
|
model.reduce(InboxChatMviModel.Intent.LoadNextPage)
|
||||||
}
|
}
|
||||||
if (uiState.loading && !uiState.refreshing) {
|
if (!uiState.initial && uiState.loading && !uiState.refreshing) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxWidth().padding(Spacing.xs),
|
modifier = Modifier.fillMaxWidth().padding(Spacing.xs),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
|
@ -6,6 +6,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationC
|
|||||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
|
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
|
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PrivateMessageModel
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PrivateMessageRepository
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PrivateMessageRepository
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
|
||||||
@ -59,6 +60,10 @@ class InboxChatViewModel(
|
|||||||
otherUserAvatar = user?.avatar,
|
otherUserAvatar = user?.avatar,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uiState.value.messages.isEmpty()) {
|
||||||
|
refresh(initial = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,59 +71,80 @@ class InboxChatViewModel(
|
|||||||
|
|
||||||
override fun reduce(intent: InboxChatMviModel.Intent) {
|
override fun reduce(intent: InboxChatMviModel.Intent) {
|
||||||
when (intent) {
|
when (intent) {
|
||||||
InboxChatMviModel.Intent.LoadNextPage -> loadNextPage()
|
InboxChatMviModel.Intent.LoadNextPage -> {
|
||||||
is InboxChatMviModel.Intent.SubmitNewMessage -> submitNewMessage(intent.value)
|
mvi.scope?.launch(Dispatchers.IO) {
|
||||||
is InboxChatMviModel.Intent.ImageSelected -> loadImageAndAppendUrlInBody(intent.value)
|
loadNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is InboxChatMviModel.Intent.SubmitNewMessage -> {
|
||||||
|
submitNewMessage(intent.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
is InboxChatMviModel.Intent.ImageSelected -> {
|
||||||
|
loadImageAndAppendUrlInBody(intent.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
is InboxChatMviModel.Intent.EditMessage -> {
|
||||||
|
uiState.value.messages.firstOrNull { it.id == intent.value }?.also { message ->
|
||||||
|
startEditingMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refresh() {
|
private suspend fun refresh(initial: Boolean = false) {
|
||||||
currentPage = 1
|
currentPage = 1
|
||||||
mvi.updateState { it.copy(canFetchMore = true, refreshing = true) }
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
initial = initial,
|
||||||
|
canFetchMore = true,
|
||||||
|
refreshing = true
|
||||||
|
)
|
||||||
|
}
|
||||||
loadNextPage()
|
loadNextPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadNextPage() {
|
private suspend fun loadNextPage() {
|
||||||
val currentState = mvi.uiState.value
|
val currentState = mvi.uiState.value
|
||||||
if (!currentState.canFetchMore || currentState.loading) {
|
if (!currentState.canFetchMore || currentState.loading) {
|
||||||
mvi.updateState { it.copy(refreshing = false) }
|
mvi.updateState { it.copy(refreshing = false) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mvi.scope?.launch(Dispatchers.IO) {
|
mvi.updateState { it.copy(loading = true) }
|
||||||
mvi.updateState { it.copy(loading = true) }
|
val auth = identityRepository.authToken.value
|
||||||
val auth = identityRepository.authToken.value
|
val refreshing = currentState.refreshing
|
||||||
val refreshing = currentState.refreshing
|
val itemList = messageRepository.getAll(
|
||||||
val itemList = messageRepository.getAll(
|
creatorId = otherUserId,
|
||||||
auth = auth,
|
auth = auth,
|
||||||
page = currentPage,
|
page = currentPage,
|
||||||
unreadOnly = false,
|
unreadOnly = false,
|
||||||
)?.filter {
|
)?.onEach {
|
||||||
it.creator?.id == otherUserId || it.recipient?.id == otherUserId
|
if (!it.read) {
|
||||||
}?.onEach {
|
markAsRead(true, it.id)
|
||||||
if (!it.read) {
|
|
||||||
launch {
|
|
||||||
markAsRead(true, it.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!itemList.isNullOrEmpty()) {
|
|
||||||
currentPage++
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!itemList.isNullOrEmpty()) {
|
||||||
|
currentPage++
|
||||||
|
}
|
||||||
|
|
||||||
mvi.updateState {
|
val itemsToAdd = itemList.orEmpty().filter {
|
||||||
val newItems = if (refreshing) {
|
it.creator?.id == otherUserId || it.recipient?.id == otherUserId
|
||||||
itemList.orEmpty()
|
}
|
||||||
} else {
|
mvi.updateState {
|
||||||
it.messages + itemList.orEmpty()
|
val newItems = if (refreshing) {
|
||||||
}
|
itemsToAdd
|
||||||
it.copy(
|
} else {
|
||||||
messages = newItems,
|
it.messages + itemsToAdd
|
||||||
loading = false,
|
|
||||||
canFetchMore = itemList?.isEmpty() != true,
|
|
||||||
refreshing = false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
it.copy(
|
||||||
|
messages = newItems,
|
||||||
|
loading = false,
|
||||||
|
canFetchMore = itemList?.isEmpty() != true,
|
||||||
|
refreshing = false,
|
||||||
|
initial = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,15 +179,36 @@ class InboxChatViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startEditingMessage(message: PrivateMessageModel) {
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(
|
||||||
|
editedMessageId = message.id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun submitNewMessage(text: String) {
|
private fun submitNewMessage(text: String) {
|
||||||
|
val editedMessageId = uiState.value.editedMessageId
|
||||||
if (text.isNotEmpty()) {
|
if (text.isNotEmpty()) {
|
||||||
mvi.scope?.launch {
|
mvi.scope?.launch {
|
||||||
val auth = identityRepository.authToken.value
|
val auth = identityRepository.authToken.value
|
||||||
messageRepository.create(
|
if (editedMessageId == null) {
|
||||||
message = text,
|
messageRepository.create(
|
||||||
auth = auth,
|
message = text,
|
||||||
recipiendId = otherUserId,
|
recipiendId = otherUserId,
|
||||||
)
|
auth = auth,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
messageRepository.edit(
|
||||||
|
messageId = editedMessageId,
|
||||||
|
message = text,
|
||||||
|
auth = auth,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
mvi.updateState {
|
||||||
|
it.copy(editedMessageId = null)
|
||||||
|
}
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,27 +12,44 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.MoreHoriz
|
||||||
import androidx.compose.material.icons.filled.Schedule
|
import androidx.compose.material.icons.filled.Schedule
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Path
|
import androidx.compose.ui.graphics.Path
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.layout.positionInParent
|
||||||
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.CornerSize
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.CornerSize
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomDropDown
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomizedContent
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomizedContent
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Option
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.OptionId
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardBody
|
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardBody
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.prettifyDate
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.prettifyDate
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun MessageCard(
|
internal fun MessageCard(
|
||||||
isMyMessage: Boolean = false,
|
isMyMessage: Boolean = false,
|
||||||
content: String = "",
|
content: String = "",
|
||||||
date: String = "",
|
date: String = "",
|
||||||
|
options: List<Option> = emptyList(),
|
||||||
|
onOptionSelected: ((OptionId) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val color = if (isMyMessage) {
|
val color = if (isMyMessage) {
|
||||||
MaterialTheme.colorScheme.tertiaryContainer
|
MaterialTheme.colorScheme.tertiaryContainer
|
||||||
@ -46,6 +63,10 @@ internal fun MessageCard(
|
|||||||
}
|
}
|
||||||
val longDistance = Spacing.l
|
val longDistance = Spacing.l
|
||||||
val mediumDistance = Spacing.s
|
val mediumDistance = Spacing.s
|
||||||
|
val ancillaryColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f)
|
||||||
|
var optionsExpanded by remember { mutableStateOf(false) }
|
||||||
|
var optionsOffset by remember { mutableStateOf(Offset.Zero) }
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
Canvas(
|
Canvas(
|
||||||
modifier = Modifier.size(mediumDistance).let {
|
modifier = Modifier.size(mediumDistance).let {
|
||||||
@ -95,30 +116,74 @@ internal fun MessageCard(
|
|||||||
PostCardBody(
|
PostCardBody(
|
||||||
text = content,
|
text = content,
|
||||||
)
|
)
|
||||||
Row(
|
Box {
|
||||||
horizontalArrangement = Arrangement.spacedBy(Spacing.xxxs),
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
horizontalArrangement = Arrangement.spacedBy(Spacing.xxxs),
|
||||||
) {
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
) {
|
||||||
|
if (options.isNotEmpty()) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(IconSize.m)
|
||||||
|
.padding(Spacing.xs)
|
||||||
|
.onGloballyPositioned {
|
||||||
|
optionsOffset = it.positionInParent()
|
||||||
|
}
|
||||||
|
.onClick(
|
||||||
|
onClick = rememberCallback {
|
||||||
|
optionsExpanded = true
|
||||||
|
},
|
||||||
|
),
|
||||||
|
imageVector = Icons.Default.MoreHoriz,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = ancillaryColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
if (date.isNotEmpty()) {
|
if (date.isNotEmpty()) {
|
||||||
val buttonModifier = Modifier.size(IconSize.m).padding(3.5.dp)
|
val buttonModifier = Modifier.size(IconSize.m).padding(3.5.dp)
|
||||||
Icon(
|
Icon(
|
||||||
modifier = buttonModifier,
|
modifier = buttonModifier,
|
||||||
imageVector = Icons.Default.Schedule,
|
imageVector = Icons.Default.Schedule,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = textColor,
|
tint = ancillaryColor,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = date.prettifyDate(),
|
text = date.prettifyDate(),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = textColor,
|
color = ancillaryColor,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "",
|
text = "",
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CustomDropDown(
|
||||||
|
expanded = optionsExpanded,
|
||||||
|
onDismiss = {
|
||||||
|
optionsExpanded = false
|
||||||
|
},
|
||||||
|
offset = DpOffset(
|
||||||
|
x = optionsOffset.x.toLocalDp(),
|
||||||
|
y = optionsOffset.y.toLocalDp(),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
options.forEach { option ->
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
horizontal = Spacing.m,
|
||||||
|
vertical = Spacing.s,
|
||||||
|
).onClick(
|
||||||
|
onClick = rememberCallback {
|
||||||
|
optionsExpanded = false
|
||||||
|
onOptionSelected?.invoke(option.id)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
text = option.text,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.chat
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.CornerSize
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.shimmerEffect
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MessageCardPlaceholder() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(100.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(CornerSize.s))
|
||||||
|
.shimmerEffect()
|
||||||
|
)
|
||||||
|
}
|
@ -77,7 +77,6 @@ fun InboxReplySubtitle(
|
|||||||
val defaultDownVoteColor = MaterialTheme.colorScheme.tertiary
|
val defaultDownVoteColor = MaterialTheme.colorScheme.tertiary
|
||||||
var optionsExpanded by remember { mutableStateOf(false) }
|
var optionsExpanded by remember { mutableStateOf(false) }
|
||||||
var optionsOffset by remember { mutableStateOf(Offset.Zero) }
|
var optionsOffset by remember { mutableStateOf(Offset.Zero) }
|
||||||
val fullColor = MaterialTheme.colorScheme.onBackground
|
|
||||||
val ancillaryColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f)
|
val ancillaryColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository
|
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository
|
||||||
|
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePrivateMessageForm
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePrivateMessageForm
|
||||||
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.EditPrivateMessageForm
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.MarkPrivateMessageAsReadForm
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.MarkPrivateMessageAsReadForm
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider
|
import com.github.diegoberaldin.raccoonforlemmy.core.api.provider.ServiceProvider
|
||||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PrivateMessageModel
|
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PrivateMessageModel
|
||||||
@ -13,6 +14,7 @@ internal class DefaultPrivateMessageRepository(
|
|||||||
|
|
||||||
override suspend fun getAll(
|
override suspend fun getAll(
|
||||||
auth: String?,
|
auth: String?,
|
||||||
|
creatorId: Int?,
|
||||||
page: Int,
|
page: Int,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
unreadOnly: Boolean,
|
unreadOnly: Boolean,
|
||||||
@ -20,6 +22,7 @@ internal class DefaultPrivateMessageRepository(
|
|||||||
val response = services.privateMessages.getPrivateMessages(
|
val response = services.privateMessages.getPrivateMessages(
|
||||||
authHeader = auth.toAuthHeader(),
|
authHeader = auth.toAuthHeader(),
|
||||||
auth = auth,
|
auth = auth,
|
||||||
|
creatorId = creatorId,
|
||||||
limit = limit,
|
limit = limit,
|
||||||
page = page,
|
page = page,
|
||||||
unreadOnly = unreadOnly,
|
unreadOnly = unreadOnly,
|
||||||
@ -44,6 +47,18 @@ internal class DefaultPrivateMessageRepository(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun edit(messageId: Int, message: String, auth: String?) {
|
||||||
|
val data = EditPrivateMessageForm(
|
||||||
|
content = message,
|
||||||
|
auth = auth.orEmpty(),
|
||||||
|
privateMessageId = messageId,
|
||||||
|
)
|
||||||
|
services.privateMessages.editPrivateMessage(
|
||||||
|
authHeader = auth.toAuthHeader(),
|
||||||
|
form = data,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun markAsRead(
|
override suspend fun markAsRead(
|
||||||
messageId: Int,
|
messageId: Int,
|
||||||
auth: String?,
|
auth: String?,
|
||||||
|
@ -10,6 +10,7 @@ interface PrivateMessageRepository {
|
|||||||
|
|
||||||
suspend fun getAll(
|
suspend fun getAll(
|
||||||
auth: String? = null,
|
auth: String? = null,
|
||||||
|
creatorId: Int? = null,
|
||||||
page: Int,
|
page: Int,
|
||||||
limit: Int = DEFAULT_PAGE_SIZE,
|
limit: Int = DEFAULT_PAGE_SIZE,
|
||||||
unreadOnly: Boolean = true,
|
unreadOnly: Boolean = true,
|
||||||
@ -21,6 +22,12 @@ interface PrivateMessageRepository {
|
|||||||
recipiendId: Int,
|
recipiendId: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun edit(
|
||||||
|
messageId: Int,
|
||||||
|
message: String,
|
||||||
|
auth: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun markAsRead(
|
suspend fun markAsRead(
|
||||||
messageId: Int,
|
messageId: Int,
|
||||||
auth: String? = null,
|
auth: String? = null,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user