fix: 部分页面重复 key 导致闪退
This commit is contained in:
parent
cad7464e2b
commit
633b17f91c
|
|
@ -7,6 +7,8 @@ import com.huanchengfly.tieba.post.api.models.protos.personalized.ThreadPersonal
|
||||||
import com.huanchengfly.tieba.post.arch.ImmutableHolder
|
import com.huanchengfly.tieba.post.arch.ImmutableHolder
|
||||||
import com.huanchengfly.tieba.post.utils.BlockManager.shouldBlock
|
import com.huanchengfly.tieba.post.utils.BlockManager.shouldBlock
|
||||||
import com.huanchengfly.tieba.post.utils.appPreferences
|
import com.huanchengfly.tieba.post.utils.appPreferences
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ThreadItemData(
|
data class ThreadItemData(
|
||||||
|
|
@ -14,4 +16,12 @@ data class ThreadItemData(
|
||||||
val blocked: Boolean = thread.get { shouldBlock() },
|
val blocked: Boolean = thread.get { shouldBlock() },
|
||||||
val personalized: ImmutableHolder<ThreadPersonalized>? = null,
|
val personalized: ImmutableHolder<ThreadPersonalized>? = null,
|
||||||
val hidden: Boolean = blocked && App.INSTANCE.appPreferences.hideBlockedContent,
|
val hidden: Boolean = blocked && App.INSTANCE.appPreferences.hideBlockedContent,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun List<ThreadItemData>.distinctById(): ImmutableList<ThreadItemData> {
|
||||||
|
return distinctBy {
|
||||||
|
it.thread.get {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}.toImmutableList()
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ import com.huanchengfly.tieba.post.arch.UiState
|
||||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||||
import com.huanchengfly.tieba.post.repository.FrsPageRepository
|
import com.huanchengfly.tieba.post.repository.FrsPageRepository
|
||||||
import com.huanchengfly.tieba.post.ui.models.ThreadItemData
|
import com.huanchengfly.tieba.post.ui.models.ThreadItemData
|
||||||
|
import com.huanchengfly.tieba.post.ui.models.distinctById
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -248,7 +249,7 @@ sealed interface ForumThreadListPartialChange : PartialChange<ForumThreadListUiS
|
||||||
is Success -> oldState.copy(
|
is Success -> oldState.copy(
|
||||||
isRefreshing = false,
|
isRefreshing = false,
|
||||||
forumRuleTitle = forumRuleTitle,
|
forumRuleTitle = forumRuleTitle,
|
||||||
threadList = threadList.toImmutableList(),
|
threadList = threadList.distinctById(),
|
||||||
threadListIds = threadListIds.toImmutableList(),
|
threadListIds = threadListIds.toImmutableList(),
|
||||||
goodClassifies = goodClassifies.toImmutableList(),
|
goodClassifies = goodClassifies.toImmutableList(),
|
||||||
goodClassifyId = goodClassifyId,
|
goodClassifyId = goodClassifyId,
|
||||||
|
|
@ -281,7 +282,7 @@ sealed interface ForumThreadListPartialChange : PartialChange<ForumThreadListUiS
|
||||||
Start -> oldState.copy(isRefreshing = true)
|
Start -> oldState.copy(isRefreshing = true)
|
||||||
is Success -> oldState.copy(
|
is Success -> oldState.copy(
|
||||||
isRefreshing = false,
|
isRefreshing = false,
|
||||||
threadList = threadList.toImmutableList(),
|
threadList = threadList.distinctById(),
|
||||||
threadListIds = threadListIds.toImmutableList(),
|
threadListIds = threadListIds.toImmutableList(),
|
||||||
goodClassifies = goodClassifies.toImmutableList(),
|
goodClassifies = goodClassifies.toImmutableList(),
|
||||||
goodClassifyId = goodClassifyId,
|
goodClassifyId = goodClassifyId,
|
||||||
|
|
@ -313,7 +314,7 @@ sealed interface ForumThreadListPartialChange : PartialChange<ForumThreadListUiS
|
||||||
Start -> oldState.copy(isLoadingMore = true)
|
Start -> oldState.copy(isLoadingMore = true)
|
||||||
is Success -> oldState.copy(
|
is Success -> oldState.copy(
|
||||||
isLoadingMore = false,
|
isLoadingMore = false,
|
||||||
threadList = (oldState.threadList + threadList).toImmutableList(),
|
threadList = (oldState.threadList + threadList).distinctById(),
|
||||||
threadListIds = threadListIds.toImmutableList(),
|
threadListIds = threadListIds.toImmutableList(),
|
||||||
currentPage = currentPage,
|
currentPage = currentPage,
|
||||||
hasMore = hasMore
|
hasMore = hasMore
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MyLazyColumn
|
import com.huanchengfly.tieba.post.ui.widgets.compose.MyLazyColumn
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
|
import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -59,7 +60,7 @@ fun ConcernPage(
|
||||||
)
|
)
|
||||||
val data by viewModel.uiState.collectPartialAsState(
|
val data by viewModel.uiState.collectPartialAsState(
|
||||||
prop1 = ConcernUiState::data,
|
prop1 = ConcernUiState::data,
|
||||||
initial = emptyList()
|
initial = persistentListOf()
|
||||||
)
|
)
|
||||||
val pullRefreshState = rememberPullRefreshState(
|
val pullRefreshState = rememberPullRefreshState(
|
||||||
refreshing = isRefreshing,
|
refreshing = isRefreshing,
|
||||||
|
|
@ -89,7 +90,7 @@ fun ConcernPage(
|
||||||
) {
|
) {
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
items = data,
|
items = data,
|
||||||
key = { _, item -> "${item.recommendType}_${item.recommendUserList.size}_${item.threadList?.id}" },
|
key = { _, item -> "${item.recommendType}_${item.threadList?.id}" },
|
||||||
contentType = { _, item -> item.recommendType }
|
contentType = { _, item -> item.recommendType }
|
||||||
) { index, item ->
|
) { index, item ->
|
||||||
Container {
|
Container {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ import com.huanchengfly.tieba.post.arch.UiIntent
|
||||||
import com.huanchengfly.tieba.post.arch.UiState
|
import com.huanchengfly.tieba.post.arch.UiState
|
||||||
import com.huanchengfly.tieba.post.utils.appPreferences
|
import com.huanchengfly.tieba.post.utils.appPreferences
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
|
@ -91,23 +94,29 @@ class ConcernViewModel @Inject constructor() :
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface ConcernUiIntent : UiIntent {
|
sealed interface ConcernUiIntent : UiIntent {
|
||||||
object Refresh : ConcernUiIntent
|
data object Refresh : ConcernUiIntent
|
||||||
|
|
||||||
data class LoadMore(val pageTag: String) : ConcernUiIntent
|
data class LoadMore(val pageTag: String) : ConcernUiIntent
|
||||||
|
|
||||||
data class Agree(
|
data class Agree(
|
||||||
val threadId: Long,
|
val threadId: Long,
|
||||||
val postId: Long,
|
val postId: Long,
|
||||||
val hasAgree: Int
|
val hasAgree: Int,
|
||||||
) : ConcernUiIntent
|
) : ConcernUiIntent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun List<ConcernData>.distinctById(): ImmutableList<ConcernData> {
|
||||||
|
return distinctBy {
|
||||||
|
it.threadList?.id
|
||||||
|
}.toImmutableList()
|
||||||
|
}
|
||||||
|
|
||||||
sealed interface ConcernPartialChange : PartialChange<ConcernUiState> {
|
sealed interface ConcernPartialChange : PartialChange<ConcernUiState> {
|
||||||
sealed class Agree private constructor() : ConcernPartialChange {
|
sealed class Agree private constructor() : ConcernPartialChange {
|
||||||
private fun List<ConcernData>.updateAgreeStatus(
|
private fun List<ConcernData>.updateAgreeStatus(
|
||||||
threadId: Long,
|
threadId: Long,
|
||||||
hasAgree: Int
|
hasAgree: Int,
|
||||||
) : List<ConcernData> {
|
): ImmutableList<ConcernData> {
|
||||||
return map {
|
return map {
|
||||||
val threadInfo = it.threadList
|
val threadInfo = it.threadList
|
||||||
if (threadInfo == null) it
|
if (threadInfo == null) it
|
||||||
|
|
@ -118,7 +127,7 @@ sealed interface ConcernPartialChange : PartialChange<ConcernUiState> {
|
||||||
threadInfo
|
threadInfo
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}.toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reduce(oldState: ConcernUiState): ConcernUiState =
|
override fun reduce(oldState: ConcernUiState): ConcernUiState =
|
||||||
|
|
@ -157,14 +166,14 @@ sealed interface ConcernPartialChange : PartialChange<ConcernUiState> {
|
||||||
Start -> oldState.copy(isRefreshing = true)
|
Start -> oldState.copy(isRefreshing = true)
|
||||||
is Success -> oldState.copy(
|
is Success -> oldState.copy(
|
||||||
isRefreshing = false,
|
isRefreshing = false,
|
||||||
data = data,
|
data = data.distinctById(),
|
||||||
hasMore = hasMore,
|
hasMore = hasMore,
|
||||||
nextPageTag = nextPageTag,
|
nextPageTag = nextPageTag,
|
||||||
)
|
)
|
||||||
is Failure -> oldState.copy(isRefreshing = false)
|
is Failure -> oldState.copy(isRefreshing = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Start: Refresh()
|
data object Start : Refresh()
|
||||||
|
|
||||||
data class Success(
|
data class Success(
|
||||||
val data: List<ConcernData>,
|
val data: List<ConcernData>,
|
||||||
|
|
@ -183,14 +192,14 @@ sealed interface ConcernPartialChange : PartialChange<ConcernUiState> {
|
||||||
Start -> oldState.copy(isLoadingMore = true)
|
Start -> oldState.copy(isLoadingMore = true)
|
||||||
is Success -> oldState.copy(
|
is Success -> oldState.copy(
|
||||||
isLoadingMore = false,
|
isLoadingMore = false,
|
||||||
data = oldState.data + data,
|
data = (oldState.data + data).distinctById(),
|
||||||
hasMore = hasMore,
|
hasMore = hasMore,
|
||||||
nextPageTag = nextPageTag,
|
nextPageTag = nextPageTag,
|
||||||
)
|
)
|
||||||
is Failure -> oldState.copy(isLoadingMore = false)
|
is Failure -> oldState.copy(isLoadingMore = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Start: LoadMore()
|
data object Start : LoadMore()
|
||||||
|
|
||||||
data class Success(
|
data class Success(
|
||||||
val data: List<ConcernData>,
|
val data: List<ConcernData>,
|
||||||
|
|
@ -209,7 +218,7 @@ data class ConcernUiState(
|
||||||
val isLoadingMore: Boolean = false,
|
val isLoadingMore: Boolean = false,
|
||||||
val hasMore: Boolean = true,
|
val hasMore: Boolean = true,
|
||||||
val nextPageTag: String = "",
|
val nextPageTag: String = "",
|
||||||
val data: List<ConcernData> = emptyList(),
|
val data: ImmutableList<ConcernData> = persistentListOf(),
|
||||||
): UiState
|
): UiState
|
||||||
|
|
||||||
sealed interface ConcernUiEvent : UiEvent
|
sealed interface ConcernUiEvent : UiEvent
|
||||||
|
|
@ -21,6 +21,7 @@ import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||||
import com.huanchengfly.tieba.post.models.DislikeBean
|
import com.huanchengfly.tieba.post.models.DislikeBean
|
||||||
import com.huanchengfly.tieba.post.repository.PersonalizedRepository
|
import com.huanchengfly.tieba.post.repository.PersonalizedRepository
|
||||||
import com.huanchengfly.tieba.post.ui.models.ThreadItemData
|
import com.huanchengfly.tieba.post.ui.models.ThreadItemData
|
||||||
|
import com.huanchengfly.tieba.post.ui.models.distinctById
|
||||||
import com.huanchengfly.tieba.post.utils.appPreferences
|
import com.huanchengfly.tieba.post.utils.appPreferences
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -276,12 +277,16 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
|
||||||
override fun reduce(oldState: PersonalizedUiState): PersonalizedUiState =
|
override fun reduce(oldState: PersonalizedUiState): PersonalizedUiState =
|
||||||
when (this) {
|
when (this) {
|
||||||
Start -> oldState.copy(isRefreshing = true)
|
Start -> oldState.copy(isRefreshing = true)
|
||||||
is Success -> oldState.copy(
|
is Success -> {
|
||||||
isRefreshing = false,
|
val oldSize = oldState.data.size
|
||||||
currentPage = 1,
|
val newData = (data + oldState.data).distinctById()
|
||||||
data = (data + oldState.data).toImmutableList(),
|
oldState.copy(
|
||||||
refreshPosition = if (oldState.data.isEmpty()) 0 else data.size
|
isRefreshing = false,
|
||||||
)
|
currentPage = 1,
|
||||||
|
data = newData,
|
||||||
|
refreshPosition = if (oldState.data.isEmpty()) 0 else (newData.size - oldSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is Failure -> oldState.copy(
|
is Failure -> oldState.copy(
|
||||||
isRefreshing = false,
|
isRefreshing = false,
|
||||||
|
|
@ -307,7 +312,7 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
|
||||||
is Success -> oldState.copy(
|
is Success -> oldState.copy(
|
||||||
isLoadingMore = false,
|
isLoadingMore = false,
|
||||||
currentPage = currentPage,
|
currentPage = currentPage,
|
||||||
data = (oldState.data + data).toImmutableList(),
|
data = (oldState.data + data).distinctById(),
|
||||||
)
|
)
|
||||||
|
|
||||||
is Failure -> oldState.copy(
|
is Failure -> oldState.copy(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue