更新关注逻辑

This commit is contained in:
2024-12-01 15:13:47 +08:00
parent 79fccda1aa
commit c54d5c914a
14 changed files with 199 additions and 156 deletions

View File

@@ -149,8 +149,6 @@ object AppState {
FavouriteNoticeViewModel.ResetModel()
// 重置粉丝通知页面
FollowerNoticeViewModel.ResetModel()
// 重置关注列表页面
FollowingListViewModel.ResetModel()
// 重置关注通知页面
IndexViewModel.ResetModel()
UserId = null

View File

@@ -349,4 +349,15 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
onListChanged?.invoke(this.list)
}
fun updateFollowStatus(authorId:Int,isFollow:Boolean) {
this.list = this.list.map { momentItem ->
if (momentItem.authorId == authorId) {
momentItem.copy(followStatus = isFollow)
} else {
momentItem
}
}.toMutableList()
onListChanged?.invoke(this.list)
}
}

View File

@@ -0,0 +1,6 @@
package com.aiosman.ravenow.event
data class FollowChangeEvent(
val userId: Int,
val isFollow: Boolean
)

View File

@@ -0,0 +1,85 @@
package com.aiosman.ravenow.ui.follower
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.entity.AccountPagingSource
import com.aiosman.ravenow.entity.AccountProfileEntity
import com.aiosman.ravenow.event.FollowChangeEvent
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
open class BaseFollowModel:ViewModel() {
private val userService = UserServiceImpl()
private val _usersFlow = MutableStateFlow<PagingData<AccountProfileEntity>>(PagingData.empty())
val usersFlow = _usersFlow.asStateFlow()
var isLoading by mutableStateOf(false)
open var followerId: Int? = null
open var followingId: Int? = null
init {
EventBus.getDefault().register(this)
}
fun loadData(id: Int,force : Boolean = false) {
if (isLoading) return
isLoading = true
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
AccountPagingSource(
userService,
followerId = followingId,
followingId = followerId
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_usersFlow.value = it
}
}
isLoading = false
}
@Subscribe
fun onFollowChangeEvent(event: FollowChangeEvent) {
updateIsFollow(event.userId, event.isFollow)
}
private fun updateIsFollow(id: Int, isFollow: Boolean = true) {
val currentPagingData = usersFlow.value
val updatedPagingData = currentPagingData.map { user ->
if (user.id == id) {
user.copy(isFollowing = isFollow)
} else {
user
}
}
_usersFlow.value = updatedPagingData
}
suspend fun followUser(userId: Int) {
userService.followUser(userId.toString())
EventBus.getDefault().post(FollowChangeEvent(userId, true))
}
suspend fun unFollowUser(userId: Int) {
userService.unFollowUser(userId.toString())
EventBus.getDefault().post(FollowChangeEvent(userId, false))
}
override fun onCleared() {
super.onCleared()
EventBus.getDefault().unregister(this)
}
}

View File

