新增我的页面下拉刷新功能

新增个人主页为空时的文案

发布动态后自动刷新个人主页
This commit is contained in:
2024-09-06 16:16:20 +08:00
parent fc324240b6
commit f357e12f7c
6 changed files with 163 additions and 157 deletions

View File

@@ -30,21 +30,7 @@ object MomentViewModel : ViewModel() {
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
var existsException = mutableStateOf(false) var existsException = mutableStateOf(false)
init { init {
viewModelScope.launch { refreshPager()
// 获取当前用户信息
val profile = accountService.getMyAccountProfile()
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
MomentPagingSource(
MomentRemoteDataSource(momentService),
timelineId = profile.id
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_momentsFlow.value = it
}
}
} }
fun refreshPager() { fun refreshPager() {
viewModelScope.launch { viewModelScope.launch {

View File

@@ -5,6 +5,7 @@ import android.net.Uri
import android.util.Log import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -32,14 +33,18 @@ import kotlinx.coroutines.launch
object MyProfileViewModel : ViewModel() { object MyProfileViewModel : ViewModel() {
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
val momentService: MomentService = MomentServiceImpl() val momentService: MomentService = MomentServiceImpl()
val userService = UserServiceImpl()
var profile by mutableStateOf<AccountProfileEntity?>(null) var profile by mutableStateOf<AccountProfileEntity?>(null)
private var _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty()) private var _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
var momentsFlow = _momentsFlow.asStateFlow() var momentsFlow = _momentsFlow.asStateFlow()
fun loadProfile() { var refreshing by mutableStateOf(false)
fun loadProfile(pullRefresh: Boolean = false) {
viewModelScope.launch { viewModelScope.launch {
if (pullRefresh){
refreshing = true
}
profile = accountService.getMyAccountProfile() profile = accountService.getMyAccountProfile()
val profile = accountService.getMyAccountProfile() val profile = accountService.getMyAccountProfile()
refreshing = false
Pager( Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false), config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = { pagingSourceFactory = {
@@ -51,6 +56,7 @@ object MyProfileViewModel : ViewModel() {
).flow.cachedIn(viewModelScope).collectLatest { ).flow.cachedIn(viewModelScope).collectLatest {
_momentsFlow.value = it _momentsFlow.value = it
} }
} }
} }

View File

@@ -2,16 +2,12 @@ package com.aiosman.riderpro.ui.index.tabs.profile
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -22,7 +18,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@@ -33,13 +28,14 @@ import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -71,23 +67,19 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.exp.formatPostTime
import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.exp.formatPostTime2 import com.aiosman.riderpro.exp.formatPostTime2
import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.CustomAsyncImage import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.index.tabs.moment.MomentCard
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.post.PostViewModel import com.aiosman.riderpro.ui.post.PostViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun ProfilePage() { fun ProfilePage() {
val model = MyProfileViewModel val model = MyProfileViewModel
@@ -98,6 +90,9 @@ fun ProfilePage() {
val moments = model.momentsFlow.collectAsLazyPagingItems() val moments = model.momentsFlow.collectAsLazyPagingItems()
val navController: NavController = LocalNavController.current val navController: NavController = LocalNavController.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.loadProfile(pullRefresh = true)
})
val context = LocalContext.current val context = LocalContext.current
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
val pickBannerImageLauncher = rememberLauncherForActivityResult( val pickBannerImageLauncher = rememberLauncherForActivityResult(
@@ -110,7 +105,7 @@ fun ProfilePage() {
} }
} }
} }
LazyColumn( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(Color(0xFFF5F5F5)) .background(Color(0xFFF5F5F5))
@@ -120,148 +115,154 @@ fun ProfilePage() {
.toDp() + 48.dp .toDp() + 48.dp
da da
}) })
.pullRefresh(state)
) { ) {
item { LazyColumn(
Box( modifier = Modifier.fillMaxSize()
modifier = Modifier ) {
.fillMaxWidth() item {
) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(400.dp)
.noRippleClickable { ) {
Intent(Intent.ACTION_PICK).apply { Box(
type = "image/*" modifier = Modifier
pickBannerImageLauncher.launch(this) .fillMaxWidth()
.height(400.dp)
.noRippleClickable {
Intent(Intent.ACTION_PICK).apply {
type = "image/*"
pickBannerImageLauncher.launch(this)
}
} }
}
) {
val banner = model.profile?.banner
if (banner != null) {
CustomAsyncImage(
LocalContext.current,
banner,
modifier = Modifier
.fillMaxSize(),
contentDescription = "",
contentScale = ContentScale.Crop
)
} else {
Image(
painter = painterResource(id = R.drawable.rider_pro_moment_demo_2),
modifier = Modifier
.fillMaxSize(),
contentDescription = "",
contentScale = ContentScale.Crop
)
}
}
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(
top = statusBarPaddingValues.calculateTopPadding(),
start = 16.dp,
end = 16.dp
)
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
modifier = Modifier.noRippleClickable {
expanded = true
},
tint = Color.White
)
MaterialTheme(
shapes = MaterialTheme.shapes.copy(
extraSmall = RoundedCornerShape(
16.dp
)
)
) { ) {
DropdownMenu( val banner = model.profile?.banner
expanded = expanded,
onDismissRequest = { expanded = false }, if (banner != null) {
modifier = Modifier CustomAsyncImage(
.width(250.dp) LocalContext.current,
.background(Color.White) banner,
) {
Box(
modifier = Modifier modifier = Modifier
.padding(vertical = 14.dp, horizontal = 24.dp) .fillMaxSize(),
.noRippleClickable { contentDescription = "",
expanded = false contentScale = ContentScale.Crop
scope.launch { )
model.logout() } else {
navController.navigate(NavigationRoute.Login.route) { Image(
popUpTo(NavigationRoute.Index.route) { painter = painterResource(id = R.drawable.rider_pro_moment_demo_2),
inclusive = true modifier = Modifier
.fillMaxSize(),
contentDescription = "",
contentScale = ContentScale.Crop
)
}
}
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(
top = statusBarPaddingValues.calculateTopPadding(),
start = 16.dp,
end = 16.dp
)
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
modifier = Modifier.noRippleClickable {
expanded = true
},
tint = Color.White
)
MaterialTheme(
shapes = MaterialTheme.shapes.copy(
extraSmall = RoundedCornerShape(
16.dp
)
)
) {
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(250.dp)
.background(Color.White)
) {
Box(
modifier = Modifier
.padding(vertical = 14.dp, horizontal = 24.dp)
.noRippleClickable {
expanded = false
scope.launch {
model.logout()
navController.navigate(NavigationRoute.Login.route) {
popUpTo(NavigationRoute.Index.route) {
inclusive = true
}
} }
} }
} }) {
}) { Row {
Row { Text(stringResource(R.string.logout), fontWeight = FontWeight.W500)
Text("Logout", fontWeight = FontWeight.W500) Spacer(modifier = Modifier.weight(1f))
Spacer(modifier = Modifier.weight(1f)) Icon(
Icon( painter = painterResource(id = R.mipmap.rider_pro_logout),
painter = painterResource(id = R.mipmap.rider_pro_logout), contentDescription = "",
contentDescription = "", modifier = Modifier.size(24.dp)
modifier = Modifier.size(24.dp) )
) }
} }
} Box(
Box( modifier = Modifier
modifier = Modifier .padding(vertical = 14.dp, horizontal = 24.dp)
.padding(vertical = 14.dp, horizontal = 24.dp) .noRippleClickable {
.noRippleClickable { expanded = false
expanded = false scope.launch {
scope.launch { navController.navigate(NavigationRoute.ChangePasswordScreen.route)
navController.navigate(NavigationRoute.ChangePasswordScreen.route) }
} }) {
}) { Row {
Row { Text(stringResource(R.string.change_password), fontWeight = FontWeight.W500)
Text("Change password", fontWeight = FontWeight.W500) Spacer(modifier = Modifier.weight(1f))
Spacer(modifier = Modifier.weight(1f)) Icon(
Icon( painter = painterResource(id = R.mipmap.rider_pro_change_password),
painter = painterResource(id = R.mipmap.rider_pro_change_password), contentDescription = "",
contentDescription = "", modifier = Modifier.size(24.dp)
modifier = Modifier.size(24.dp) )
) }
} }
} }
} }
} }
} }
Spacer(modifier = Modifier.height(32.dp))
model.profile?.let {
UserInformation(
accountProfileEntity = it,
onEditProfileClick = {
navController.navigate(NavigationRoute.AccountEdit.route)
}
)
}
if (moments.itemCount == 0) {
EmptyMomentPostUnit()
}
} }
Spacer(modifier = Modifier.height(32.dp))
model.profile?.let {
UserInformation(
accountProfileEntity = it,
onEditProfileClick = {
navController.navigate(NavigationRoute.AccountEdit.route)
}
)
}
if (moments.itemCount == 0) {
EmptyMomentPostUnit()
}
}
items(moments.itemCount) { idx -> items(moments.itemCount) { idx ->
val momentItem = moments[idx] ?: return@items val momentItem = moments[idx] ?: return@items
MomentPostUnit(momentItem) MomentPostUnit(momentItem)
} }
item { item {
Spacer(modifier = Modifier.height(48.dp)) Spacer(modifier = Modifier.height(48.dp))
} }
}
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
} }
@@ -541,7 +542,7 @@ fun CommunicationOperatorGroup(
contentDescription = "" contentDescription = ""
) )
Text( Text(
text = "Edit profile", text = stringResource(R.string.edit_profile),
fontSize = 14.sp, fontSize = 14.sp,
color = Color.Black, color = Color.Black,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
@@ -619,7 +620,7 @@ fun RidingStyleItem(styleContent: String) {
@Composable @Composable
fun EmptyMomentPostUnit() { fun EmptyMomentPostUnit() {
TimeGroup("You haven't left any tracks yet") TimeGroup(stringResource(R.string.empty_my_post_title))
ProfileEmptyMomentCard() ProfileEmptyMomentCard()
} }
@@ -660,7 +661,7 @@ fun ProfileEmptyMomentCard(
columnHeight = coordinates.size.height columnHeight = coordinates.size.height
} }
) { ) {
Text("Post a moment now", fontSize = 16.sp) Text(stringResource(R.string.empty_my_post_content), fontSize = 16.sp)
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Box( Box(
modifier = Modifier modifier = Modifier

View File

@@ -11,6 +11,7 @@ import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.entity.MomentServiceImpl import com.aiosman.riderpro.entity.MomentServiceImpl
import com.aiosman.riderpro.data.UploadImage import com.aiosman.riderpro.data.UploadImage
import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.riderpro.ui.modification.Modification import com.aiosman.riderpro.ui.modification.Modification
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -75,6 +76,8 @@ object NewPostViewModel : ViewModel() {
index += 1 index += 1
} }
momentService.createMoment(textContent, 1, uploadImageList, relPostId) momentService.createMoment(textContent, 1, uploadImageList, relPostId)
// 刷新个人动态
MyProfileViewModel.loadProfile()
} }
suspend fun init() { suspend fun init() {

View File

@@ -45,4 +45,9 @@
<string name="error_unknown">服务端未知错误</string> <string name="error_unknown">服务端未知错误</string>
<string name="error_not_accept_recive_notice">为了为您提供更个性化的服务,请允许我们向您推送相关信息。</string> <string name="error_not_accept_recive_notice">为了为您提供更个性化的服务,请允许我们向您推送相关信息。</string>
<string name="error_not_accept_term">"为了提供更好的服务,请您在注册前仔细阅读并同意《用户协议》。 "</string> <string name="error_not_accept_term">"为了提供更好的服务,请您在注册前仔细阅读并同意《用户协议》。 "</string>
<string name="empty_my_post_title">还没有发布任何动态</string>
<string name="empty_my_post_content">发布一个动态吧</string>
<string name="edit_profile">编辑个人资料</string>
<string name="logout">登出</string>
<string name="change_password">变更密码</string>
</resources> </resources>

View File

@@ -44,4 +44,9 @@
<string name="error_unknown">Unknown error</string> <string name="error_unknown">Unknown error</string>
<string name="error_not_accept_recive_notice">To provide you with a more personalized experience, please allow us to send you relevant notifications.</string> <string name="error_not_accept_recive_notice">To provide you with a more personalized experience, please allow us to send you relevant notifications.</string>
<string name="error_not_accept_term">To provide you with the best service, please read and agree to our User Agreement before registering.</string> <string name="error_not_accept_term">To provide you with the best service, please read and agree to our User Agreement before registering.</string>
<string name="empty_my_post_title">You haven\'t left any tracks yet</string>
<string name="empty_my_post_content">Post a moment now</string>
<string name="edit_profile">Edit profile</string>
<string name="logout">Logout</string>
<string name="change_password">Change password</string>
</resources> </resources>