新增我的页面下拉刷新功能
新增个人主页为空时的文案 发布动态后自动刷新个人主页
This commit is contained in:
@@ -30,21 +30,7 @@ object MomentViewModel : ViewModel() {
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
var existsException = mutableStateOf(false)
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
// 获取当前用户信息
|
||||
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
|
||||
}
|
||||
}
|
||||
refreshPager()
|
||||
}
|
||||
fun refreshPager() {
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -32,14 +33,18 @@ import kotlinx.coroutines.launch
|
||||
object MyProfileViewModel : ViewModel() {
|
||||
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()
|
||||
fun loadProfile() {
|
||||
var refreshing by mutableStateOf(false)
|
||||
fun loadProfile(pullRefresh: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
if (pullRefresh){
|
||||
refreshing = true
|
||||
}
|
||||
profile = accountService.getMyAccountProfile()
|
||||
val profile = accountService.getMyAccountProfile()
|
||||
refreshing = false
|
||||
Pager(
|
||||
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
||||
pagingSourceFactory = {
|
||||
@@ -51,6 +56,7 @@ object MyProfileViewModel : ViewModel() {
|
||||
).flow.cachedIn(viewModelScope).collectLatest {
|
||||
_momentsFlow.value = it
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,12 @@ package com.aiosman.riderpro.ui.index.tabs.profile
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.asPaddingValues
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.widthIn
|
||||
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.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -71,23 +67,19 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.aiosman.riderpro.LocalAnimatedContentScope
|
||||
import com.aiosman.riderpro.LocalNavController
|
||||
import com.aiosman.riderpro.LocalSharedTransitionScope
|
||||
import com.aiosman.riderpro.R
|
||||
import com.aiosman.riderpro.entity.AccountProfileEntity
|
||||
import com.aiosman.riderpro.exp.formatPostTime
|
||||
import com.aiosman.riderpro.entity.MomentEntity
|
||||
import com.aiosman.riderpro.exp.formatPostTime2
|
||||
import com.aiosman.riderpro.ui.NavigationRoute
|
||||
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.post.PostViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ProfilePage() {
|
||||
val model = MyProfileViewModel
|
||||
@@ -98,6 +90,9 @@ fun ProfilePage() {
|
||||
val moments = model.momentsFlow.collectAsLazyPagingItems()
|
||||
val navController: NavController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
|
||||
model.loadProfile(pullRefresh = true)
|
||||
})
|
||||
val context = LocalContext.current
|
||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||
val pickBannerImageLauncher = rememberLauncherForActivityResult(
|
||||
@@ -110,7 +105,7 @@ fun ProfilePage() {
|
||||
}
|
||||
}
|
||||
}
|
||||
LazyColumn(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0xFFF5F5F5))
|
||||
@@ -120,148 +115,154 @@ fun ProfilePage() {
|
||||
.toDp() + 48.dp
|
||||
da
|
||||
})
|
||||
.pullRefresh(state)
|
||||
) {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(400.dp)
|
||||
.noRippleClickable {
|
||||
Intent(Intent.ACTION_PICK).apply {
|
||||
type = "image/*"
|
||||
pickBannerImageLauncher.launch(this)
|
||||
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.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(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
modifier = Modifier
|
||||
.width(250.dp)
|
||||
.background(Color.White)
|
||||
) {
|
||||
Box(
|
||||
val banner = model.profile?.banner
|
||||
|
||||
if (banner != null) {
|
||||
CustomAsyncImage(
|
||||
LocalContext.current,
|
||||
banner,
|
||||
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
|
||||
.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(
|
||||
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 {
|
||||
Text("Logout", fontWeight = FontWeight.W500)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(
|
||||
painter = painterResource(id = R.mipmap.rider_pro_logout),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}) {
|
||||
Row {
|
||||
Text(stringResource(R.string.logout), fontWeight = FontWeight.W500)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(
|
||||
painter = painterResource(id = R.mipmap.rider_pro_logout),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 14.dp, horizontal = 24.dp)
|
||||
.noRippleClickable {
|
||||
expanded = false
|
||||
scope.launch {
|
||||
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
|
||||
}
|
||||
}) {
|
||||
Row {
|
||||
Text("Change password", fontWeight = FontWeight.W500)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(
|
||||
painter = painterResource(id = R.mipmap.rider_pro_change_password),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 14.dp, horizontal = 24.dp)
|
||||
.noRippleClickable {
|
||||
expanded = false
|
||||
scope.launch {
|
||||
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
|
||||
}
|
||||
}) {
|
||||
Row {
|
||||
Text(stringResource(R.string.change_password), fontWeight = FontWeight.W500)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(
|
||||
painter = painterResource(id = R.mipmap.rider_pro_change_password),
|
||||
contentDescription = "",
|
||||
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 ->
|
||||
val momentItem = moments[idx] ?: return@items
|
||||
MomentPostUnit(momentItem)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
}
|
||||
items(moments.itemCount) { idx ->
|
||||
val momentItem = moments[idx] ?: return@items
|
||||
MomentPostUnit(momentItem)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
|
||||
}
|
||||
|
||||
|
||||
@@ -541,7 +542,7 @@ fun CommunicationOperatorGroup(
|
||||
contentDescription = ""
|
||||
)
|
||||
Text(
|
||||
text = "Edit profile",
|
||||
text = stringResource(R.string.edit_profile),
|
||||
fontSize = 14.sp,
|
||||
color = Color.Black,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -619,7 +620,7 @@ fun RidingStyleItem(styleContent: String) {
|
||||
|
||||
@Composable
|
||||
fun EmptyMomentPostUnit() {
|
||||
TimeGroup("You haven't left any tracks yet")
|
||||
TimeGroup(stringResource(R.string.empty_my_post_title))
|
||||
ProfileEmptyMomentCard()
|
||||
}
|
||||
|
||||
@@ -660,7 +661,7 @@ fun ProfileEmptyMomentCard(
|
||||
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))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.aiosman.riderpro.data.MomentService
|
||||
import com.aiosman.riderpro.entity.MomentServiceImpl
|
||||
import com.aiosman.riderpro.data.UploadImage
|
||||
import com.aiosman.riderpro.entity.MomentEntity
|
||||
import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel
|
||||
import com.aiosman.riderpro.ui.modification.Modification
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -75,6 +76,8 @@ object NewPostViewModel : ViewModel() {
|
||||
index += 1
|
||||
}
|
||||
momentService.createMoment(textContent, 1, uploadImageList, relPostId)
|
||||
// 刷新个人动态
|
||||
MyProfileViewModel.loadProfile()
|
||||
}
|
||||
|
||||
suspend fun init() {
|
||||
|
||||
@@ -45,4 +45,9 @@
|
||||
<string name="error_unknown">服务端未知错误</string>
|
||||
<string name="error_not_accept_recive_notice">为了为您提供更个性化的服务,请允许我们向您推送相关信息。</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>
|
||||
@@ -44,4 +44,9 @@
|
||||
<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_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>
|
||||
Reference in New Issue
Block a user