@@ -17,9 +17,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import com.aiosman.ravenow.exp.viewModelFactory
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout
import kotlinx.coroutines.launch
@@ -27,8 +29,10 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FollowerListScreen(userId: Int) {
val AppColors = LocalAppTheme.current
val model = FollowerListViewModel
val appColors = LocalAppTheme.current
val model: FollowerListViewModel = viewModel(factory = viewModelFactory {
FollowerListViewModel(userId)
}, key = "viewModel_${userId}")
val scope = rememberCoroutineScope()
val refreshState = rememberPullRefreshState(model.isLoading, onRefresh = {
model.loadData(userId, true)
@@ -38,9 +42,8 @@ fun FollowerListScreen(userId: Int) {
}
StatusBarMaskLayout(
modifier = Modifier
.background(color = AppColors.background)
.background(color = appColors.background)
.padding(horizontal = 16.dp),
maskBoxBackgroundColor = AppColors.background,
) {
var dataFlow = model.usersFlow
var users = dataFlow.collectAsLazyPagingItems()

View File

@@ -1,71 +1,7 @@
package com.aiosman.ravenow.ui.follower
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.entity.AccountPagingSource
import com.aiosman.ravenow.entity.AccountProfileEntity
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object FollowerListViewModel : ViewModel() {
private val userService = UserServiceImpl()
private val _usersFlow = MutableStateFlow<PagingData<AccountProfileEntity>>(PagingData.empty())
val usersFlow = _usersFlow.asStateFlow()
private var userId by mutableStateOf<Int?>(null)
var isLoading by mutableStateOf(false)
fun loadData(id: Int,force : Boolean = false) {
if (userId == id && !force) {
return
}
isLoading = true
userId = id
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
AccountPagingSource(
userService,
followerId = id
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_usersFlow.value = it
}
}
isLoading = false
}
private fun updateIsFollow(id: Int, isFollow: Boolean = true) {
val currentPagingData = usersFlow.value
val updatedPagingData = currentPagingData.map { user ->
if (user.id == id) {
user.copy(isFollowing = isFollow)
} else {
user
}
}
_usersFlow.value = updatedPagingData
}
suspend fun followUser(userId: Int) {
userService.followUser(userId.toString())
updateIsFollow(userId)
}
suspend fun unFollowUser(userId: Int) {
userService.unFollowUser(userId.toString())
updateIsFollow(userId, false)
}
class FollowerListViewModel(
val userId: Int
) : BaseFollowModel() {
override var followingId: Int? = userId
}

View File

@@ -34,6 +34,7 @@ import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.FollowButton
import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch
@@ -163,6 +164,13 @@ fun FollowItem(
) {
Text(nickname, fontWeight = FontWeight.Bold, fontSize = 16.sp, color = AppColors.text)
}
if (!isFollowing && userId != AppState.UserId) {
FollowButton(
isFollowing = false,
) {
onFollow()
}
}
}
}
}

View File

@@ -16,10 +16,13 @@ import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.data.UserService
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.UpdateNoticeRequestBody
import com.aiosman.ravenow.event.FollowChangeEvent
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
/**
* 关注消息列表的 ViewModel
@@ -32,6 +35,10 @@ object FollowerNoticeViewModel : ViewModel() {
val followerItemsFlow = _followerItemsFlow.asStateFlow()
var isFirstLoad = true
init {
EventBus.getDefault().register(this)
}
fun reload(force: Boolean = false) {
if (!isFirstLoad && !force) {
return
@@ -50,12 +57,15 @@ object FollowerNoticeViewModel : ViewModel() {
}
}
}
private fun updateIsFollow(id: Int) {
@Subscribe
fun onFollowChangeEvent(event: FollowChangeEvent) {
updateIsFollow(event.userId, event.isFollow)
}
private fun updateIsFollow(id: Int, isFollow: Boolean = true) {
val currentPagingData = _followerItemsFlow.value
val updatedPagingData = currentPagingData.map { follow ->
if (follow.userId == id) {
follow.copy(isFollowing = true)
follow.copy(isFollowing = isFollow)
} else {
follow
}
@@ -64,7 +74,7 @@ object FollowerNoticeViewModel : ViewModel() {
}
suspend fun followUser(userId: Int) {
userService.followUser(userId.toString())
updateIsFollow(userId)
EventBus.getDefault().post(FollowChangeEvent(userId, true))
}
suspend fun updateNotice() {
@@ -78,4 +88,9 @@ object FollowerNoticeViewModel : ViewModel() {
fun ResetModel() {
isFirstLoad = true
}
override fun onCleared() {
super.onCleared()
EventBus.getDefault().unregister(this)
}
}

View File

@@ -24,9 +24,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import com.aiosman.ravenow.exp.viewModelFactory
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout
import kotlinx.coroutines.launch
@@ -34,8 +36,10 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FollowingListScreen(userId: Int) {
val AppColors = LocalAppTheme.current
val model = FollowingListViewModel
val appColors = LocalAppTheme.current
val model : FollowingListViewModel = viewModel(factory = viewModelFactory {
FollowingListViewModel(userId)
}, key = "viewModel_${userId}")
val scope = rememberCoroutineScope()
val refreshState = rememberPullRefreshState(model.isLoading, onRefresh = {
model.loadData(userId, true)
@@ -45,9 +49,8 @@ fun FollowingListScreen(userId: Int) {
}
StatusBarMaskLayout(
modifier = Modifier
.background(color = AppColors.background)
.background(color = appColors.background)
.padding(horizontal = 16.dp),
maskBoxBackgroundColor = AppColors.background
) {
var dataFlow = model.usersFlow
var users = dataFlow.collectAsLazyPagingItems()
@@ -76,14 +79,14 @@ fun FollowingListScreen(userId: Int) {
Spacer(modifier = Modifier.size(32.dp))
androidx.compose.material.Text(
text = "You haven't followed anyone yet",
color = AppColors.text,
color = appColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.size(16.dp))
androidx.compose.material.Text(
text = "Click start your social journey.",
color = AppColors.text,
color = appColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W400
)
@@ -109,7 +112,7 @@ fun FollowingListScreen(userId: Int) {
) {
scope.launch {
if (user.isFollowing) {
model.unfollowUser(user.id)
model.unFollowUser(user.id)
} else {
model.followUser(user.id)
}

View File

@@ -18,58 +18,8 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object FollowingListViewModel : ViewModel() {
private val userService = UserServiceImpl()
private val _usersFlow = MutableStateFlow<PagingData<AccountProfileEntity>>(PagingData.empty())
var isLoading by mutableStateOf(false)
val usersFlow = _usersFlow.asStateFlow()
private var userId by mutableStateOf<Int?>(null)
fun loadData(id: Int, force: Boolean = false) {
if (userId == id && !force) {
return
}
isLoading = true
userId = id
viewModelScope.launch {
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
AccountPagingSource(
userService,
followingId = id
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_usersFlow.value = it
}
}
isLoading = false
}
private fun updateIsFollow(id: Int, isFollow: Boolean = true) {
val currentPagingData = usersFlow.value
val updatedPagingData = currentPagingData.map { user ->
if (user.id == id) {
user.copy(isFollowing = isFollow)
} else {
user
}
}
_usersFlow.value = updatedPagingData
}
suspend fun followUser(userId: Int) {
userService.followUser(userId.toString())
updateIsFollow(userId)
}
suspend fun unfollowUser(userId: Int) {
userService.unFollowUser(userId.toString())
updateIsFollow(userId, false)
}
fun ResetModel() {
userId = null
}
class FollowingListViewModel(
val userId: Int
) : BaseFollowModel() {
override var followerId: Int? = userId
}

View File

@@ -11,6 +11,7 @@ import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.entity.MomentLoader
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
import com.aiosman.ravenow.entity.MomentServiceImpl
import com.aiosman.ravenow.event.FollowChangeEvent
import com.aiosman.ravenow.event.MomentAddEvent
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
import com.aiosman.ravenow.event.MomentLikeChangeEvent
@@ -101,20 +102,26 @@ open class BaseMomentModel :ViewModel(){
momentLoader.addMoment(event.moment)
}
@Subscribe
fun onFollowChangeEvent(event: FollowChangeEvent) {
momentLoader.updateFollowStatus(event.userId, event.isFollow)
}
fun followAction(moment: MomentEntity) {
// viewModelScope.launch {
// try {
// if (moment.followStatus) {
// userService.unFollowUser(moment.authorId.toString())
// } else {
// userService.followUser(moment.authorId.toString())
// }
// updateFollowStatus(moment.authorId, !moment.followStatus)
// } catch (e: Exception) {
// e.printStackTrace()
// }
// }
viewModelScope.launch {
try {
if (moment.followStatus) {
userService.unFollowUser(moment.authorId.toString())
EventBus.getDefault().post(FollowChangeEvent(moment.authorId, false))
} else {
userService.followUser(moment.authorId.toString())
EventBus.getDefault().post(FollowChangeEvent(moment.authorId, true))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun ResetModel() {

View File

@@ -95,7 +95,8 @@ fun ExploreMomentsList() {
},
onFollowClick = {
model.followAction(momentItem)
}
},
showFollowButton = true
)
}
}

View File

@@ -20,6 +20,7 @@ import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.entity.MomentLoader
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
import com.aiosman.ravenow.entity.MomentServiceImpl
import com.aiosman.ravenow.event.FollowChangeEvent
import com.aiosman.ravenow.event.MomentAddEvent
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
import com.aiosman.ravenow.event.MomentLikeChangeEvent
@@ -184,4 +185,8 @@ object MyProfileViewModel : ViewModel() {
momentLoader.removeMoment(event.postId)
}
@Subscribe
fun onFollowChangeEvent(event: FollowChangeEvent) {
momentLoader.updateFollowStatus(event.userId, event.isFollow)
}
}

View File

@@ -12,7 +12,10 @@ import com.aiosman.ravenow.entity.AccountProfileEntity
import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.entity.MomentLoader
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
import com.aiosman.ravenow.event.FollowChangeEvent
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
class AccountProfileViewModel : ViewModel() {
@@ -27,6 +30,9 @@ class AccountProfileViewModel : ViewModel() {
}
}
var moments by mutableStateOf<List<MomentEntity>>(listOf())
init {
EventBus.getDefault().register(this)
}
fun loadProfile(id: String, pullRefresh: Boolean = false) {
viewModelScope.launch {
@@ -47,19 +53,23 @@ class AccountProfileViewModel : ViewModel() {
}
}
}
@Subscribe
fun onFollowChangeEvent(event: FollowChangeEvent) {
if (event.userId == profile?.id) {
profile = profile?.copy(followerCount = profile!!.followerCount + if (event.isFollow) 1 else -1, isFollowing = event.isFollow)
}
}
fun followUser(userId: String) {
viewModelScope.launch {
userService.followUser(userId)
profile = profile?.copy(followerCount = profile!!.followerCount + 1, isFollowing = true)
EventBus.getDefault().post(FollowChangeEvent(userId.toInt(), true))
}
}
fun unFollowUser(userId: String) {
viewModelScope.launch {
userService.unFollowUser(userId)
profile =
profile?.copy(followerCount = profile!!.followerCount - 1, isFollowing = false)
EventBus.getDefault().post(FollowChangeEvent(userId.toInt(), false))
}
}
@@ -67,4 +77,9 @@ class AccountProfileViewModel : ViewModel() {
val nickName get() = profile?.nickName ?: ""
val avatar get() = profile?.avatar
override fun onCleared() {
super.onCleared()
EventBus.getDefault().unregister(this)
}
}