更新动态加载逻辑
This commit is contained in:
@@ -116,6 +116,8 @@ dependencies {
|
||||
implementation("androidx.core:core-splashscreen:1.0.1") // 添加 SplashScreen 依赖
|
||||
// 添加 lifecycle-runtime-ktx 依赖
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||
implementation ("org.greenrobot:eventbus:3.3.1")
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.aiosman.ravenow.ui.follower.FollowerNoticeViewModel
|
||||
import com.aiosman.ravenow.ui.follower.FollowingListViewModel
|
||||
import com.aiosman.ravenow.ui.index.IndexViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.MomentExploreViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.search.DiscoverViewModel
|
||||
@@ -131,6 +132,7 @@ object AppState {
|
||||
fun ReloadAppState(context: Context) {
|
||||
// 重置动态列表页面
|
||||
TimelineMomentViewModel.ResetModel()
|
||||
MomentExploreViewModel.ResetModel()
|
||||
// 重置我的页面
|
||||
MyProfileViewModel.ResetModel()
|
||||
// 重置发现页面
|
||||
|
||||
60
app/src/main/java/com/aiosman/ravenow/entity/Loader.kt
Normal file
60
app/src/main/java/com/aiosman/ravenow/entity/Loader.kt
Normal file
@@ -0,0 +1,60 @@
|
||||
package com.aiosman.ravenow.entity
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.aiosman.ravenow.data.ListContainer
|
||||
|
||||
abstract class DataLoader<T,ET> {
|
||||
var list: MutableList<T> = mutableListOf()
|
||||
var page by mutableStateOf(1)
|
||||
var total by mutableStateOf(0)
|
||||
var pageSize by mutableStateOf(10)
|
||||
var hasNext by mutableStateOf(true)
|
||||
var onListChanged: ((List<T>) -> Unit)? = null
|
||||
private var firstLoad = true
|
||||
|
||||
|
||||
abstract suspend fun fetchData(
|
||||
page: Int, pageSize: Int, extra: ET
|
||||
): ListContainer<T>
|
||||
|
||||
suspend fun loadData(
|
||||
extra: ET
|
||||
) {
|
||||
if (!firstLoad) {
|
||||
return
|
||||
}
|
||||
firstLoad = false
|
||||
val result = fetchData(page, pageSize, extra)
|
||||
list = result.list.toMutableList()
|
||||
this.page = page
|
||||
this.total = result.total
|
||||
this.pageSize = pageSize
|
||||
this.hasNext = result.list.size == pageSize
|
||||
onListChanged?.invoke(list)
|
||||
}
|
||||
|
||||
suspend fun loadMore(extra: ET) {
|
||||
if (firstLoad) {
|
||||
return
|
||||
}
|
||||
if (!hasNext) {
|
||||
return
|
||||
}
|
||||
val result = fetchData(page + 1, pageSize, extra)
|
||||
list.addAll(result.list)
|
||||
page += 1
|
||||
hasNext = result.list.size == pageSize
|
||||
onListChanged?.invoke(list)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
list.clear()
|
||||
page = 1
|
||||
total = 0
|
||||
pageSize = 10
|
||||
hasNext = true
|
||||
firstLoad = true
|
||||
}
|
||||
}
|
||||
@@ -285,3 +285,68 @@ data class MomentEntity(
|
||||
// 是否收藏
|
||||
var isFavorite: Boolean = false
|
||||
)
|
||||
class MomentLoaderExtraArgs(
|
||||
val explore: Boolean? = false,
|
||||
val timelineId: Int? = null,
|
||||
val authorId : Int? = null
|
||||
)
|
||||
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||
override suspend fun fetchData(
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
extra: MomentLoaderExtraArgs
|
||||
): ListContainer<MomentEntity> {
|
||||
val result = ApiClient.api.getPosts(
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
explore = if (extra.explore == true) "true" else "",
|
||||
timelineId = extra.timelineId,
|
||||
authorId = extra.authorId
|
||||
)
|
||||
val data = result.body()?.let {
|
||||
ListContainer(
|
||||
list = it.list.map { it.toMomentItem() },
|
||||
total = it.total,
|
||||
page = page,
|
||||
pageSize = pageSize
|
||||
)
|
||||
}
|
||||
if (data == null) {
|
||||
throw ServiceException("Failed to get moments")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
fun updateMomentLike(id: Int,isLike:Boolean) {
|
||||
this.list = this.list.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(likeCount = momentItem.likeCount + if (isLike) 1 else -1, liked = isLike)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}.toMutableList()
|
||||
onListChanged?.invoke(this.list)
|
||||
}
|
||||
|
||||
fun updateFavoriteCount(id: Int,isFavorite:Boolean) {
|
||||
this.list = this.list.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(favoriteCount = momentItem.favoriteCount + if (isFavorite) 1 else -1, isFavorite = isFavorite)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}.toMutableList()
|
||||
onListChanged?.invoke(this.list)
|
||||
}
|
||||
|
||||
fun removeMoment(id: Int) {
|
||||
this.list = this.list.filter { it.id != id }.toMutableList()
|
||||
onListChanged?.invoke(this.list)
|
||||
}
|
||||
|
||||
fun addMoment(moment: MomentEntity) {
|
||||
this.list.add(0, moment)
|
||||
onListChanged?.invoke(this.list)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aiosman.ravenow.event
|
||||
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
|
||||
data class MomentAddEvent(
|
||||
val moment:MomentEntity
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.aiosman.ravenow.event
|
||||
|
||||
data class MomentFavouriteChangeEvent(
|
||||
val postId: Int,
|
||||
val isFavourite: Boolean
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aiosman.ravenow.event
|
||||
|
||||
data class MomentLikeChangeEvent(
|
||||
val postId: Int,
|
||||
val likeCount: Int?,
|
||||
val isLike: Boolean
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.aiosman.ravenow.event
|
||||
|
||||
data class MomentRemoveEvent(
|
||||
val postId: Int
|
||||
)
|
||||
@@ -66,6 +66,7 @@ import com.aiosman.ravenow.ui.navigateToPost
|
||||
|
||||
@Composable
|
||||
fun MomentCard(
|
||||
modifier: Modifier = Modifier,
|
||||
momentEntity: MomentEntity,
|
||||
onLikeClick: () -> Unit = {},
|
||||
onFavoriteClick: () -> Unit = {},
|
||||
@@ -78,7 +79,7 @@ fun MomentCard(
|
||||
var imageIndex by remember { mutableStateOf(0) }
|
||||
val navController = LocalNavController.current
|
||||
Column(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(AppColors.background)
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.moment
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.data.MomentService
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
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.MomentAddEvent
|
||||
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
||||
import com.aiosman.ravenow.event.MomentLikeChangeEvent
|
||||
import com.aiosman.ravenow.event.MomentRemoveEvent
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
|
||||
open class BaseMomentModel :ViewModel(){
|
||||
private val momentService: MomentService = MomentServiceImpl()
|
||||
private val userService = UserServiceImpl()
|
||||
|
||||
|
||||
val momentLoader = MomentLoader().apply {
|
||||
onListChanged = {
|
||||
moments = it
|
||||
}
|
||||
}
|
||||
var refreshing by mutableStateOf(false)
|
||||
var isFirstLoad = true
|
||||
var moments by mutableStateOf<List<MomentEntity>>(listOf())
|
||||
open fun extraArgs(): MomentLoaderExtraArgs {
|
||||
return MomentLoaderExtraArgs()
|
||||
}
|
||||
fun refreshPager(pullRefresh: Boolean = false) {
|
||||
if (!isFirstLoad && !pullRefresh) {
|
||||
return
|
||||
}
|
||||
isFirstLoad = false
|
||||
momentLoader.clear()
|
||||
viewModelScope.launch {
|
||||
momentLoader.loadData(extraArgs())
|
||||
}
|
||||
}
|
||||
|
||||
fun loadMore() {
|
||||
viewModelScope.launch {
|
||||
momentLoader.loadMore(extraArgs())
|
||||
moments = momentLoader.list
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Subscribe
|
||||
fun onMomentLikeChangeEvent(event: MomentLikeChangeEvent) {
|
||||
momentLoader.updateMomentLike(event.postId, event.isLike)
|
||||
}
|
||||
suspend fun likeMoment(id: Int) {
|
||||
momentService.likeMoment(id)
|
||||
momentLoader.updateMomentLike(id, true)
|
||||
}
|
||||
suspend fun dislikeMoment(id: Int) {
|
||||
momentService.dislikeMoment(id)
|
||||
momentLoader.updateMomentLike(id, false)
|
||||
}
|
||||
|
||||
|
||||
suspend fun onAddComment(id: Int) {
|
||||
// val currentPagingData = _momentsFlow.value
|
||||
// updateCommentCount(id)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Subscribe
|
||||
fun onMomentFavoriteChangeEvent(event: MomentFavouriteChangeEvent) {
|
||||
momentLoader.updateFavoriteCount(event.postId, event.isFavourite)
|
||||
}
|
||||
suspend fun favoriteMoment(id: Int) {
|
||||
momentService.favoriteMoment(id)
|
||||
momentLoader.updateFavoriteCount(id, true)
|
||||
}
|
||||
|
||||
|
||||
suspend fun unfavoriteMoment(id: Int) {
|
||||
momentService.unfavoriteMoment(id)
|
||||
momentLoader.updateFavoriteCount(id, false)
|
||||
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onRemoveMomentEvent(event: MomentRemoveEvent) {
|
||||
momentLoader.removeMoment(event.postId)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onAddMomentEvent(event: MomentAddEvent) {
|
||||
momentLoader.addMoment(event.moment)
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fun ResetModel() {
|
||||
momentLoader.clear()
|
||||
isFirstLoad = true
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,19 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.aiosman.ravenow.ui.composables.MomentCard
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -24,14 +27,30 @@ import kotlinx.coroutines.launch
|
||||
@Composable
|
||||
fun ExploreMomentsList() {
|
||||
val model = MomentExploreViewModel
|
||||
var dataFlow = model.momentsFlow
|
||||
var moments = dataFlow.collectAsLazyPagingItems()
|
||||
var moments = model.moments
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
|
||||
model.refreshPager(
|
||||
pullRefresh = true
|
||||
)
|
||||
})
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
// observe list scrolling
|
||||
val reachedBottom by remember {
|
||||
derivedStateOf {
|
||||
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
|
||||
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 2
|
||||
}
|
||||
}
|
||||
|
||||
// load more if scrolled to bottom
|
||||
LaunchedEffect(reachedBottom) {
|
||||
if (reachedBottom) {
|
||||
model.loadMore()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
model.refreshPager()
|
||||
}
|
||||
@@ -43,9 +62,10 @@ fun ExploreMomentsList() {
|
||||
Box(Modifier.pullRefresh(state)) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState
|
||||
) {
|
||||
items(
|
||||
moments.itemCount,
|
||||
moments.size,
|
||||
key = { idx -> idx }
|
||||
) { idx ->
|
||||
val momentItem = moments[idx] ?: return@items
|
||||
@@ -75,7 +95,7 @@ fun ExploreMomentsList() {
|
||||
},
|
||||
onFollowClick = {
|
||||
model.followAction(momentItem)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre
|
||||
|
||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
|
||||
object MomentExploreViewModel : BaseMomentModel() {
|
||||
init {
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
}
|
||||
override fun extraArgs(): MomentLoaderExtraArgs {
|
||||
return MomentLoaderExtraArgs(explore = true)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre
|
||||
|
||||
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.AppState
|
||||
import com.aiosman.ravenow.data.MomentService
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentPagingSource
|
||||
import com.aiosman.ravenow.entity.MomentRemoteDataSource
|
||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
object MomentExploreViewModel : ViewModel() {
|
||||
private val momentService: MomentService = MomentServiceImpl()
|
||||
private val userService = UserServiceImpl()
|
||||
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
|
||||
val momentsFlow = _momentsFlow.asStateFlow()
|
||||
var existsMoment = mutableStateOf(false)
|
||||
var refreshing by mutableStateOf(false)
|
||||
var isFirstLoad = true
|
||||
fun refreshPager(pullRefresh: Boolean = false) {
|
||||
if (!isFirstLoad && !pullRefresh) {
|
||||
return
|
||||
}
|
||||
isFirstLoad = false
|
||||
viewModelScope.launch {
|
||||
if (pullRefresh) {
|
||||
refreshing = true
|
||||
}
|
||||
// 检查是否有动态
|
||||
val existMoments =
|
||||
momentService.getMoments(timelineId = AppState.UserId, pageNumber = 1)
|
||||
if (existMoments.list.isEmpty()) {
|
||||
existsMoment.value = true
|
||||
}
|
||||
if (pullRefresh) {
|
||||
refreshing = false
|
||||
}
|
||||
Pager(
|
||||
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
||||
pagingSourceFactory = {
|
||||
MomentPagingSource(
|
||||
MomentRemoteDataSource(momentService),
|
||||
// 如果没有动态,则显示热门动态
|
||||
explore = true
|
||||
)
|
||||
}
|
||||
).flow.cachedIn(viewModelScope).collectLatest {
|
||||
_momentsFlow.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLikeCount(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(likeCount = momentItem.likeCount + 1, liked = true)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun likeMoment(id: Int) {
|
||||
momentService.likeMoment(id)
|
||||
updateLikeCount(id)
|
||||
}
|
||||
|
||||
fun updateCommentCount(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(commentCount = momentItem.commentCount + 1)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun onAddComment(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
updateCommentCount(id)
|
||||
}
|
||||
|
||||
|
||||
fun updateDislikeMomentById(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(likeCount = momentItem.likeCount - 1, liked = false)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun dislikeMoment(id: Int) {
|
||||
momentService.dislikeMoment(id)
|
||||
updateDislikeMomentById(id)
|
||||
}
|
||||
|
||||
fun updateFavoriteCount(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(favoriteCount = momentItem.favoriteCount + 1, isFavorite = true)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun favoriteMoment(id: Int) {
|
||||
momentService.favoriteMoment(id)
|
||||
updateFavoriteCount(id)
|
||||
}
|
||||
|
||||
fun updateUnfavoriteCount(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(favoriteCount = momentItem.favoriteCount - 1, isFavorite = false)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun unfavoriteMoment(id: Int) {
|
||||
momentService.unfavoriteMoment(id)
|
||||
updateUnfavoriteCount(id)
|
||||
}
|
||||
fun updateFollowStatus(authorId:Int,isFollow:Boolean) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.authorId == authorId) {
|
||||
momentItem.copy(followStatus = isFollow)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ResetModel() {
|
||||
_momentsFlow.value = PagingData.empty()
|
||||
isFirstLoad = true
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,16 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -24,8 +28,7 @@ import kotlinx.coroutines.launch
|
||||
@Composable
|
||||
fun TimelineMomentsList() {
|
||||
val model = TimelineMomentViewModel
|
||||
var dataFlow = model.momentsFlow
|
||||
var moments = dataFlow.collectAsLazyPagingItems()
|
||||
var moments = model.moments
|
||||
val scope = rememberCoroutineScope()
|
||||
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
|
||||
model.refreshPager(
|
||||
@@ -35,6 +38,20 @@ fun TimelineMomentsList() {
|
||||
LaunchedEffect(Unit) {
|
||||
model.refreshPager()
|
||||
}
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
// observe list scrolling
|
||||
val reachedBottom by remember {
|
||||
derivedStateOf {
|
||||
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
|
||||
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 2
|
||||
}
|
||||
}
|
||||
LaunchedEffect(reachedBottom) {
|
||||
if (reachedBottom) {
|
||||
model.loadMore()
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -42,12 +59,13 @@ fun TimelineMomentsList() {
|
||||
Box(Modifier.pullRefresh(state)) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState
|
||||
) {
|
||||
items(
|
||||
moments.itemCount,
|
||||
key = { idx -> moments[idx]?.id ?: idx }
|
||||
moments.size,
|
||||
key = { idx -> moments[idx].id }
|
||||
) { idx ->
|
||||
val momentItem = moments[idx] ?: return@items
|
||||
val momentItem = moments[idx]
|
||||
MomentCard(momentEntity = momentItem,
|
||||
onAddComment = {
|
||||
scope.launch {
|
||||
@@ -77,12 +95,6 @@ fun TimelineMomentsList() {
|
||||
},
|
||||
showFollowButton = false
|
||||
)
|
||||
// Box(
|
||||
// modifier = Modifier
|
||||
// .height(4.dp)
|
||||
// .fillMaxWidth()
|
||||
// .background(Color(0xFFF0F2F5))
|
||||
// )
|
||||
}
|
||||
}
|
||||
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline
|
||||
|
||||
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.filter
|
||||
import androidx.paging.map
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.data.MomentService
|
||||
import com.aiosman.ravenow.data.UserService
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentPagingSource
|
||||
import com.aiosman.ravenow.entity.MomentRemoteDataSource
|
||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
object TimelineMomentViewModel : ViewModel() {
|
||||
private val momentService: MomentService = MomentServiceImpl()
|
||||
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
|
||||
private val userService :UserService = UserServiceImpl()
|
||||
val momentsFlow = _momentsFlow.asStateFlow()
|
||||
var existsMoment = mutableStateOf(false)
|
||||
var refreshing by mutableStateOf(false)
|
||||
var isFirstLoad = true
|
||||
fun refreshPager(pullRefresh: Boolean = false) {
|
||||
if (!isFirstLoad && !pullRefresh) {
|
||||
return
|
||||
}
|
||||
isFirstLoad = false
|
||||
viewModelScope.launch {
|
||||
if (pullRefresh) {
|
||||
refreshing = true
|
||||
}
|
||||
// 检查是否有动态
|
||||
val existMoments =
|
||||
momentService.getMoments(timelineId = AppState.UserId, pageNumber = 1)
|
||||
if (existMoments.list.isEmpty()) {
|
||||
existsMoment.value = true
|
||||
}
|
||||
if (pullRefresh) {
|
||||
refreshing = false
|
||||
}
|
||||
Pager(
|
||||
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
||||
pagingSourceFactory = {
|
||||
MomentPagingSource(
|
||||
MomentRemoteDataSource(momentService),
|
||||
// 如果没有动态,则显示热门动态
|
||||
timelineId = if (existMoments.list.isEmpty()) null else AppState.UserId,
|
||||
trend = if (existMoments.list.isEmpty()) true else null
|
||||
)
|
||||
}
|
||||
).flow.cachedIn(viewModelScope).collectLatest {
|
||||
_momentsFlow.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLikeCount(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(likeCount = momentItem.likeCount + 1, liked = true)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun likeMoment(id: Int) {
|
||||
momentService.likeMoment(id)
|
||||
updateLikeCount(id)
|
||||
}
|
||||
|
||||
fun updateCommentCount(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(commentCount = momentItem.commentCount + 1)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun onAddComment(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
updateCommentCount(id)
|
||||
}
|
||||
|
||||
|
||||
fun updateDislikeMomentById(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(likeCount = momentItem.likeCount - 1, liked = false)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun dislikeMoment(id: Int) {
|
||||
momentService.dislikeMoment(id)
|
||||
updateDislikeMomentById(id)
|
||||
}
|
||||
|
||||
fun updateFavoriteCount(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(favoriteCount = momentItem.favoriteCount + 1, isFavorite = true)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun favoriteMoment(id: Int) {
|
||||
momentService.favoriteMoment(id)
|
||||
updateFavoriteCount(id)
|
||||
}
|
||||
|
||||
fun updateUnfavoriteCount(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(favoriteCount = momentItem.favoriteCount - 1, isFavorite = false)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
suspend fun unfavoriteMoment(id: Int) {
|
||||
momentService.unfavoriteMoment(id)
|
||||
updateUnfavoriteCount(id)
|
||||
}
|
||||
|
||||
fun deleteMoment(id: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.filter { momentItem ->
|
||||
momentItem.id != id
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新动态评论数
|
||||
*/
|
||||
fun updateMomentCommentCount(id: Int, delta: Int) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.id == id) {
|
||||
momentItem.copy(commentCount = momentItem.commentCount + delta)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
|
||||
fun updateFollowStatus(authorId:Int,isFollow:Boolean) {
|
||||
val currentPagingData = _momentsFlow.value
|
||||
val updatedPagingData = currentPagingData.map { momentItem ->
|
||||
if (momentItem.authorId == authorId) {
|
||||
momentItem.copy(followStatus = isFollow)
|
||||
} else {
|
||||
momentItem
|
||||
}
|
||||
}
|
||||
_momentsFlow.value = updatedPagingData
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ResetModel() {
|
||||
_momentsFlow.value = PagingData.empty()
|
||||
isFirstLoad = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline
|
||||
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
|
||||
object TimelineMomentViewModel : BaseMomentModel() {
|
||||
init {
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
override fun extraArgs(): MomentLoaderExtraArgs {
|
||||
return MomentLoaderExtraArgs(timelineId = AppState.UserId!!)
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,6 @@ 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.filter
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.Messaging
|
||||
@@ -22,28 +17,40 @@ import com.aiosman.ravenow.data.MomentService
|
||||
import com.aiosman.ravenow.data.UploadImage
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentPagingSource
|
||||
import com.aiosman.ravenow.entity.MomentRemoteDataSource
|
||||
import com.aiosman.ravenow.entity.MomentLoader
|
||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import com.aiosman.ravenow.event.MomentAddEvent
|
||||
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
||||
import com.aiosman.ravenow.event.MomentLikeChangeEvent
|
||||
import com.aiosman.ravenow.event.MomentRemoveEvent
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import java.io.File
|
||||
|
||||
object MyProfileViewModel : ViewModel() {
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
val momentService: MomentService = MomentServiceImpl()
|
||||
var profile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
private var _sharedFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
|
||||
var sharedFlow = _sharedFlow.asStateFlow()
|
||||
|
||||
var moments by mutableStateOf<List<MomentEntity>>(emptyList())
|
||||
val momentLoader: MomentLoader = MomentLoader().apply {
|
||||
onListChanged = {
|
||||
moments = it
|
||||
}
|
||||
}
|
||||
var refreshing by mutableStateOf(false)
|
||||
var firstLoad = true
|
||||
|
||||
init {
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
suspend fun loadUserProfile() {
|
||||
val profile = accountService.getMyAccountProfile()
|
||||
MyProfileViewModel.profile = profile
|
||||
}
|
||||
|
||||
fun loadProfile(pullRefresh: Boolean = false) {
|
||||
if (!firstLoad && !pullRefresh) return
|
||||
viewModelScope.launch {
|
||||
@@ -55,18 +62,7 @@ object MyProfileViewModel : ViewModel() {
|
||||
refreshing = false
|
||||
profile?.let {
|
||||
try {
|
||||
// Collect shared flow
|
||||
Pager(
|
||||
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
||||
pagingSourceFactory = {
|
||||
MomentPagingSource(
|
||||
MomentRemoteDataSource(momentService),
|
||||
author = AppState.UserId
|
||||
)
|
||||
}
|
||||
).flow.cachedIn(viewModelScope).collectLatest {
|
||||
_sharedFlow.value = it
|
||||
}
|
||||
momentLoader.loadData(extra = MomentLoaderExtraArgs(authorId = it.id))
|
||||
} catch (e: Exception) {
|
||||
Log.e("MyProfileViewModel", "loadProfile: ", e)
|
||||
}
|
||||
@@ -75,6 +71,12 @@ object MyProfileViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadMoreMoment() {
|
||||
viewModelScope.launch {
|
||||
momentLoader.loadMore(extra = MomentLoaderExtraArgs(authorId = profile?.id))
|
||||
}
|
||||
}
|
||||
|
||||
fun logout(context: Context) {
|
||||
viewModelScope.launch {
|
||||
Messaging.unregisterDevice(context)
|
||||
@@ -89,7 +91,7 @@ object MyProfileViewModel : ViewModel() {
|
||||
|
||||
}
|
||||
|
||||
fun updateUserProfileBanner(bannerImageUrl: Uri?,file:File, context: Context) {
|
||||
fun updateUserProfileBanner(bannerImageUrl: Uri?, file: File, context: Context) {
|
||||
viewModelScope.launch {
|
||||
val newBanner = bannerImageUrl?.let {
|
||||
val cursor = context.contentResolver.query(it, null, null, null, null)
|
||||
@@ -117,13 +119,30 @@ object MyProfileViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteMoment(id: Int) {
|
||||
val currentPagingData = _sharedFlow.value
|
||||
val updatedPagingData = currentPagingData.filter { momentItem ->
|
||||
momentItem.id != id
|
||||
fun likeMoment(momentLMomentEntity: MomentEntity) {
|
||||
viewModelScope.launch {
|
||||
if (momentLMomentEntity.liked) {
|
||||
momentService.dislikeMoment(momentLMomentEntity.id)
|
||||
EventBus.getDefault().post(
|
||||
MomentLikeChangeEvent(
|
||||
momentLMomentEntity.id,
|
||||
likeCount = momentLMomentEntity.likeCount - 1,
|
||||
isLike = false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
momentService.likeMoment(momentLMomentEntity.id)
|
||||
EventBus.getDefault().post(
|
||||
MomentLikeChangeEvent(
|
||||
momentLMomentEntity.id,
|
||||
likeCount = momentLMomentEntity.likeCount + 1,
|
||||
isLike = true
|
||||
)
|
||||
)
|
||||
}
|
||||
_sharedFlow.value = updatedPagingData
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val bio get() = profile?.bio ?: ""
|
||||
val nickName get() = profile?.nickName ?: ""
|
||||
@@ -131,7 +150,38 @@ object MyProfileViewModel : ViewModel() {
|
||||
|
||||
fun ResetModel() {
|
||||
profile = null
|
||||
_sharedFlow.value = PagingData.empty()
|
||||
momentLoader.clear()
|
||||
firstLoad = true
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onMomentLikeChangeEvent(event: MomentLikeChangeEvent) {
|
||||
momentLoader.updateMomentLike(event.postId, event.isLike)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onMomentFavoriteChangeEvent(event: MomentFavouriteChangeEvent) {
|
||||
momentLoader.updateFavoriteCount(event.postId, event.isFavourite)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onRemoveMomentEvent(event: MomentRemoveEvent) {
|
||||
momentLoader.removeMoment(event.postId)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onAddMomentEvent(event: MomentAddEvent) {
|
||||
momentLoader.addMoment(event.moment)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onMomentDelete(event: MomentRemoveEvent) {
|
||||
momentLoader.removeMoment(event.postId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,8 +23,10 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
@@ -38,6 +40,8 @@ import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -58,8 +62,6 @@ 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.paging.PagingData
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.ConstVars
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
@@ -88,9 +90,6 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.ui.post.NewPostViewModel
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
@@ -102,22 +101,23 @@ fun ProfileV3(
|
||||
onLogout: () -> Unit = {},
|
||||
onFollowClick: () -> Unit = {},
|
||||
onChatClick: () -> Unit = {},
|
||||
sharedFlow: SharedFlow<PagingData<MomentEntity>> = MutableStateFlow<PagingData<MomentEntity>>(
|
||||
PagingData.empty()
|
||||
).asStateFlow(),
|
||||
isSelf: Boolean = true
|
||||
moments: List<MomentEntity>,
|
||||
isSelf: Boolean = true,
|
||||
onLoadMore: () -> Unit = {},
|
||||
onLike: (MomentEntity) -> Unit = {},
|
||||
onComment: (MomentEntity) -> Unit = {},
|
||||
) {
|
||||
val model = MyProfileViewModel
|
||||
val state = rememberCollapsingToolbarScaffoldState()
|
||||
val pagerState = rememberPagerState(pageCount = { 2 })
|
||||
var enabled by remember { mutableStateOf(true) }
|
||||
val enabled by remember { mutableStateOf(true) }
|
||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var minibarExpanded by remember { mutableStateOf(false) }
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val navController = LocalNavController.current
|
||||
var bannerHeight = 400
|
||||
val bannerHeight = 400
|
||||
val pickBannerImageLauncher = pickupAndCompressLauncher(
|
||||
context,
|
||||
scope,
|
||||
@@ -126,15 +126,44 @@ fun ProfileV3(
|
||||
) { uri, file ->
|
||||
onUpdateBanner?.invoke(uri, file, context)
|
||||
}
|
||||
val moments = sharedFlow.collectAsLazyPagingItems()
|
||||
val refreshState = rememberPullRefreshState(model.refreshing, onRefresh = {
|
||||
model.loadProfile(pullRefresh = true)
|
||||
})
|
||||
var miniToolbarHeight by remember { mutableStateOf(0) }
|
||||
val density = LocalDensity.current
|
||||
var appTheme = LocalAppTheme.current
|
||||
var AppColors = appTheme
|
||||
var systemUiController = rememberSystemUiController()
|
||||
val appTheme = LocalAppTheme.current
|
||||
val AppColors = appTheme
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
// observe list scrolling
|
||||
val reachedListBottom by remember {
|
||||
derivedStateOf {
|
||||
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
|
||||
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 2
|
||||
}
|
||||
}
|
||||
|
||||
// load more if scrolled to bottom
|
||||
LaunchedEffect(reachedListBottom) {
|
||||
if (reachedListBottom) {
|
||||
onLoadMore()
|
||||
}
|
||||
}
|
||||
|
||||
val gridState = rememberLazyStaggeredGridState()
|
||||
val reachedGridBottom by remember {
|
||||
derivedStateOf {
|
||||
val lastVisibleItem = gridState.layoutInfo.visibleItemsInfo.lastOrNull()
|
||||
lastVisibleItem?.index != 0 && lastVisibleItem?.index == gridState.layoutInfo.totalItemsCount - 2
|
||||
}
|
||||
}
|
||||
LaunchedEffect(reachedGridBottom) {
|
||||
if (reachedGridBottom) {
|
||||
onLoadMore()
|
||||
}
|
||||
}
|
||||
|
||||
fun switchTheme(){
|
||||
// delay
|
||||
scope.launch {
|
||||
@@ -499,7 +528,8 @@ fun ProfileV3(
|
||||
8.dp
|
||||
),
|
||||
verticalItemSpacing = 8.dp,
|
||||
contentPadding = PaddingValues(8.dp)
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
state = gridState
|
||||
) {
|
||||
if (isSelf) {
|
||||
items(1) {
|
||||
@@ -541,7 +571,7 @@ fun ProfileV3(
|
||||
}
|
||||
}
|
||||
}
|
||||
items(moments.itemCount) { idx ->
|
||||
items(moments.size) { idx ->
|
||||
val moment = moments[idx] ?: return@items
|
||||
GalleryItem(moment, idx)
|
||||
}
|
||||
@@ -553,18 +583,26 @@ fun ProfileV3(
|
||||
1 ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.fillMaxSize(),
|
||||
state = listState
|
||||
) {
|
||||
if (moments.itemCount == 0 && isSelf) {
|
||||
if (moments.isEmpty() && isSelf) {
|
||||
item {
|
||||
EmptyMomentPostUnit()
|
||||
}
|
||||
}
|
||||
item {
|
||||
for (idx in 0 until moments.itemCount) {
|
||||
val moment = moments[idx]
|
||||
moment?.let {
|
||||
MomentPostUnit(it)
|
||||
for (element in moments) {
|
||||
element.let {
|
||||
MomentPostUnit(
|
||||
it,
|
||||
onLikeClick = {
|
||||
onLike(it)
|
||||
},
|
||||
onCommentClick = {
|
||||
onComment(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ package com.aiosman.ravenow.ui.index.tabs.profile
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.ui.navigateToPost
|
||||
|
||||
@Composable
|
||||
fun ProfileWrap(
|
||||
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
LaunchedEffect(Unit) {
|
||||
MyProfileViewModel.loadProfile()
|
||||
}
|
||||
@@ -20,6 +23,15 @@ fun ProfileWrap(
|
||||
MyProfileViewModel.logout(context)
|
||||
},
|
||||
profile = MyProfileViewModel.profile,
|
||||
sharedFlow = MyProfileViewModel.sharedFlow,
|
||||
moments = MyProfileViewModel.moments,
|
||||
onLoadMore = {
|
||||
MyProfileViewModel.loadMoreMoment()
|
||||
},
|
||||
onLike = { moments ->
|
||||
MyProfileViewModel.likeMoment(moments)
|
||||
},
|
||||
onComment = {
|
||||
navController.navigateToPost(it.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -47,7 +47,9 @@ import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.exp.formatPostTime2
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.composables.AnimatedLikeIcon
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.composables.MomentOperateBtn
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.ui.navigateToPost
|
||||
import com.aiosman.ravenow.ui.post.NewPostViewModel
|
||||
@@ -130,20 +132,26 @@ fun ProfileEmptyMomentCard(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MomentPostUnit(momentEntity: MomentEntity) {
|
||||
fun MomentPostUnit(
|
||||
momentEntity: MomentEntity,
|
||||
onCommentClick: () -> Unit = {},
|
||||
onLikeClick: () -> Unit = {}
|
||||
) {
|
||||
TimeGroup(momentEntity.time.formatPostTime2())
|
||||
ProfileMomentCard(
|
||||
momentEntity.momentTextContent,
|
||||
momentEntity.images[0].thumbnail,
|
||||
momentEntity.likeCount.toString(),
|
||||
momentEntity.commentCount.toString(),
|
||||
momentEntity = momentEntity
|
||||
momentEntity = momentEntity,
|
||||
onCommentClick = onCommentClick,
|
||||
onLikeClick = onLikeClick
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TimeGroup(time: String = "2024.06.08 12:23") {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val appColors = LocalAppTheme.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -162,7 +170,7 @@ fun TimeGroup(time: String = "2024.06.08 12:23") {
|
||||
Text(
|
||||
text = time,
|
||||
fontSize = 16.sp,
|
||||
color = AppColors.text,
|
||||
color = appColors.text,
|
||||
style = TextStyle(fontWeight = FontWeight.W600)
|
||||
)
|
||||
}
|
||||
@@ -174,7 +182,9 @@ fun ProfileMomentCard(
|
||||
imageUrl: String,
|
||||
like: String,
|
||||
comment: String,
|
||||
momentEntity: MomentEntity
|
||||
momentEntity: MomentEntity,
|
||||
onCommentClick: () -> Unit = {},
|
||||
onLikeClick: () -> Unit = {}
|
||||
) {
|
||||
var columnHeight by remember { mutableStateOf(0) }
|
||||
val AppColors = LocalAppTheme.current
|
||||
@@ -214,7 +224,13 @@ fun ProfileMomentCard(
|
||||
MomentCardTopContent(content)
|
||||
}
|
||||
MomentCardPicture(imageUrl, momentEntity = momentEntity)
|
||||
MomentCardOperation(like, comment)
|
||||
MomentCardOperation(
|
||||
like = like,
|
||||
isLike = momentEntity.liked,
|
||||
onLikeClick = onLikeClick,
|
||||
comment = comment,
|
||||
onCommentClick = onCommentClick
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,7 +278,13 @@ fun MomentCardPicture(imageUrl: String, momentEntity: MomentEntity) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MomentCardOperation(like: String, comment: String) {
|
||||
fun MomentCardOperation(
|
||||
like: String,
|
||||
isLike: Boolean,
|
||||
onLikeClick: () -> Unit,
|
||||
comment: String,
|
||||
onCommentClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -270,17 +292,25 @@ fun MomentCardOperation(like: String, comment: String) {
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
MomentCardOperationItem(
|
||||
drawable = R.drawable.rider_pro_like,
|
||||
number = like,
|
||||
modifier = Modifier.padding(end = 32.dp)
|
||||
MomentOperateBtn(count = like) {
|
||||
AnimatedLikeIcon(
|
||||
liked = isLike,
|
||||
onClick = onLikeClick,
|
||||
)
|
||||
MomentCardOperationItem(
|
||||
drawable = R.drawable.rider_pro_moment_comment,
|
||||
number = comment,
|
||||
modifier = Modifier.padding(end = 32.dp)
|
||||
}
|
||||
|
||||
MomentOperateBtn(count = comment) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_moment_comment),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(LocalAppTheme.current.text),
|
||||
modifier = Modifier.noRippleClickable {
|
||||
onCommentClick()
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -200,7 +200,7 @@ class CommentsViewModel(
|
||||
replyUserId = replyUserId,
|
||||
replyCommentId = replyCommentId
|
||||
)
|
||||
TimelineMomentViewModel.updateCommentCount(postId.toInt())
|
||||
// TimelineMomentViewModel.updateCommentCount(postId.toInt())
|
||||
// add to first
|
||||
addedCommentList = listOf(comment) + addedCommentList
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.aiosman.ravenow.data.MomentService
|
||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||
import com.aiosman.ravenow.data.UploadImage
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.event.MomentAddEvent
|
||||
import com.aiosman.ravenow.exp.rotate
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
||||
@@ -21,6 +22,7 @@ import com.aiosman.ravenow.ui.modification.Modification
|
||||
import com.aiosman.ravenow.utils.FileUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
@@ -141,10 +143,10 @@ object NewPostViewModel : ViewModel() {
|
||||
onUploadProgress(((index / imageList.size).toFloat())) // progressValue 是当前上传进度,例如 0.5 表示 50%
|
||||
index += 1
|
||||
}
|
||||
momentService.createMoment(textContent, 1, uploadImageList, relPostId)
|
||||
val result = momentService.createMoment(textContent, 1, uploadImageList, relPostId)
|
||||
// 刷新个人动态
|
||||
MyProfileViewModel.loadProfile(pullRefresh = true)
|
||||
TimelineMomentViewModel.refreshPager()
|
||||
EventBus.getDefault().post(MomentAddEvent(result))
|
||||
}
|
||||
|
||||
suspend fun init() {
|
||||
|
||||
@@ -13,9 +13,14 @@ import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
||||
import com.aiosman.ravenow.event.MomentLikeChangeEvent
|
||||
import com.aiosman.ravenow.event.MomentRemoveEvent
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
|
||||
|
||||
class PostViewModel(
|
||||
@@ -31,6 +36,9 @@ class PostViewModel(
|
||||
var commentsViewModel: CommentsViewModel = CommentsViewModel(postId)
|
||||
var isError by mutableStateOf(false)
|
||||
var isFirstLoad by mutableStateOf(true)
|
||||
init {
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
fun reloadComment() {
|
||||
commentsViewModel.reloadComment()
|
||||
@@ -77,37 +85,80 @@ class PostViewModel(
|
||||
moment = moment?.copy(commentCount = moment?.commentCount?.plus(1) ?: 0)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onMomentLikeChangeEvent(event: MomentLikeChangeEvent) {
|
||||
moment?.let {
|
||||
if (event.postId == it.id) {
|
||||
moment = it.copy(likeCount = event.likeCount ?: it.likeCount, liked = event.isLike)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun likeMoment() {
|
||||
moment?.let {
|
||||
service.likeMoment(it.id)
|
||||
moment = moment?.copy(likeCount = moment?.likeCount?.plus(1) ?: 0, liked = true)
|
||||
TimelineMomentViewModel.updateLikeCount(it.id)
|
||||
EventBus.getDefault().post(
|
||||
MomentLikeChangeEvent(
|
||||
postId = it.id,
|
||||
likeCount = it.likeCount + 1,
|
||||
isLike = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun dislikeMoment() {
|
||||
moment?.let {
|
||||
service.dislikeMoment(it.id)
|
||||
moment = moment?.copy(likeCount = moment?.likeCount?.minus(1) ?: 0, liked = false)
|
||||
// update home list
|
||||
TimelineMomentViewModel.updateDislikeMomentById(it.id)
|
||||
EventBus.getDefault().post(
|
||||
MomentLikeChangeEvent(
|
||||
postId = it.id,
|
||||
likeCount = it.likeCount - 1,
|
||||
isLike = false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onMomentFavouriteChangeEvent(event: MomentFavouriteChangeEvent) {
|
||||
moment?.let {
|
||||
if (event.postId == it.id) {
|
||||
val favouriteCount = if (event.isFavourite) {
|
||||
it.favoriteCount + 1
|
||||
} else {
|
||||
it.favoriteCount - 1
|
||||
}
|
||||
moment = it.copy(
|
||||
favoriteCount = favouriteCount,
|
||||
isFavorite = event.isFavourite
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun favoriteMoment() {
|
||||
moment?.let {
|
||||
service.favoriteMoment(it.id)
|
||||
moment =
|
||||
moment?.copy(favoriteCount = moment?.favoriteCount?.plus(1) ?: 0, isFavorite = true)
|
||||
EventBus.getDefault().post(
|
||||
MomentFavouriteChangeEvent(
|
||||
postId = it.id,
|
||||
isFavourite = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unfavoriteMoment() {
|
||||
moment?.let {
|
||||
service.unfavoriteMoment(it.id)
|
||||
moment = moment?.copy(
|
||||
favoriteCount = moment?.favoriteCount?.minus(1) ?: 0, isFavorite = false
|
||||
EventBus.getDefault().post(
|
||||
MomentFavouriteChangeEvent(
|
||||
postId = it.id,
|
||||
isFavourite = false
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +182,6 @@ class PostViewModel(
|
||||
commentsViewModel.deleteComment(commentId)
|
||||
moment = moment?.copy(commentCount = moment?.commentCount?.minus(1) ?: 0)
|
||||
moment?.let {
|
||||
TimelineMomentViewModel.updateMomentCommentCount(it.id, -1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,8 +210,7 @@ class PostViewModel(
|
||||
viewModelScope.launch {
|
||||
moment?.let {
|
||||
service.deleteMoment(it.id)
|
||||
TimelineMomentViewModel.deleteMoment(it.id)
|
||||
MyProfileViewModel.deleteMoment(it.id)
|
||||
EventBus.getDefault().post(MomentRemoveEvent(it.id))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
@@ -171,4 +220,8 @@ class PostViewModel(
|
||||
commentsViewModel.loadMoreSubComments(commentId)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ fun AccountProfileV2(id: String){
|
||||
isSelf = true
|
||||
}
|
||||
ProfileV3(
|
||||
sharedFlow = model.momentsFlow,
|
||||
moments = model.moments,
|
||||
profile = model.profile,
|
||||
isSelf = isSelf,
|
||||
onChatClick = {
|
||||
|
||||
@@ -5,34 +5,29 @@ 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 com.aiosman.ravenow.data.AccountService
|
||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||
import com.aiosman.ravenow.data.MomentService
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentPagingSource
|
||||
import com.aiosman.ravenow.entity.MomentRemoteDataSource
|
||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import com.aiosman.ravenow.entity.MomentLoader
|
||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class AccountProfileViewModel : ViewModel() {
|
||||
var profileId by mutableStateOf(0)
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
val momentService: MomentService = MomentServiceImpl()
|
||||
val userService = UserServiceImpl()
|
||||
var profile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
private var _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
|
||||
var momentsFlow = _momentsFlow.asStateFlow()
|
||||
var refreshing by mutableStateOf(false)
|
||||
var momentLoader = MomentLoader().apply {
|
||||
onListChanged = {
|
||||
moments = it
|
||||
}
|
||||
}
|
||||
var moments by mutableStateOf<List<MomentEntity>>(listOf())
|
||||
|
||||
fun loadProfile(id: String, pullRefresh: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
if (pullRefresh) {
|
||||
@@ -43,16 +38,12 @@ class AccountProfileViewModel : ViewModel() {
|
||||
}
|
||||
profile = userService.getUserProfile(id)
|
||||
refreshing = false
|
||||
Pager(
|
||||
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
||||
pagingSourceFactory = {
|
||||
MomentPagingSource(
|
||||
MomentRemoteDataSource(momentService),
|
||||
author = profile!!.id
|
||||
)
|
||||
profile?.let {
|
||||
try {
|
||||
momentLoader.loadData(MomentLoaderExtraArgs(authorId = it.id))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
).flow.cachedIn(viewModelScope).collectLatest {
|
||||
_momentsFlow.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,8 +63,6 @@ class AccountProfileViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
val followerCount get() = profile?.followerCount ?: 0
|
||||
val followingCount get() = profile?.followingCount ?: 0
|
||||
val bio get() = profile?.bio ?: ""
|
||||
val nickName get() = profile?.nickName ?: ""
|
||||
val avatar get() = profile?.avatar
|
||||
|
||||
Reference in New Issue
Block a user