mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-01 22:26:56 +01:00
fix: avoid concurrency issue while posts are being marked as read before opening detail (#262)
* update PostListScreen * update CommunityDetailScreen * update FilteredContentsScreen * update MultiCommunityScreen * update UserDetailScreen * update ProfileLoggedScreen * update InboxChatScreen * update InboxMentionsScreen * update InboxRepliesScreen
This commit is contained in:
parent
9f4ebbcf38
commit
705c0893e2
@ -277,7 +277,7 @@ class InboxChatScreen(
|
||||
items(
|
||||
items = uiState.messages,
|
||||
key = {
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate) + it.read
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate)
|
||||
},
|
||||
) { message ->
|
||||
val isMyMessage = message.creator?.id == uiState.currentUserId
|
||||
|
@ -166,21 +166,19 @@ class InboxChatViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun markAsRead(
|
||||
private suspend fun markAsRead(
|
||||
read: Boolean,
|
||||
messageId: Long,
|
||||
) {
|
||||
val auth = identityRepository.authToken.value
|
||||
screenModelScope.launch {
|
||||
val newMessage =
|
||||
messageRepository.markAsRead(
|
||||
read = read,
|
||||
messageId = messageId,
|
||||
auth = auth,
|
||||
)
|
||||
if (newMessage != null) {
|
||||
handleMessageUpdate(newMessage)
|
||||
}
|
||||
val newMessage =
|
||||
messageRepository.markAsRead(
|
||||
read = read,
|
||||
messageId = messageId,
|
||||
auth = auth,
|
||||
)
|
||||
if (newMessage != null) {
|
||||
handleMessageUpdate(newMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,9 @@ interface CommunityDetailMviModel :
|
||||
val value: Boolean,
|
||||
) : Intent
|
||||
|
||||
data object WillOpenDetail : Intent
|
||||
data class WillOpenDetail(
|
||||
val id: Long,
|
||||
) : Intent
|
||||
|
||||
data object UnhideCommunity : Intent
|
||||
|
||||
@ -175,5 +177,9 @@ interface CommunityDetailMviModel :
|
||||
) : Effect
|
||||
|
||||
data object Back : Effect
|
||||
|
||||
data class OpenDetail(
|
||||
val post: PostModel,
|
||||
) : Effect
|
||||
}
|
||||
}
|
||||
|
@ -246,6 +246,8 @@ class CommunityDetailScreen(
|
||||
}
|
||||
|
||||
CommunityDetailMviModel.Effect.Back -> navigationCoordinator.popScreen()
|
||||
is CommunityDetailMviModel.Effect.OpenDetail ->
|
||||
detailOpener.openPostDetail(effect.post)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
@ -822,7 +824,7 @@ class CommunityDetailScreen(
|
||||
items(
|
||||
items = uiState.posts,
|
||||
key = {
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate) + it.read
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate)
|
||||
},
|
||||
) { post ->
|
||||
LaunchedEffect(post.id) {
|
||||
@ -977,11 +979,10 @@ class CommunityDetailScreen(
|
||||
meTagColor = uiState.meTagColor,
|
||||
onClick = {
|
||||
model.reduce(
|
||||
CommunityDetailMviModel.Intent.MarkAsRead(
|
||||
CommunityDetailMviModel.Intent.WillOpenDetail(
|
||||
post.id,
|
||||
),
|
||||
)
|
||||
model.reduce(CommunityDetailMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(
|
||||
post = post,
|
||||
otherInstance = otherInstanceName,
|
||||
@ -1027,11 +1028,10 @@ class CommunityDetailScreen(
|
||||
onReply =
|
||||
{
|
||||
model.reduce(
|
||||
CommunityDetailMviModel.Intent.MarkAsRead(
|
||||
CommunityDetailMviModel.Intent.WillOpenDetail(
|
||||
post.id,
|
||||
),
|
||||
)
|
||||
model.reduce(CommunityDetailMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
}.takeIf { uiState.isLogged && !isOnOtherInstance },
|
||||
onOpenImage = { url ->
|
||||
|
@ -299,8 +299,9 @@ class CommunityDetailViewModel(
|
||||
|
||||
CommunityDetailMviModel.Intent.Block -> blockCommunity()
|
||||
CommunityDetailMviModel.Intent.BlockInstance -> blockInstance()
|
||||
is CommunityDetailMviModel.Intent.MarkAsRead -> {
|
||||
markAsRead(uiState.value.posts.first { it.id == intent.id })
|
||||
is CommunityDetailMviModel.Intent.MarkAsRead ->
|
||||
screenModelScope.launch {
|
||||
markAsRead(uiState.value.posts.first { it.id == intent.id })
|
||||
}
|
||||
|
||||
CommunityDetailMviModel.Intent.ClearRead -> clearRead()
|
||||
@ -367,9 +368,15 @@ class CommunityDetailViewModel(
|
||||
|
||||
is CommunityDetailMviModel.Intent.SetSearch -> updateSearchText(intent.value)
|
||||
|
||||
CommunityDetailMviModel.Intent.WillOpenDetail -> {
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
is CommunityDetailMviModel.Intent.WillOpenDetail ->
|
||||
screenModelScope.launch {
|
||||
uiState.value.posts
|
||||
.firstOrNull { it.id == intent.id }
|
||||
?.also { post ->
|
||||
markAsRead(post)
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
}
|
||||
}
|
||||
|
||||
CommunityDetailMviModel.Intent.UnhideCommunity -> {
|
||||
@ -552,24 +559,22 @@ class CommunityDetailViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun markAsRead(post: PostModel) {
|
||||
private suspend fun markAsRead(post: PostModel) {
|
||||
if (post.read) {
|
||||
return
|
||||
}
|
||||
val newPost = post.copy(read = true)
|
||||
screenModelScope.launch {
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postRepository.setRead(
|
||||
read = true,
|
||||
postId = post.id,
|
||||
auth = auth,
|
||||
)
|
||||
handlePostUpdate(newPost)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
handlePostUpdate(post)
|
||||
}
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postRepository.setRead(
|
||||
read = true,
|
||||
postId = post.id,
|
||||
auth = auth,
|
||||
)
|
||||
handlePostUpdate(newPost)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
handlePostUpdate(post)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,10 @@ interface FilteredContentsMviModel :
|
||||
val commentId: Long,
|
||||
) : Intent
|
||||
|
||||
data object WillOpenDetail : Intent
|
||||
data class WillOpenDetail(
|
||||
val postId: Long,
|
||||
val commentId: Long? = null,
|
||||
) : Intent
|
||||
}
|
||||
|
||||
data class State(
|
||||
@ -136,5 +139,10 @@ interface FilteredContentsMviModel :
|
||||
|
||||
sealed interface Effect {
|
||||
data object BackToTop : Effect
|
||||
|
||||
data class OpenDetail(
|
||||
val postId: Long,
|
||||
val commentId: Long? = null,
|
||||
) : Effect
|
||||
}
|
||||
}
|
||||
|
@ -154,6 +154,13 @@ class FilteredContentsScreen(
|
||||
topAppBarState.contentOffset = 0f
|
||||
}
|
||||
}
|
||||
|
||||
is FilteredContentsMviModel.Effect.OpenDetail ->
|
||||
detailOpener.openPostDetail(
|
||||
post = PostModel(id = effect.postId),
|
||||
highlightCommentId = effect.commentId,
|
||||
isMod = true,
|
||||
)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
@ -369,7 +376,7 @@ class FilteredContentsScreen(
|
||||
items(
|
||||
items = uiState.posts,
|
||||
key = {
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate) + it.read
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate)
|
||||
},
|
||||
) { post ->
|
||||
|
||||
@ -482,8 +489,11 @@ class FilteredContentsScreen(
|
||||
botTagColor = uiState.botTagColor,
|
||||
meTagColor = uiState.meTagColor,
|
||||
onClick = {
|
||||
model.reduce(FilteredContentsMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(
|
||||
FilteredContentsMviModel.Intent.WillOpenDetail(
|
||||
postId = post.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onOpenCommunity = { community, instance ->
|
||||
detailOpener.openCommunityDetail(
|
||||
@ -512,8 +522,11 @@ class FilteredContentsScreen(
|
||||
)
|
||||
},
|
||||
onReply = {
|
||||
model.reduce(FilteredContentsMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(
|
||||
FilteredContentsMviModel.Intent.WillOpenDetail(
|
||||
postId = post.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onOpenImage = { url ->
|
||||
navigationCoordinator.pushScreen(
|
||||
@ -820,11 +833,11 @@ class FilteredContentsScreen(
|
||||
detailOpener.openUserDetail(user, instance)
|
||||
},
|
||||
onOpen = {
|
||||
model.reduce(FilteredContentsMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(
|
||||
post = PostModel(id = comment.postId),
|
||||
highlightCommentId = comment.id,
|
||||
isMod = true,
|
||||
model.reduce(
|
||||
FilteredContentsMviModel.Intent.WillOpenDetail(
|
||||
postId = comment.postId,
|
||||
commentId = comment.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onUpVote = {
|
||||
|
@ -212,10 +212,19 @@ class FilteredContentsViewModel(
|
||||
distinguish(comment)
|
||||
}
|
||||
|
||||
FilteredContentsMviModel.Intent.WillOpenDetail -> {
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
}
|
||||
is FilteredContentsMviModel.Intent.WillOpenDetail ->
|
||||
screenModelScope.launch {
|
||||
if (intent.commentId == null) {
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
}
|
||||
emitEffect(
|
||||
FilteredContentsMviModel.Effect.OpenDetail(
|
||||
postId = intent.postId,
|
||||
commentId = intent.commentId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import com.livefast.eattrash.raccoonforlemmy.core.appearance.data.VoteFormat
|
||||
import com.livefast.eattrash.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.ActionOnSwipe
|
||||
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.data.PersonMentionModel
|
||||
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||
|
||||
@Stable
|
||||
interface InboxMentionsMviModel :
|
||||
@ -31,6 +32,12 @@ interface InboxMentionsMviModel :
|
||||
data class DownVoteComment(
|
||||
val id: Long,
|
||||
) : Intent
|
||||
|
||||
data class WillOpenDetail(
|
||||
val id: Long,
|
||||
val post: PostModel,
|
||||
val commentId: Long,
|
||||
) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
@ -58,5 +65,10 @@ interface InboxMentionsMviModel :
|
||||
) : Effect
|
||||
|
||||
data object BackToTop : Effect
|
||||
|
||||
data class OpenDetail(
|
||||
val post: PostModel,
|
||||
val commentId: Long,
|
||||
) : Effect
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,12 @@ class InboxMentionsScreen : Tab {
|
||||
lazyListState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
|
||||
is InboxMentionsMviModel.Effect.OpenDetail ->
|
||||
detailOpener.openPostDetail(
|
||||
post = effect.post,
|
||||
highlightCommentId = effect.commentId,
|
||||
)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
@ -138,7 +144,7 @@ class InboxMentionsScreen : Tab {
|
||||
}
|
||||
items(
|
||||
items = uiState.mentions,
|
||||
key = { it.id.toString() + it.read + uiState.unreadOnly },
|
||||
key = { it.id.toString() + uiState.unreadOnly },
|
||||
) { mention ->
|
||||
@Composable
|
||||
fun List<ActionOnSwipe>.toSwipeActions(): List<SwipeAction> =
|
||||
@ -233,18 +239,12 @@ class InboxMentionsScreen : Tab {
|
||||
downVoteEnabled = uiState.downVoteEnabled,
|
||||
previewMaxLines = uiState.previewMaxLines,
|
||||
onClick = { post ->
|
||||
if (!mention.read) {
|
||||
model.reduce(
|
||||
InboxMentionsMviModel.Intent.MarkAsRead(
|
||||
read = true,
|
||||
id = mention.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
detailOpener.openPostDetail(
|
||||
post = post,
|
||||
highlightCommentId = mention.comment.id,
|
||||
otherInstance = "",
|
||||
model.reduce(
|
||||
InboxMentionsMviModel.Intent.WillOpenDetail(
|
||||
id = mention.id,
|
||||
post = post,
|
||||
commentId = mention.comment.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onOpenCreator = { user, instance ->
|
||||
|
@ -101,13 +101,14 @@ class InboxMentionsViewModel(
|
||||
emitEffect(InboxMentionsMviModel.Effect.BackToTop)
|
||||
}
|
||||
|
||||
is InboxMentionsMviModel.Intent.MarkAsRead -> {
|
||||
val mention = uiState.value.mentions.first { it.id == intent.id }
|
||||
markAsRead(
|
||||
read = intent.read,
|
||||
mention = mention,
|
||||
)
|
||||
}
|
||||
is InboxMentionsMviModel.Intent.MarkAsRead ->
|
||||
screenModelScope.launch {
|
||||
val mention = uiState.value.mentions.first { it.id == intent.id }
|
||||
markAsRead(
|
||||
read = intent.read,
|
||||
mention = mention,
|
||||
)
|
||||
}
|
||||
|
||||
InboxMentionsMviModel.Intent.HapticIndication -> hapticFeedback.vibrate()
|
||||
is InboxMentionsMviModel.Intent.DownVoteComment -> {
|
||||
@ -119,6 +120,24 @@ class InboxMentionsViewModel(
|
||||
val mention = uiState.value.mentions.first { it.id == intent.id }
|
||||
toggleUpVoteComment(mention)
|
||||
}
|
||||
|
||||
is InboxMentionsMviModel.Intent.WillOpenDetail ->
|
||||
screenModelScope.launch {
|
||||
uiState.value.mentions.firstOrNull { it.id == intent.id }?.also { mention ->
|
||||
if (!mention.read) {
|
||||
markAsRead(
|
||||
mention = mention,
|
||||
read = true,
|
||||
)
|
||||
}
|
||||
emitEffect(
|
||||
InboxMentionsMviModel.Effect.OpenDetail(
|
||||
post = intent.post,
|
||||
commentId = intent.commentId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,33 +221,31 @@ class InboxMentionsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun markAsRead(
|
||||
private suspend fun markAsRead(
|
||||
read: Boolean,
|
||||
mention: PersonMentionModel,
|
||||
) {
|
||||
val auth = identityRepository.authToken.value
|
||||
screenModelScope.launch {
|
||||
userRepository.setMentionRead(
|
||||
read = read,
|
||||
mentionId = mention.id,
|
||||
auth = auth,
|
||||
)
|
||||
val currentState = uiState.value
|
||||
if (read && currentState.unreadOnly) {
|
||||
updateState {
|
||||
it.copy(
|
||||
mentions =
|
||||
currentState.mentions.filter { m ->
|
||||
m.id != mention.id
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val newMention = mention.copy(read = read)
|
||||
handleItemUpdate(newMention)
|
||||
userRepository.setMentionRead(
|
||||
read = read,
|
||||
mentionId = mention.id,
|
||||
auth = auth,
|
||||
)
|
||||
val currentState = uiState.value
|
||||
if (read && currentState.unreadOnly) {
|
||||
updateState {
|
||||
it.copy(
|
||||
mentions =
|
||||
currentState.mentions.filter { m ->
|
||||
m.id != mention.id
|
||||
},
|
||||
)
|
||||
}
|
||||
updateUnreadItems()
|
||||
} else {
|
||||
val newMention = mention.copy(read = read)
|
||||
handleItemUpdate(newMention)
|
||||
}
|
||||
updateUnreadItems()
|
||||
}
|
||||
|
||||
private fun toggleUpVoteComment(mention: PersonMentionModel) {
|
||||
|
@ -48,7 +48,9 @@ interface MultiCommunityMviModel :
|
||||
val url: String,
|
||||
) : Intent
|
||||
|
||||
data object WillOpenDetail : Intent
|
||||
data class WillOpenDetail(
|
||||
val id: Long,
|
||||
) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
@ -83,5 +85,9 @@ interface MultiCommunityMviModel :
|
||||
|
||||
sealed interface Effect {
|
||||
data object BackToTop : Effect
|
||||
|
||||
data class OpenDetail(
|
||||
val post: PostModel,
|
||||
) : Effect
|
||||
}
|
||||
}
|
||||
|
@ -138,6 +138,9 @@ class MultiCommunityScreen(
|
||||
topAppBarState.contentOffset = 0f
|
||||
}
|
||||
}
|
||||
|
||||
is MultiCommunityMviModel.Effect.OpenDetail ->
|
||||
detailOpener.openPostDetail(post = effect.post)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
@ -282,7 +285,7 @@ class MultiCommunityScreen(
|
||||
items(
|
||||
items = uiState.posts,
|
||||
key = {
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate) + it.read
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate)
|
||||
},
|
||||
) { post ->
|
||||
LaunchedEffect(post.id) {
|
||||
@ -404,9 +407,9 @@ class MultiCommunityScreen(
|
||||
botTagColor = uiState.botTagColor,
|
||||
meTagColor = uiState.meTagColor,
|
||||
onClick = {
|
||||
model.reduce(MultiCommunityMviModel.Intent.MarkAsRead(post.id))
|
||||
model.reduce(MultiCommunityMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(
|
||||
MultiCommunityMviModel.Intent.WillOpenDetail(post.id),
|
||||
)
|
||||
},
|
||||
onDoubleClick =
|
||||
{
|
||||
@ -439,8 +442,9 @@ class MultiCommunityScreen(
|
||||
)
|
||||
},
|
||||
onReply = {
|
||||
model.reduce(MultiCommunityMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(
|
||||
MultiCommunityMviModel.Intent.WillOpenDetail(post.id),
|
||||
)
|
||||
},
|
||||
onOpenImage = { url ->
|
||||
model.reduce(MultiCommunityMviModel.Intent.MarkAsRead(post.id))
|
||||
|
@ -191,19 +191,26 @@ class MultiCommunityViewModel(
|
||||
|
||||
MultiCommunityMviModel.Intent.ClearRead -> clearRead()
|
||||
is MultiCommunityMviModel.Intent.MarkAsRead ->
|
||||
markAsRead(
|
||||
post = uiState.value.posts.first { it.id == intent.id },
|
||||
)
|
||||
screenModelScope.launch {
|
||||
markAsRead(
|
||||
post = uiState.value.posts.first { it.id == intent.id },
|
||||
)
|
||||
}
|
||||
|
||||
is MultiCommunityMviModel.Intent.Hide ->
|
||||
hide(
|
||||
post = uiState.value.posts.first { it.id == intent.id },
|
||||
)
|
||||
|
||||
MultiCommunityMviModel.Intent.WillOpenDetail -> {
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
}
|
||||
is MultiCommunityMviModel.Intent.WillOpenDetail ->
|
||||
screenModelScope.launch {
|
||||
uiState.value.posts.firstOrNull { it.id == intent.id }?.also { post ->
|
||||
markAsRead(post)
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
emitEffect(MultiCommunityMviModel.Effect.OpenDetail(post))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,24 +321,22 @@ class MultiCommunityViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun markAsRead(post: PostModel) {
|
||||
private suspend fun markAsRead(post: PostModel) {
|
||||
if (post.read) {
|
||||
return
|
||||
}
|
||||
val newPost = post.copy(read = true)
|
||||
screenModelScope.launch {
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postRepository.setRead(
|
||||
read = true,
|
||||
postId = post.id,
|
||||
auth = auth,
|
||||
)
|
||||
handlePostUpdate(newPost)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
handlePostUpdate(post)
|
||||
}
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postRepository.setRead(
|
||||
read = true,
|
||||
postId = post.id,
|
||||
auth = auth,
|
||||
)
|
||||
handlePostUpdate(newPost)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
handlePostUpdate(post)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,10 @@ interface ProfileLoggedMviModel :
|
||||
val feedback: Boolean = false,
|
||||
) : Intent
|
||||
|
||||
data object WillOpenDetail : Intent
|
||||
data class WillOpenDetail(
|
||||
val postId: Long,
|
||||
val commentId: Long? = null,
|
||||
) : Intent
|
||||
|
||||
data class RestorePost(
|
||||
val id: Long,
|
||||
@ -96,5 +99,10 @@ interface ProfileLoggedMviModel :
|
||||
val isModerator: Boolean = false,
|
||||
)
|
||||
|
||||
sealed interface Effect
|
||||
sealed interface Effect {
|
||||
data class OpenDetail(
|
||||
val postId: Long,
|
||||
val commentId: Long? = null,
|
||||
) : Effect
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,18 @@ object ProfileLoggedScreen : Tab {
|
||||
model.reduce(ProfileLoggedMviModel.Intent.Refresh)
|
||||
}.launchIn(this)
|
||||
}
|
||||
LaunchedEffect(model) {
|
||||
model.effects
|
||||
.onEach { effect ->
|
||||
when (effect) {
|
||||
is ProfileLoggedMviModel.Effect.OpenDetail ->
|
||||
detailOpener.openPostDetail(
|
||||
post = PostModel(id = effect.postId),
|
||||
highlightCommentId = effect.commentId,
|
||||
)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
if (uiState.initial) {
|
||||
ProgressHud()
|
||||
@ -277,7 +289,7 @@ object ProfileLoggedScreen : Tab {
|
||||
items(
|
||||
items = uiState.posts,
|
||||
key = {
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate) + it.read
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate)
|
||||
},
|
||||
) { post ->
|
||||
PostCard(
|
||||
@ -295,12 +307,18 @@ object ProfileLoggedScreen : Tab {
|
||||
blurNsfw = false,
|
||||
downVoteEnabled = uiState.downVoteEnabled,
|
||||
onClick = {
|
||||
model.reduce(ProfileLoggedMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(
|
||||
ProfileLoggedMviModel.Intent.WillOpenDetail(
|
||||
postId = post.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onReply = {
|
||||
model.reduce(ProfileLoggedMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(
|
||||
ProfileLoggedMviModel.Intent.WillOpenDetail(
|
||||
postId = post.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onOpenCommunity = { community, instance ->
|
||||
detailOpener.openCommunityDetail(community, instance)
|
||||
@ -494,9 +512,11 @@ object ProfileLoggedScreen : Tab {
|
||||
detailOpener.openCommunityDetail(community, instance)
|
||||
},
|
||||
onClick = {
|
||||
detailOpener.openPostDetail(
|
||||
post = PostModel(id = comment.postId),
|
||||
highlightCommentId = comment.id,
|
||||
model.reduce(
|
||||
ProfileLoggedMviModel.Intent.WillOpenDetail(
|
||||
postId = comment.postId,
|
||||
commentId = comment.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onReply = {
|
||||
|
@ -217,9 +217,18 @@ class ProfileLoggedViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
ProfileLoggedMviModel.Intent.WillOpenDetail -> {
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
is ProfileLoggedMviModel.Intent.WillOpenDetail ->
|
||||
screenModelScope.launch {
|
||||
if (intent.commentId == null) {
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
}
|
||||
emitEffect(
|
||||
ProfileLoggedMviModel.Effect.OpenDetail(
|
||||
postId = intent.postId,
|
||||
commentId = intent.commentId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
is ProfileLoggedMviModel.Intent.RestorePost -> {
|
||||
|
@ -66,7 +66,9 @@ interface PostListMviModel :
|
||||
|
||||
data object PauseZombieMode : Intent
|
||||
|
||||
data object WillOpenDetail : Intent
|
||||
data class WillOpenDetail(
|
||||
val id: Long,
|
||||
) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
@ -107,5 +109,9 @@ interface PostListMviModel :
|
||||
data class ZombieModeTick(
|
||||
val index: Int,
|
||||
) : Effect
|
||||
|
||||
data class OpenDetail(
|
||||
val post: PostModel,
|
||||
) : Effect
|
||||
}
|
||||
}
|
||||
|
@ -188,6 +188,9 @@ class PostListScreen : Screen {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is PostListMviModel.Effect.OpenDetail ->
|
||||
detailOpener.openPostDetail(effect.post)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
@ -361,7 +364,7 @@ class PostListScreen : Screen {
|
||||
key = {
|
||||
it.id.toString() + (
|
||||
it.updateDate ?: it.publishDate
|
||||
) + it.read + uiState.isLogged
|
||||
) + uiState.isLogged
|
||||
},
|
||||
) { post ->
|
||||
LaunchedEffect(post.id) {
|
||||
@ -504,9 +507,7 @@ class PostListScreen : Screen {
|
||||
botTagColor = uiState.botTagColor,
|
||||
meTagColor = uiState.meTagColor,
|
||||
onClick = {
|
||||
model.reduce(PostListMviModel.Intent.MarkAsRead(post.id))
|
||||
model.reduce(PostListMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(PostListMviModel.Intent.WillOpenDetail(post.id))
|
||||
},
|
||||
onDoubleClick =
|
||||
{
|
||||
@ -549,9 +550,9 @@ class PostListScreen : Screen {
|
||||
},
|
||||
onReply = {
|
||||
if (uiState.isLogged) {
|
||||
model.reduce(PostListMviModel.Intent.MarkAsRead(post.id))
|
||||
model.reduce(PostListMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(
|
||||
PostListMviModel.Intent.WillOpenDetail(post.id),
|
||||
)
|
||||
}
|
||||
},
|
||||
onOpenImage = { url ->
|
||||
|
@ -268,11 +268,12 @@ class PostListViewModel(
|
||||
shareHelper.share(intent.url)
|
||||
}
|
||||
|
||||
is PostListMviModel.Intent.MarkAsRead -> {
|
||||
uiState.value.posts.firstOrNull { it.id == intent.id }?.also { post ->
|
||||
markAsRead(post = post)
|
||||
is PostListMviModel.Intent.MarkAsRead ->
|
||||
screenModelScope.launch {
|
||||
uiState.value.posts.firstOrNull { it.id == intent.id }?.also { post ->
|
||||
markAsRead(post = post)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PostListMviModel.Intent.ClearRead -> clearRead()
|
||||
is PostListMviModel.Intent.Hide -> {
|
||||
@ -298,9 +299,15 @@ class PostListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
PostListMviModel.Intent.WillOpenDetail -> {
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
is PostListMviModel.Intent.WillOpenDetail -> {
|
||||
screenModelScope.launch {
|
||||
uiState.value.posts.firstOrNull { it.id == intent.id }?.also { post ->
|
||||
markAsRead(post)
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
emitEffect(PostListMviModel.Effect.OpenDetail(post))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -427,24 +434,22 @@ class PostListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun markAsRead(post: PostModel) {
|
||||
private suspend fun markAsRead(post: PostModel) {
|
||||
if (post.read) {
|
||||
return
|
||||
}
|
||||
val newPost = post.copy(read = true)
|
||||
screenModelScope.launch {
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postRepository.setRead(
|
||||
read = true,
|
||||
postId = post.id,
|
||||
auth = auth,
|
||||
)
|
||||
handlePostUpdate(newPost)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
handlePostUpdate(post)
|
||||
}
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postRepository.setRead(
|
||||
read = true,
|
||||
postId = post.id,
|
||||
auth = auth,
|
||||
)
|
||||
handlePostUpdate(newPost)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
handlePostUpdate(post)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import com.livefast.eattrash.raccoonforlemmy.core.appearance.data.VoteFormat
|
||||
import com.livefast.eattrash.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.ActionOnSwipe
|
||||
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.data.PersonMentionModel
|
||||
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||
|
||||
@Stable
|
||||
interface InboxRepliesMviModel :
|
||||
@ -31,6 +32,12 @@ interface InboxRepliesMviModel :
|
||||
data class DownVoteComment(
|
||||
val id: Long,
|
||||
) : Intent
|
||||
|
||||
data class WillOpenDetail(
|
||||
val id: Long,
|
||||
val post: PostModel,
|
||||
val commentId: Long,
|
||||
) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
@ -58,5 +65,10 @@ interface InboxRepliesMviModel :
|
||||
) : Effect
|
||||
|
||||
data object BackToTop : Effect
|
||||
|
||||
data class OpenDetail(
|
||||
val post: PostModel,
|
||||
val commentId: Long,
|
||||
) : Effect
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,12 @@ class InboxRepliesScreen : Tab {
|
||||
lazyListState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
|
||||
is InboxRepliesMviModel.Effect.OpenDetail ->
|
||||
detailOpener.openPostDetail(
|
||||
post = effect.post,
|
||||
highlightCommentId = effect.commentId,
|
||||
)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
@ -138,7 +144,7 @@ class InboxRepliesScreen : Tab {
|
||||
}
|
||||
items(
|
||||
items = uiState.replies,
|
||||
key = { it.id.toString() + it.read + uiState.unreadOnly },
|
||||
key = { it.id.toString() + uiState.unreadOnly },
|
||||
) { reply ->
|
||||
|
||||
@Composable
|
||||
@ -234,17 +240,12 @@ class InboxRepliesScreen : Tab {
|
||||
downVoteEnabled = uiState.downVoteEnabled,
|
||||
previewMaxLines = uiState.previewMaxLines,
|
||||
onClick = { post ->
|
||||
if (!reply.read) {
|
||||
model.reduce(
|
||||
InboxRepliesMviModel.Intent.MarkAsRead(
|
||||
read = true,
|
||||
id = reply.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
detailOpener.openPostDetail(
|
||||
post = post,
|
||||
highlightCommentId = reply.comment.id,
|
||||
model.reduce(
|
||||
InboxRepliesMviModel.Intent.WillOpenDetail(
|
||||
id = reply.id,
|
||||
post = post,
|
||||
commentId = reply.comment.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onOpenCreator = { user, instance ->
|
||||
|
@ -101,10 +101,11 @@ class InboxRepliesViewModel(
|
||||
emitEffect(InboxRepliesMviModel.Effect.BackToTop)
|
||||
}
|
||||
|
||||
is InboxRepliesMviModel.Intent.MarkAsRead -> {
|
||||
val reply = uiState.value.replies.first { it.id == intent.id }
|
||||
markAsRead(read = intent.read, reply = reply)
|
||||
}
|
||||
is InboxRepliesMviModel.Intent.MarkAsRead ->
|
||||
screenModelScope.launch {
|
||||
val reply = uiState.value.replies.first { it.id == intent.id }
|
||||
markAsRead(read = intent.read, reply = reply)
|
||||
}
|
||||
|
||||
InboxRepliesMviModel.Intent.HapticIndication -> hapticFeedback.vibrate()
|
||||
is InboxRepliesMviModel.Intent.DownVoteComment -> {
|
||||
@ -116,6 +117,24 @@ class InboxRepliesViewModel(
|
||||
val reply = uiState.value.replies.first { it.id == intent.id }
|
||||
toggleUpVoteComment(reply)
|
||||
}
|
||||
|
||||
is InboxRepliesMviModel.Intent.WillOpenDetail ->
|
||||
screenModelScope.launch {
|
||||
uiState.value.replies.firstOrNull { it.id == intent.id }?.also { reply ->
|
||||
if (!reply.read) {
|
||||
markAsRead(
|
||||
reply = reply,
|
||||
read = true,
|
||||
)
|
||||
}
|
||||
emitEffect(
|
||||
InboxRepliesMviModel.Effect.OpenDetail(
|
||||
post = intent.post,
|
||||
commentId = intent.commentId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,33 +218,31 @@ class InboxRepliesViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun markAsRead(
|
||||
private suspend fun markAsRead(
|
||||
read: Boolean,
|
||||
reply: PersonMentionModel,
|
||||
) {
|
||||
val auth = identityRepository.authToken.value
|
||||
screenModelScope.launch {
|
||||
userRepository.setReplyRead(
|
||||
read = read,
|
||||
replyId = reply.id,
|
||||
auth = auth,
|
||||
)
|
||||
val currentState = uiState.value
|
||||
if (read && currentState.unreadOnly) {
|
||||
updateState {
|
||||
it.copy(
|
||||
replies =
|
||||
currentState.replies.filter { r ->
|
||||
r.id != reply.id
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val newItem = reply.copy(read = read)
|
||||
handleItemUpdate(newItem)
|
||||
userRepository.setReplyRead(
|
||||
read = read,
|
||||
replyId = reply.id,
|
||||
auth = auth,
|
||||
)
|
||||
val currentState = uiState.value
|
||||
if (read && currentState.unreadOnly) {
|
||||
updateState {
|
||||
it.copy(
|
||||
replies =
|
||||
currentState.replies.filter { r ->
|
||||
r.id != reply.id
|
||||
},
|
||||
)
|
||||
}
|
||||
updateUnreadItems()
|
||||
} else {
|
||||
val newItem = reply.copy(read = read)
|
||||
handleItemUpdate(newItem)
|
||||
}
|
||||
updateUnreadItems()
|
||||
}
|
||||
|
||||
private fun toggleUpVoteComment(mention: PersonMentionModel) {
|
||||
|
@ -66,7 +66,10 @@ interface UserDetailMviModel :
|
||||
|
||||
data object BlockInstance : Intent
|
||||
|
||||
data object WillOpenDetail : Intent
|
||||
data class WillOpenDetail(
|
||||
val postId: Long,
|
||||
val commentId: Long? = null,
|
||||
) : Intent
|
||||
|
||||
data class UpdateTags(
|
||||
val ids: List<Long>,
|
||||
@ -122,5 +125,10 @@ interface UserDetailMviModel :
|
||||
) : Effect
|
||||
|
||||
data object BackToTop : Effect
|
||||
|
||||
data class OpenDetail(
|
||||
val postId: Long,
|
||||
val commentId: Long? = null,
|
||||
) : Effect
|
||||
}
|
||||
}
|
||||
|
@ -197,6 +197,12 @@ class UserDetailScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is UserDetailMviModel.Effect.OpenDetail ->
|
||||
detailOpener.openPostDetail(
|
||||
post = PostModel(id = effect.postId),
|
||||
highlightCommentId = effect.commentId,
|
||||
)
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
@ -546,7 +552,7 @@ class UserDetailScreen(
|
||||
items(
|
||||
items = uiState.posts,
|
||||
key = {
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate) + it.read
|
||||
it.id.toString() + (it.updateDate ?: it.publishDate)
|
||||
},
|
||||
) { post ->
|
||||
|
||||
@ -665,8 +671,11 @@ class UserDetailScreen(
|
||||
actionButtonsActive = uiState.isLogged,
|
||||
downVoteEnabled = uiState.downVoteEnabled,
|
||||
onClick = {
|
||||
model.reduce(UserDetailMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(
|
||||
UserDetailMviModel.Intent.WillOpenDetail(
|
||||
postId = post.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onDoubleClick =
|
||||
{
|
||||
@ -709,8 +718,11 @@ class UserDetailScreen(
|
||||
},
|
||||
onReply =
|
||||
{
|
||||
model.reduce(UserDetailMviModel.Intent.WillOpenDetail)
|
||||
detailOpener.openPostDetail(post)
|
||||
model.reduce(
|
||||
UserDetailMviModel.Intent.WillOpenDetail(
|
||||
postId = post.id,
|
||||
),
|
||||
)
|
||||
}.takeIf { uiState.isLogged && !isOnOtherInstance },
|
||||
onOpenImage = { url ->
|
||||
navigationCoordinator.pushScreen(
|
||||
@ -969,9 +981,11 @@ class UserDetailScreen(
|
||||
downVoteEnabled = uiState.downVoteEnabled,
|
||||
actionButtonsActive = uiState.isLogged,
|
||||
onClick = {
|
||||
detailOpener.openPostDetail(
|
||||
post = PostModel(id = comment.postId),
|
||||
highlightCommentId = comment.id,
|
||||
model.reduce(
|
||||
UserDetailMviModel.Intent.WillOpenDetail(
|
||||
postId = comment.postId,
|
||||
commentId = comment.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onImageClick = { url ->
|
||||
|
@ -308,10 +308,19 @@ class UserDetailViewModel(
|
||||
UserDetailMviModel.Intent.Block -> blockUser()
|
||||
UserDetailMviModel.Intent.BlockInstance -> blockInstance()
|
||||
|
||||
UserDetailMviModel.Intent.WillOpenDetail -> {
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
}
|
||||
is UserDetailMviModel.Intent.WillOpenDetail ->
|
||||
screenModelScope.launch {
|
||||
if (intent.commentId == null) {
|
||||
val state = postPaginationManager.extractState()
|
||||
postNavigationManager.push(state)
|
||||
}
|
||||
emitEffect(
|
||||
UserDetailMviModel.Effect.OpenDetail(
|
||||
postId = intent.postId,
|
||||
commentId = intent.commentId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
is UserDetailMviModel.Intent.AddUserTag ->
|
||||
addUserTag(name = intent.name, color = intent.color)
|
||||
|
Loading…
x
Reference in New Issue
Block a user