个人主页新增下拉刷新功能

在个人主页界面新增下拉刷新功能,提升用户体验。
同时修复了关注按钮的样式问题,使其在
不同状态下显示正确的颜色和文字。
此外,还修复了登录后无法获取用户信息的问题,确保用户登录后能够正常使用应用。
This commit is contained in:
2024-09-06 17:43:43 +08:00
parent f357e12f7c
commit 1fc487c73d
7 changed files with 128 additions and 72 deletions

View File

@@ -0,0 +1,5 @@
package com.aiosman.riderpro
object AppState {
var UserId: Int? = null
}

View File

@@ -80,10 +80,12 @@ class MainActivity : ComponentActivity() {
/** /**
* 获取账号信息 * 获取账号信息
*/ */
suspend fun getAccount(): Boolean { private suspend fun getAccount(): Boolean {
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
try { try {
val resp = accountService.getMyAccount() val resp = accountService.getMyAccount()
// 设置当前登录用户 ID
AppState.UserId = resp.id
return true return true
} catch (e: Exception) { } catch (e: Exception) {
return false return false

View File

@@ -67,6 +67,7 @@ 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.AppState
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
@@ -206,7 +207,10 @@ fun ProfilePage() {
} }
}) { }) {
Row { Row {
Text(stringResource(R.string.logout), fontWeight = FontWeight.W500) Text(
stringResource(R.string.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),
@@ -225,7 +229,10 @@ fun ProfilePage() {
} }
}) { }) {
Row { Row {
Text(stringResource(R.string.change_password), fontWeight = FontWeight.W500) Text(
stringResource(R.string.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),
@@ -339,7 +346,8 @@ fun UserInformation(
isSelf = isSelf, isSelf = isSelf,
isFollowing = accountProfileEntity.isFollowing, isFollowing = accountProfileEntity.isFollowing,
onFollowClick = onFollowClick, onFollowClick = onFollowClick,
onEditProfileClick = onEditProfileClick onEditProfileClick = onEditProfileClick,
accountProfileEntity = accountProfileEntity
) )
} }
} }
@@ -491,6 +499,7 @@ fun UserInformationSlogan(accountProfileEntity: AccountProfileEntity) {
@Composable @Composable
fun CommunicationOperatorGroup( fun CommunicationOperatorGroup(
accountProfileEntity: AccountProfileEntity,
isSelf: Boolean = true, isSelf: Boolean = true,
isFollowing: Boolean = false, isFollowing: Boolean = false,
onFollowClick: () -> Unit = {}, onFollowClick: () -> Unit = {},
@@ -502,7 +511,7 @@ fun CommunicationOperatorGroup(
.fillMaxWidth() .fillMaxWidth()
.padding(top = 16.dp), horizontalArrangement = Arrangement.Center .padding(top = 16.dp), horizontalArrangement = Arrangement.Center
) { ) {
if (!isSelf) { if (!isSelf && AppState.UserId != accountProfileEntity.id) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(width = 142.dp, height = 40.dp) .size(width = 142.dp, height = 40.dp)
@@ -513,16 +522,18 @@ fun CommunicationOperatorGroup(
) { ) {
Image( Image(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.drawable.rider_pro_profile_follow), painter = if (isFollowing) painterResource(id = R.mipmap.rider_pro_follow_grey) else painterResource(
id = R.mipmap.rider_pro_follow_red
),
contentDescription = "" contentDescription = ""
) )
Text( Text(
text = if (isFollowing) stringResource(R.string.following_upper) else stringResource( text = if (isFollowing) stringResource(R.string.following_upper) else stringResource(
R.string.follow_upper R.string.follow_upper
), ),
fontSize = 16.sp, fontSize = 14.sp,
color = Color.White, color = if (isFollowing) Color.Black else Color.White,
style = TextStyle(fontWeight = FontWeight.Bold) style = TextStyle(fontWeight = FontWeight.W600, fontStyle = FontStyle.Italic),
) )
} }
} }
@@ -550,7 +561,6 @@ fun CommunicationOperatorGroup(
) )
} }
} }
} }
} }

View File

