From 01fb092e8306e7b6759dad3f58a3932ed0126e03 Mon Sep 17 00:00:00 2001 From: AllenTom Date: Sat, 26 Oct 2024 17:44:51 +0800 Subject: [PATCH] feat: Implement moment follow/unfollow feature This commit implements the follow/unfollow feature for moments in the Search, Timeline, and Explore tabs. It includes: - Adding a follow button to moment cards and implementing follow/unfollow logic in the respective ViewModels. - Updating the UI to reflect the follow status changes in the moment list. - Handling follow/unfollow API requests. --- .../aiosman/riderpro/ui/composables/Moment.kt | 36 +++++++++++++++---- .../index/tabs/moment/tabs/expolre/Moment.kt | 5 ++- .../moment/tabs/expolre/MomentViewModel.kt | 28 +++++++++++++++ .../index/tabs/moment/tabs/timeline/Moment.kt | 3 ++ .../moment/tabs/timeline/MomentViewModel.kt | 29 +++++++++++++++ .../ui/index/tabs/search/SearchScreen.kt | 8 ++++- .../ui/index/tabs/search/SearchViewModel.kt | 33 ++++++++++++++--- 7 files changed, 129 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/Moment.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/Moment.kt index a5ea43a..bf9452b 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/Moment.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/Moment.kt @@ -54,6 +54,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.aiosman.riderpro.AppColors +import com.aiosman.riderpro.AppState import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.R import com.aiosman.riderpro.entity.MomentEntity @@ -70,17 +71,20 @@ fun MomentCard( onLikeClick: () -> Unit = {}, onFavoriteClick: () -> Unit = {}, onAddComment: () -> Unit = {}, + onFollowClick: () -> Unit = {}, hideAction: Boolean = false ) { var imageIndex by remember { mutableStateOf(0) } val navController = LocalNavController.current Column( - modifier = Modifier.fillMaxWidth().background(AppColors.background) + modifier = Modifier + .fillMaxWidth() + .background(AppColors.background) ) { Box( modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp) ) { - MomentTopRowGroup(momentEntity = momentEntity) + MomentTopRowGroup(momentEntity = momentEntity, onFollowClick = onFollowClick) } Column( modifier = Modifier @@ -163,9 +167,9 @@ fun ModificationListHeader() { } @Composable -fun MomentName(name: String) { +fun MomentName(name: String, modifier: Modifier = Modifier) { Text( - modifier = Modifier, + modifier = modifier, textAlign = TextAlign.Start, text = name, color = AppColors.text, @@ -214,7 +218,10 @@ fun MomentPostTime(time: String) { } @Composable -fun MomentTopRowGroup(momentEntity: MomentEntity) { +fun MomentTopRowGroup( + momentEntity: MomentEntity, + onFollowClick: () -> Unit = {} +) { val navController = LocalNavController.current val context = LocalContext.current Row( @@ -239,7 +246,7 @@ fun MomentTopRowGroup(momentEntity: MomentEntity) { ) Column( modifier = Modifier - .defaultMinSize() + .weight(1f) .padding(start = 12.dp, end = 12.dp) ) { Row( @@ -248,8 +255,13 @@ fun MomentTopRowGroup(momentEntity: MomentEntity) { .height(22.dp), verticalAlignment = Alignment.CenterVertically ) { - MomentName(momentEntity.nickname) + MomentName( + modifier = Modifier.weight(1f), + name = momentEntity.nickname + ) // MomentFollowBtn() + Spacer(modifier = Modifier.width(16.dp)) + } Row( modifier = Modifier @@ -262,6 +274,16 @@ fun MomentTopRowGroup(momentEntity: MomentEntity) { MomentPostLocation(momentEntity.location) } } + Spacer(modifier = Modifier.width(16.dp)) + if (AppState.UserId != momentEntity.authorId) { + FollowButton( + isFollowing = momentEntity.followStatus + ) { + onFollowClick() + } + } + + } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/expolre/Moment.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/expolre/Moment.kt index c626919..b41c28d 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/expolre/Moment.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/expolre/Moment.kt @@ -72,7 +72,10 @@ fun ExploreMomentsList() { model.favoriteMoment(momentItem.id) } } - } + }, + onFollowClick = { + model.followAction(momentItem) + }, ) } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/expolre/MomentViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/expolre/MomentViewModel.kt index ccaa473..dc28918 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/expolre/MomentViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/expolre/MomentViewModel.kt @@ -11,7 +11,9 @@ import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.map import com.aiosman.riderpro.AppState +import com.aiosman.riderpro.data.Moment import com.aiosman.riderpro.data.MomentService +import com.aiosman.riderpro.data.UserServiceImpl import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentPagingSource import com.aiosman.riderpro.entity.MomentRemoteDataSource @@ -24,6 +26,7 @@ import kotlinx.coroutines.launch object MomentExploreViewModel : ViewModel() { private val momentService: MomentService = MomentServiceImpl() + private val userService = UserServiceImpl() private val _momentsFlow = MutableStateFlow>(PagingData.empty()) val momentsFlow = _momentsFlow.asStateFlow() var existsMoment = mutableStateOf(false) @@ -147,6 +150,31 @@ object MomentExploreViewModel : ViewModel() { 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() diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/timeline/Moment.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/timeline/Moment.kt index 1bec6a9..419be45 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/timeline/Moment.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/timeline/Moment.kt @@ -71,6 +71,9 @@ fun TimelineMomentsList() { model.favoriteMoment(momentItem.id) } } + }, + onFollowClick = { + model.followAction(momentItem) } ) // Box( diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/timeline/MomentViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/timeline/MomentViewModel.kt index dd22b58..50296db 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/timeline/MomentViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/moment/tabs/timeline/MomentViewModel.kt @@ -13,6 +13,8 @@ import androidx.paging.filter import androidx.paging.map import com.aiosman.riderpro.AppState import com.aiosman.riderpro.data.MomentService +import com.aiosman.riderpro.data.UserService +import com.aiosman.riderpro.data.UserServiceImpl import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentPagingSource import com.aiosman.riderpro.entity.MomentRemoteDataSource @@ -26,6 +28,7 @@ import kotlinx.coroutines.launch object TimelineMomentViewModel : ViewModel() { private val momentService: MomentService = MomentServiceImpl() private val _momentsFlow = MutableStateFlow>(PagingData.empty()) + private val userService :UserService = UserServiceImpl() val momentsFlow = _momentsFlow.asStateFlow() var existsMoment = mutableStateOf(false) var refreshing by mutableStateOf(false) @@ -173,6 +176,32 @@ object TimelineMomentViewModel : ViewModel() { _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 diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchScreen.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchScreen.kt index 49d752f..cf68094 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchScreen.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchScreen.kt @@ -265,7 +265,13 @@ fun MomentResultTab() { .fillMaxWidth() .background(Color.White) ) { - MomentCard(momentEntity = momentItem, hideAction = true) + MomentCard( + momentEntity = momentItem, + hideAction = true, + onFollowClick = { + model.momentFollowAction(momentItem) + } + ) } // Spacer(modifier = Modifier.padding(16.dp)) } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchViewModel.kt index 61a2b05..db3e4ea 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/search/SearchViewModel.kt @@ -10,15 +10,14 @@ import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.cachedIn import androidx.paging.map +import com.aiosman.riderpro.data.MomentService +import com.aiosman.riderpro.data.UserServiceImpl import com.aiosman.riderpro.entity.AccountPagingSource import com.aiosman.riderpro.entity.AccountProfileEntity +import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentPagingSource import com.aiosman.riderpro.entity.MomentRemoteDataSource -import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.entity.MomentServiceImpl -import com.aiosman.riderpro.data.UserServiceImpl -import com.aiosman.riderpro.entity.MomentEntity -import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest @@ -99,4 +98,30 @@ object SearchViewModel : ViewModel() { _usersFlow.value = PagingData.empty() showResult = false } + + fun updateMomentFollowStatus(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 momentFollowAction(moment: MomentEntity) { + viewModelScope.launch { + try { + if (moment.followStatus) { + userService.unFollowUser(moment.authorId.toString()) + } else { + userService.followUser(moment.authorId.toString()) + } + updateMomentFollowStatus(moment.authorId, !moment.followStatus) + } catch (e: Exception) { + e.printStackTrace() + } + } + } } \ No newline at end of file