fix: post not updated in list when upvoted or downvoted from detail screen

This commit is contained in:
Diego Beraldin 2023-09-16 20:11:16 +02:00
parent c47bb2be0a
commit a7ca24bdea
11 changed files with 104 additions and 35 deletions

View File

@ -72,6 +72,7 @@ val commonUiModule = module {
postsRepository = get(), postsRepository = get(),
hapticFeedback = get(), hapticFeedback = get(),
keyStore = get(), keyStore = get(),
notificationCenter = get(),
) )
} }
factory { factory {

View File

@ -5,6 +5,7 @@ import com.github.diegoberaldin.racconforlemmy.core.utils.HapticFeedback
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
@ -180,7 +181,9 @@ class PostDetailViewModel(
post = post, post = post,
voted = newValue, voted = newValue,
) )
notificationCenter.send(NotificationCenter.Event.PostUpdate(newPost)) notificationCenter.getObserver(NotificationCenterContractKeys.PostUpdate)?.also {
it.invoke(newPost)
}
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
mvi.updateState { it.copy(post = post) } mvi.updateState { it.copy(post = post) }
@ -210,7 +213,9 @@ class PostDetailViewModel(
post = post, post = post,
downVoted = newValue, downVoted = newValue,
) )
notificationCenter.send(NotificationCenter.Event.PostUpdate(newPost)) notificationCenter.getObserver(NotificationCenterContractKeys.PostUpdate)?.also {
it.invoke(newPost)
}
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
mvi.updateState { it.copy(post = post) } mvi.updateState { it.copy(post = post) }
@ -239,7 +244,9 @@ class PostDetailViewModel(
post = post, post = post,
saved = newValue, saved = newValue,
) )
notificationCenter.send(NotificationCenter.Event.PostUpdate(newPost)) notificationCenter.getObserver(NotificationCenterContractKeys.PostUpdate)?.also {
it.invoke(newPost)
}
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
mvi.updateState { it.copy(post = post) } mvi.updateState { it.copy(post = post) }
@ -278,7 +285,6 @@ class PostDetailViewModel(
comment = comment, comment = comment,
voted = newValue, voted = newValue,
) )
notificationCenter.send(NotificationCenter.Event.CommentUpdate(newComment))
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
mvi.updateState { mvi.updateState {
@ -324,7 +330,6 @@ class PostDetailViewModel(
comment = comment, comment = comment,
downVoted = newValue, downVoted = newValue,
) )
notificationCenter.send(NotificationCenter.Event.CommentUpdate(newComment))
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
mvi.updateState { mvi.updateState {
@ -373,7 +378,6 @@ class PostDetailViewModel(
comment = comment, comment = comment,
saved = newValue, saved = newValue,
) )
notificationCenter.send(NotificationCenter.Event.CommentUpdate(newComment))
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
mvi.updateState { mvi.updateState {

View File

@ -4,6 +4,8 @@ import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.racconforlemmy.core.utils.HapticFeedback import com.github.diegoberaldin.racconforlemmy.core.utils.HapticFeedback
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
@ -24,13 +26,25 @@ class UserPostsViewModel(
private val postsRepository: PostsRepository, private val postsRepository: PostsRepository,
private val hapticFeedback: HapticFeedback, private val hapticFeedback: HapticFeedback,
private val keyStore: TemporaryKeyStore, private val keyStore: TemporaryKeyStore,
private val notificationCenter: NotificationCenter,
) : ScreenModel, ) : ScreenModel,
MviModel<UserPostsMviModel.Intent, UserPostsMviModel.UiState, UserPostsMviModel.Effect> by mvi { MviModel<UserPostsMviModel.Intent, UserPostsMviModel.UiState, UserPostsMviModel.Effect> by mvi {
private var currentPage: Int = 1 private var currentPage: Int = 1
fun finalize() {
notificationCenter.removeObserver(this::class.simpleName.orEmpty())
}
override fun onStarted() { override fun onStarted() {
mvi.onStarted() mvi.onStarted()
notificationCenter.addObserver({
(it as? PostModel)?.also { post ->
handlePostUpdate(post)
}
}, this::class.simpleName.orEmpty(), NotificationCenterContractKeys.PostUpdate)
mvi.scope.launch(Dispatchers.IO) { mvi.scope.launch(Dispatchers.IO) {
val user = userRepository.get(user.id) val user = userRepository.get(user.id)
if (user != null) { if (user != null) {
@ -248,4 +262,18 @@ class UserPostsViewModel(
} }
} }
} }
private fun handlePostUpdate(post: PostModel) {
mvi.updateState {
it.copy(
posts = it.posts.map { p ->
if (p.id == post.id) {
post
} else {
p
}
},
)
}
}
} }

View File

@ -19,6 +19,8 @@ object DefaultNotificationCenter : NotificationCenter {
} }
override fun addObserver(observer: (Any) -> Unit, key: String, contract: String) { override fun addObserver(observer: (Any) -> Unit, key: String, contract: String) {
removeObserver(key)
val mapKey = key to contract val mapKey = key to contract
registry[mapKey] = observer registry[mapKey] = observer
} }

View File

@ -1,7 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.core.notifications package com.github.diegoberaldin.raccoonforlemmy.core.notifications
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
/** /**
@ -13,8 +11,6 @@ interface NotificationCenter {
* Available event types. * Available event types.
*/ */
sealed interface Event { sealed interface Event {
data class PostUpdate(val post: PostModel) : Event
data class CommentUpdate(val comment: CommentModel) : Event
object Logout : Event object Logout : Event
} }

View File

@ -0,0 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.core.notifications
object NotificationCenterContractKeys {
const val PostUpdate = "postUpdate"
}

View File

@ -16,6 +16,7 @@ interface PostListMviModel :
data class UpVotePost(val index: Int, val feedback: Boolean = false) : Intent data class UpVotePost(val index: Int, val feedback: Boolean = false) : Intent
data class DownVotePost(val index: Int, val feedback: Boolean = false) : Intent data class DownVotePost(val index: Int, val feedback: Boolean = false) : Intent
data class SavePost(val index: Int, val feedback: Boolean = false) : Intent data class SavePost(val index: Int, val feedback: Boolean = false) : Intent
data class HandlePostUpdate(val post: PostModel) : Intent
object HapticIndication : Intent object HapticIndication : Intent
} }

View File

@ -61,6 +61,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDet
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.ListingType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.ListingType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
import com.github.diegoberaldin.raccoonforlemmy.feature.home.di.getHomeScreenModel import com.github.diegoberaldin.raccoonforlemmy.feature.home.di.getHomeScreenModel
import com.github.diegoberaldin.raccoonforlemmy.feature.home.ui.HomeTab import com.github.diegoberaldin.raccoonforlemmy.feature.home.ui.HomeTab

View File

@ -5,6 +5,7 @@ import com.github.diegoberaldin.racconforlemmy.core.utils.HapticFeedback
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
@ -35,6 +36,10 @@ class PostListViewModel(
private var currentPage: Int = 1 private var currentPage: Int = 1
fun finalize() {
notificationCenter.removeObserver(this::class.simpleName.orEmpty())
}
override fun reduce(intent: PostListMviModel.Intent) { override fun reduce(intent: PostListMviModel.Intent) {
when (intent) { when (intent) {
PostListMviModel.Intent.LoadNextPage -> loadNextPage() PostListMviModel.Intent.LoadNextPage -> loadNextPage()
@ -57,15 +62,13 @@ class PostListViewModel(
) )
PostListMviModel.Intent.HapticIndication -> hapticFeedback.vibrate() PostListMviModel.Intent.HapticIndication -> hapticFeedback.vibrate()
is PostListMviModel.Intent.HandlePostUpdate -> handlePostUpdate(intent.post)
} }
} }
override fun onDisposed() {
mvi.onDisposed()
}
override fun onStarted() { override fun onStarted() {
mvi.onStarted() mvi.onStarted()
val listingType = keyStore[KeyStoreKeys.DefaultListingType, 0].toListingType() val listingType = keyStore[KeyStoreKeys.DefaultListingType, 0].toListingType()
val sortType = keyStore[KeyStoreKeys.DefaultPostSortType, 0].toSortType() val sortType = keyStore[KeyStoreKeys.DefaultPostSortType, 0].toSortType()
mvi.updateState { mvi.updateState {
@ -77,33 +80,18 @@ class PostListViewModel(
) )
} }
notificationCenter.addObserver({
(it as? PostModel)?.also { post ->
handlePostUpdate(post)
}
}, this::class.simpleName.orEmpty(), NotificationCenterContractKeys.PostUpdate)
mvi.scope.launch(Dispatchers.Main) { mvi.scope.launch(Dispatchers.Main) {
identityRepository.authToken.map { !it.isNullOrEmpty() }.onEach { isLogged -> identityRepository.authToken.map { !it.isNullOrEmpty() }.onEach { isLogged ->
mvi.updateState { mvi.updateState {
it.copy(isLogged = isLogged) it.copy(isLogged = isLogged)
} }
}.launchIn(this) }.launchIn(this)
notificationCenter.events
.onEach { evt ->
when (evt) {
is NotificationCenter.Event.PostUpdate -> {
val newPost = evt.post
mvi.updateState {
it.copy(
posts = it.posts.map { p ->
if (p.id == newPost.id) {
newPost
} else {
p
}
},
)
}
}
else -> Unit
}
}.launchIn(this)
notificationCenter.events notificationCenter.events
.onEach { evt -> .onEach { evt ->
when (evt) { when (evt) {
@ -328,4 +316,18 @@ class PostListViewModel(
} }
} }
} }
private fun handlePostUpdate(post: PostModel) {
mvi.updateState {
it.copy(
posts = it.posts.map { p ->
if (p.id == post.id) {
post
} else {
p
}
},
)
}
}
} }

View File

@ -3,7 +3,10 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.
import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
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.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostsRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostsRepository
@ -18,14 +21,25 @@ class ProfilePostsViewModel(
private val savedOnly: Boolean = false, private val savedOnly: Boolean = false,
private val identityRepository: IdentityRepository, private val identityRepository: IdentityRepository,
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val notificationCenter: NotificationCenter,
) : ScreenModel, ) : ScreenModel,
MviModel<ProfilePostsMviModel.Intent, ProfilePostsMviModel.UiState, ProfilePostsMviModel.Effect> by mvi { MviModel<ProfilePostsMviModel.Intent, ProfilePostsMviModel.UiState, ProfilePostsMviModel.Effect> by mvi {
private var currentPage: Int = 1 private var currentPage: Int = 1
fun finalize() {
notificationCenter.removeObserver(this::class.simpleName.orEmpty())
}
override fun onStarted() { override fun onStarted() {
mvi.onStarted() mvi.onStarted()
notificationCenter.addObserver({
(it as? PostModel)?.also { post ->
handlePostUpdate(post)
}
}, this::class.simpleName.orEmpty(), NotificationCenterContractKeys.PostUpdate)
if (mvi.uiState.value.posts.isEmpty()) { if (mvi.uiState.value.posts.isEmpty()) {
refresh() refresh()
} }
@ -79,4 +93,18 @@ class ProfilePostsViewModel(
} }
} }
} }
private fun handlePostUpdate(post: PostModel) {
mvi.updateState {
it.copy(
posts = it.posts.map { p ->
if (p.id == post.id) {
post
} else {
p
}
},
)
}
}
} }

View File

@ -42,6 +42,7 @@ val profileTabModule = module {
savedOnly = params[1], savedOnly = params[1],
identityRepository = get(), identityRepository = get(),
userRepository = get(), userRepository = get(),
notificationCenter = get(),
) )
} }
factory { params -> factory { params ->