@@ -30,6 +30,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.AppState
import com.aiosman.riderpro.AppStore import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.ErrorCode import com.aiosman.riderpro.ErrorCode
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
@@ -100,20 +101,28 @@ fun EmailSignupScreen() {
} }
if (!acceptTerms) { if (!acceptTerms) {
scope.launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.error_not_accept_term), Toast.LENGTH_SHORT).show() Toast.makeText(
context,
context.getString(R.string.error_not_accept_term),
Toast.LENGTH_SHORT
).show()
} }
termsError = true termsError = true
return false return false
}else{ } else {
termsError = false termsError = false
} }
if (!acceptPromotions) { if (!acceptPromotions) {
scope.launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
Toast.makeText(context, context.getString(R.string.error_not_accept_recive_notice), Toast.LENGTH_SHORT).show() Toast.makeText(
context,
context.getString(R.string.error_not_accept_recive_notice),
Toast.LENGTH_SHORT
).show()
} }
promotionsError = true promotionsError = true
return false return false
}else{ } else {
promotionsError = false promotionsError = false
} }
@@ -131,7 +140,8 @@ fun EmailSignupScreen() {
emailError = context.getString(R.string.error_10001_user_exist) emailError = context.getString(R.string.error_10001_user_exist)
return@launch return@launch
} }
Toast.makeText(context, context.getErrorMessageCode(e.code), Toast.LENGTH_SHORT).show() Toast.makeText(context, context.getErrorMessageCode(e.code), Toast.LENGTH_SHORT)
.show()
} }
return return
} catch (e: Exception) { } catch (e: Exception) {
@@ -154,7 +164,8 @@ fun EmailSignupScreen() {
} }
// 获取token 信息 // 获取token 信息
try { try {
accountService.getMyAccount() val resp = accountService.getMyAccount()
AppState.UserId = resp.id
Messaging.InitFCM(scope) Messaging.InitFCM(scope)
} catch (e: ServiceException) { } catch (e: ServiceException) {
scope.launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {

View File

@@ -28,6 +28,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.AppState
import com.aiosman.riderpro.AppStore import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.Messaging import com.aiosman.riderpro.Messaging
@@ -53,7 +54,7 @@ fun SignupScreen() {
fun googleLogin() { fun googleLogin() {
coroutineScope.launch { coroutineScope.launch {
try { try {
GoogleLogin(context) { GoogleLogin(context) {
coroutineScope.launch { coroutineScope.launch {
try { try {
@@ -82,7 +83,8 @@ fun SignupScreen() {
} }
// 获取token 信息 // 获取token 信息
try { try {
accountService.getMyAccount() val resp = accountService.getMyAccount()
AppState.UserId = resp.id
Messaging.InitFCM(coroutineScope) Messaging.InitFCM(coroutineScope)
} catch (e: ServiceException) { } catch (e: ServiceException) {
coroutineScope.launch(Dispatchers.Main) { coroutineScope.launch(Dispatchers.Main) {

View File

@@ -1,18 +1,25 @@
package com.aiosman.riderpro.ui.profile package com.aiosman.riderpro.ui.profile
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.Spacer
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
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
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.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@@ -24,73 +31,84 @@ import com.aiosman.riderpro.ui.index.tabs.profile.MomentPostUnit
import com.aiosman.riderpro.ui.index.tabs.profile.UserInformation import com.aiosman.riderpro.ui.index.tabs.profile.UserInformation
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun AccountProfile(id: String) { fun AccountProfile(id: String) {
val model = AccountProfileViewModel val model = AccountProfileViewModel
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val items = model.momentsFlow.collectAsLazyPagingItems() val items = model.momentsFlow.collectAsLazyPagingItems()
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.loadProfile(id,pullRefresh = true)
})
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
model.loadProfile(id) model.loadProfile(id)
} }
Box(
LazyColumn(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize().background(Color.White).pullRefresh(state)
.padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
) { ) {
item { LazyColumn(
Box( modifier = Modifier
modifier = Modifier .fillMaxSize()
.fillMaxWidth() .padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
) { ) {
val banner = model.profile?.banner val banner = model.profile?.banner
if (banner != null) { if (banner != null) {
CustomAsyncImage( CustomAsyncImage(
LocalContext.current, LocalContext.current,
banner, banner,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(400.dp), .height(400.dp),
contentDescription = "", contentDescription = "",
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
} else { } else {
Image( Image(
painter = painterResource(id = R.drawable.rider_pro_moment_demo_2), painter = painterResource(id = R.drawable.rider_pro_moment_demo_2),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(400.dp), .height(400.dp),
contentDescription = "", contentDescription = "",
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
)
}
}
Spacer(modifier = Modifier.height(32.dp))
model.profile?.let {
UserInformation(
isSelf = false,
accountProfileEntity = it,
onFollowClick = {
scope.launch {
if (it.isFollowing) {
model.unFollowUser(id)
} else {
model.followUser(id)
}
}
},
) )
} }
}
model.profile?.let {
UserInformation(
isSelf = false,
accountProfileEntity = it,
onFollowClick = {
scope.launch {
if (it.isFollowing) {
model.unFollowUser(id)
} else {
model.followUser(id)
}
}
},
)
}
// RidingStyle() // RidingStyle()
} }
items(items.itemCount) { idx -> items(items.itemCount) { idx ->
val momentItem = items[idx] ?: return@items val momentItem = items[idx] ?: return@items
MomentPostUnit(momentItem) MomentPostUnit(momentItem)
} }
}
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
} }
} }

View File

@@ -31,12 +31,17 @@ object AccountProfileViewModel : ViewModel() {
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(id: String) { var refreshing by mutableStateOf(false)
fun loadProfile(id: String, pullRefresh: Boolean = false) {
viewModelScope.launch { viewModelScope.launch {
if (profileId == profile?.id){ if (pullRefresh) {
refreshing = true
}
if (profileId == profile?.id) {
return@launch return@launch
} }
profile = userService.getUserProfile(id) profile = userService.getUserProfile(id)
refreshing = false
Pager( Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false), config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = { pagingSourceFactory = {
@@ -50,6 +55,7 @@ object AccountProfileViewModel : ViewModel() {
} }
} }
} }
fun followUser(userId: String) { fun followUser(userId: String) {
viewModelScope.launch { viewModelScope.launch {
userService.followUser(userId) userService.followUser(userId)
@@ -60,9 +66,11 @@ object AccountProfileViewModel : ViewModel() {
fun unFollowUser(userId: String) { fun unFollowUser(userId: String) {
viewModelScope.launch { viewModelScope.launch {
userService.unFollowUser(userId) userService.unFollowUser(userId)
profile = profile?.copy(followerCount = profile!!.followerCount - 1, isFollowing = false) profile =
profile?.copy(followerCount = profile!!.followerCount - 1, isFollowing = false)
} }
} }
val followerCount get() = profile?.followerCount ?: 0 val followerCount get() = profile?.followerCount ?: 0
val followingCount get() = profile?.followingCount ?: 0 val followingCount get() = profile?.followingCount ?: 0
val bio get() = profile?.bio ?: "" val bio get() = profile?.bio ?: ""