更新个人主页

This commit is contained in:
2024-09-29 23:17:59 +08:00
parent 2497698f27
commit 2c6af2ad94
18 changed files with 863 additions and 2483 deletions

View File

@@ -0,0 +1,18 @@
package com.aiosman.riderpro.data
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.DictItem
interface DictService {
/**
* 获取字典项
*/
suspend fun getDictByKey(key: String): DictItem
}
class DictServiceImpl : DictService {
override suspend fun getDictByKey(key: String): DictItem {
val resp = ApiClient.api.getDict(key)
return resp.body()?.data ?: throw Exception("failed to get dict")
}
}

View File

@@ -119,6 +119,15 @@ data class AppConfig(
val trtcAppId: Int,
)
data class DictItem(
@SerializedName("key")
val key: String,
@SerializedName("value")
val value: String,
@SerializedName("desc")
val desc: String,
)
interface RiderProAPI {
@POST("register")
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
@@ -322,4 +331,9 @@ interface RiderProAPI {
@GET("app/info")
suspend fun getAppConfig(): Response<DataContainer<AppConfig>>
@GET("dict")
suspend fun getDict(
@Query("key") key: String
): Response<DataContainer<DictItem>>
}

View File

@@ -53,7 +53,7 @@ import com.aiosman.riderpro.ui.modification.EditModificationScreen
import com.aiosman.riderpro.ui.post.NewPostImageGridScreen
import com.aiosman.riderpro.ui.post.NewPostScreen
import com.aiosman.riderpro.ui.post.PostScreen
import com.aiosman.riderpro.ui.profile.AccountProfile
import com.aiosman.riderpro.ui.profile.AccountProfileV2
sealed class NavigationRoute(
val route: String,
@@ -240,7 +240,7 @@ fun NavigationController(
CompositionLocalProvider(
LocalAnimatedContentScope provides this,
) {
AccountProfile(it.arguments?.getString("id")!!)
AccountProfileV2(it.arguments?.getString("id")!!)
}
}
composable(
@@ -363,7 +363,6 @@ fun NavigationController(
}
}
}
@OptIn(ExperimentalSharedTransitionApi::class)
@@ -394,7 +393,7 @@ fun Navigation(
fun NavHostController.navigateToPost(
id: Int,
highlightCommentId: Int? = 0,
initImagePagerIndex: Int? = null
initImagePagerIndex: Int? = 0
) {
navigate(
route = NavigationRoute.Post.route

View File

@@ -3,7 +3,6 @@ package com.aiosman.riderpro.ui.composables
import android.content.Context
import android.graphics.Bitmap
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -13,15 +12,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import coil.ImageLoader
import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import coil.compose.rememberImagePainter
import coil.request.ImageRequest
import coil.request.SuccessResult
import com.aiosman.riderpro.utils.BlurHashDecoder
import com.aiosman.riderpro.utils.Utils.getImageLoader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

View File

@@ -0,0 +1,144 @@
package com.aiosman.riderpro.ui.composables
import android.net.http.SslError
import android.webkit.SslErrorHandler
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import com.aiosman.riderpro.data.DictService
import com.aiosman.riderpro.data.DictServiceImpl
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable
fun PolicyCheckbox(
checked: Boolean = false,
error: Boolean = false,
onCheckedChange: (Boolean) -> Unit,
) {
var showModal by remember { mutableStateOf(false) }
var modalSheetState = androidx.compose.material3.rememberModalBottomSheetState(
skipPartiallyExpanded = true,
)
var scope = rememberCoroutineScope()
val dictService: DictService = DictServiceImpl()
var policyUrl by remember { mutableStateOf("") }
fun openPolicyModel() {
scope.launch {
try {
val resp = dictService.getDictByKey("private_policy")
policyUrl = resp.value
showModal = true
} catch (e: Exception) {
e.printStackTrace()
}
}
}
if (showModal) {
ModalBottomSheet(
onDismissRequest = {
showModal = false
},
sheetState = modalSheetState,
windowInsets = WindowInsets(0),
containerColor = Color.White,
) {
WebViewDisplay(
url = policyUrl
)
}
}
Row {
Checkbox(
checked = checked,
onCheckedChange = {
onCheckedChange(it)
},
size = 16
)
val text = buildAnnotatedString {
append("I agree to the ")
withStyle(style = SpanStyle(color = if (error) Color.Red else Color.Black)) {
append("terms and conditions")
}
addStyle(
style = SpanStyle(
color = Color.Blue,
textDecoration = TextDecoration.Underline
),
start = "I agree to the ".length,
end = "I agree to the terms and conditions".length
)
}
ClickableText(
text = text,
modifier = Modifier.padding(start = 8.dp),
onClick = {
openPolicyModel()
},
style = TextStyle(
fontSize = 12.sp,
color = if (error) Color.Red else Color.Black
)
)
}
}
@Composable
fun WebViewDisplay(modifier: Modifier = Modifier, url: String) {
LazyColumn(
modifier = modifier.fillMaxSize()
) {
item {
AndroidView(
factory = { context ->
WebView(context).apply {
webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
handler?.proceed() // 忽略证书错误
}
}
settings.apply {
domStorageEnabled = true
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
}
loadUrl(url)
}
},
modifier = modifier.fillMaxSize()
)
}
}
}

View File

@@ -33,9 +33,7 @@ import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.index.tabs.add.AddPage
import com.aiosman.riderpro.ui.index.tabs.message.NotificationsScreen
import com.aiosman.riderpro.ui.index.tabs.moment.MomentsList
import com.aiosman.riderpro.ui.index.tabs.profile.v2.Profile2
import com.aiosman.riderpro.ui.index.tabs.profile.v2.Profile4
import com.aiosman.riderpro.ui.index.tabs.profile.v2.ProfilePage
import com.aiosman.riderpro.ui.index.tabs.profile.ProfileWrap
import com.aiosman.riderpro.ui.index.tabs.search.DiscoverScreen
import com.aiosman.riderpro.ui.index.tabs.shorts.ShortVideo
import com.aiosman.riderpro.ui.index.tabs.street.StreetPage
@@ -209,7 +207,7 @@ fun Profile() {
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Profile2()
ProfileWrap()
}
}

View File

@@ -17,7 +17,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
@@ -27,7 +26,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
@@ -37,21 +35,14 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewModelScope
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.CommentEntity
import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
import com.aiosman.riderpro.ui.favourite.FavouriteNoticeViewModel
import com.aiosman.riderpro.ui.follower.FollowerNoticeViewModel
import com.aiosman.riderpro.ui.like.LikeNoticeViewModel
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.navigateToChat
import com.aiosman.riderpro.ui.navigateToPost
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch

View File

@@ -5,7 +5,6 @@ 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
@@ -19,7 +18,6 @@ import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.UploadImage
import com.aiosman.riderpro.data.UserServiceImpl
import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.entity.MomentPagingSource
@@ -35,23 +33,24 @@ object MyProfileViewModel : ViewModel() {
val accountService: AccountService = AccountServiceImpl()
val momentService: MomentService = MomentServiceImpl()
var profile by mutableStateOf<AccountProfileEntity?>(null)
private var _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
var momentsFlow = _momentsFlow.asStateFlow()
private var _sharedFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
var sharedFlow = _sharedFlow.asStateFlow()
var refreshing by mutableStateOf(false)
var firstLoad = true
fun loadProfile(pullRefresh: Boolean = false) {
// if (!firstLoad && !pullRefresh) {
// return
// }
if (!firstLoad) return
viewModelScope.launch {
if (pullRefresh){
if (pullRefresh) {
refreshing = true
}
firstLoad = false
val profile = accountService.getMyAccountProfile()
this@MyProfileViewModel.profile = profile
MyProfileViewModel.profile = profile
refreshing = false
try {
// Collect shared flow
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
@@ -61,13 +60,11 @@ object MyProfileViewModel : ViewModel() {
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_momentsFlow.value = it
_sharedFlow.value = it
}
}catch (e: Exception){
} catch (e: Exception) {
Log.e("MyProfileViewModel", "loadProfile: ", e)
}
}
}
@@ -76,7 +73,6 @@ object MyProfileViewModel : ViewModel() {
token = null
rememberMe = false
saveData()
}
AppState.ReloadAppState()
}
@@ -115,8 +111,7 @@ object MyProfileViewModel : ViewModel() {
fun ResetModel() {
profile = null
_momentsFlow.value = PagingData.empty()
_sharedFlow.value = PagingData.empty()
firstLoad = true
}
}

View File

@@ -1,116 +0,0 @@
package com.aiosman.riderpro.ui.index.tabs.profile.v2
import android.content.Context
import android.net.Uri
import android.util.Log
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 com.aiosman.riderpro.AppState
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.UploadImage
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.entity.MomentServiceImpl
import com.aiosman.riderpro.ui.post.NewPostViewModel.uriToFile
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object MyProfileViewModel2 : 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 refreshing by mutableStateOf(false)
var firstLoad = true
fun loadProfile(pullRefresh: Boolean = false) {
viewModelScope.launch {
if (pullRefresh) {
refreshing = true
}
firstLoad = false
val profile = accountService.getMyAccountProfile()
MyProfileViewModel2.profile = profile
refreshing = false
try {
// Collect shared flow
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
MomentPagingSource(
MomentRemoteDataSource(momentService),
author = profile.id
)
}
).flow.cachedIn(viewModelScope).collectLatest {
_sharedFlow.value = it
}
} catch (e: Exception) {
Log.e("MyProfileViewModel", "loadProfile: ", e)
}
}
}
suspend fun logout() {
AppStore.apply {
token = null
rememberMe = false
saveData()
}
AppState.ReloadAppState()
}
fun updateUserProfileBanner(bannerImageUrl: Uri?, context: Context) {
viewModelScope.launch {
var newBanner = bannerImageUrl?.let {
val cursor = context.contentResolver.query(it, null, null, null, null)
var newBanner: UploadImage? = null
cursor?.use { cur ->
if (cur.moveToFirst()) {
val displayName = cur.getString(cur.getColumnIndex("_display_name"))
val extension = displayName.substringAfterLast(".")
Log.d("NewPost", "File name: $displayName, extension: $extension")
// read as file
val file = uriToFile(context, it)
Log.d("NewPost", "File size: ${file.length()}")
newBanner = UploadImage(file, displayName, it.toString(), extension)
}
}
newBanner
}
accountService.updateProfile(
banner = newBanner,
avatar = null,
nickName = null,
bio = null
)
profile = accountService.getMyAccountProfile()
}
}
val bio get() = profile?.bio ?: ""
val nickName get() = profile?.nickName ?: ""
val avatar get() = profile?.avatar
fun ResetModel() {
profile = null
_sharedFlow.value = PagingData.empty()
firstLoad = true
}
}

View File

@@ -1,542 +0,0 @@
package com.aiosman.riderpro.ui.index.tabs.profile.v2
import android.app.Activity
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
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.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
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.navigation.NavController
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.composables.MenuItem
import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun Profile2() {
val model = MyProfileViewModel2
var expanded by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
MyProfileViewModel2.loadProfile()
}
val navController: NavController = LocalNavController.current
val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(MyProfileViewModel2.refreshing, onRefresh = {
MyProfileViewModel2.loadProfile(pullRefresh = true)
})
val context = LocalContext.current
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
val pickBannerImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val uri = result.data?.data
uri?.let {
MyProfileViewModel2.updateUserProfileBanner(it, context = context)
}
}
}
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// 处理滚动事件之前的逻辑
return Offset.Zero
}
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
// 处理滚动事件之后的逻辑
return Offset.Zero
}
}
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.padding(bottom = with(LocalDensity.current) {
val da = WindowInsets.navigationBars
.getBottom(this)
.toDp() + 48.dp
da
})
.pullRefresh(state)
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection),
) {
item {
Column(
modifier = Modifier
.fillMaxWidth()
) {
// banner
Box(
modifier = Modifier
.fillMaxWidth()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(500.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.noRippleClickable {
Intent(Intent.ACTION_PICK).apply {
type = "image/*"
pickBannerImageLauncher.launch(this)
}
}
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(
bottomStart = 32.dp,
bottomEnd = 32.dp
)
) // 添加阴影
) {
val banner = MyProfileViewModel2.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 = 8.dp,
end = 8.dp
)
) {
Box(
modifier = Modifier
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
.shadow(
elevation = 20.dp
)
.background(Color.White.copy(alpha = 0.7f))
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
modifier = Modifier.noRippleClickable {
expanded = true
},
tint = Color.Black
)
}
com.aiosman.riderpro.ui.composables.DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
width = 250,
menuItems = listOf(
MenuItem(
stringResource(R.string.logout),
R.mipmap.rider_pro_logout
) {
expanded = false
scope.launch {
MyProfileViewModel2.logout()
navController.navigate(NavigationRoute.Login.route) {
popUpTo(NavigationRoute.Index.route) {
inclusive = true
}
}
}
},
MenuItem(
stringResource(R.string.change_password),
R.mipmap.rider_pro_change_password
) {
expanded = false
scope.launch {
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
}
},
MenuItem(
stringResource(R.string.favourites),
R.drawable.rider_pro_favourite
) {
expanded = false
scope.launch {
navController.navigate(NavigationRoute.FavouriteList.route)
}
}
),
)
}
}
}
Spacer(modifier = Modifier.height(32.dp))
// 个人信息
Box(
modifier = Modifier.padding(horizontal = 16.dp)
) {
MyProfileViewModel2.profile?.let {
UserItem(it)
}
}
Spacer(modifier = Modifier.height(16.dp))
MyProfileViewModel2.profile?.let {
Box(
modifier = Modifier.padding(horizontal = 16.dp)
) {
SelfProfileAction {
navController.navigate(NavigationRoute.AccountEdit.route)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// 动态列表
}
UserContentTab(nestedScrollConnection)
}
}
PullRefreshIndicator(MyProfileViewModel2.refreshing, state, Modifier.align(Alignment.TopCenter))
}
}
@Composable
fun UserItem(accountProfileEntity: AccountProfileEntity) {
Column(
modifier = Modifier
.fillMaxWidth()
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
// 头像
CustomAsyncImage(
LocalContext.current,
accountProfileEntity.avatar,
modifier = Modifier
.clip(CircleShape)
.size(48.dp),
contentDescription = "",
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(32.dp))
//个人统计
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.weight(1f)
) {
Text(
text = accountProfileEntity.followerCount.toString(),
fontWeight = FontWeight.W600,
fontSize = 16.sp
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.followers_upper),
)
}
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = accountProfileEntity.followingCount.toString(),
fontWeight = FontWeight.W600,
fontSize = 16.sp
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.following_upper),
)
}
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
}
}
}
Spacer(modifier = Modifier.height(12.dp))
// 昵称
Text(
text = accountProfileEntity.nickName,
fontWeight = FontWeight.W600,
fontSize = 16.sp,
)
Spacer(modifier = Modifier.height(4.dp))
// 个人简介
Text(
text = accountProfileEntity.bio,
fontSize = 14.sp,
color = Color.Gray
)
}
}
@Composable
fun SelfProfileAction(
onEditProfile: () -> Unit
) {
// 按钮
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(32.dp))
.background(Color(0xffebebeb))
.padding(horizontal = 16.dp, vertical = 4.dp)
.noRippleClickable {
onEditProfile()
}
) {
Icon(
Icons.Default.Edit,
contentDescription = "",
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = stringResource(R.string.edit_profile),
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = Color.Black,
modifier = Modifier.padding(8.dp)
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun UserContentTab(nestedScrollConnection: NestedScrollConnection) {
val pagerState = rememberPagerState(pageCount = { 2 })
val scope = rememberCoroutineScope()
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(32.dp))
.background(if (pagerState.currentPage == 0) Color(0xFFFFFFFF) else Color.Transparent)
.padding(horizontal = 16.dp, vertical = 4.dp)
.noRippleClickable {
// switch to gallery
scope.launch {
pagerState.scrollToPage(0)
}
}
) {
Text(
text = "Gallery",
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = Color.Black,
modifier = Modifier.padding(8.dp)
)
}
Spacer(modifier = Modifier.width(4.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(32.dp))
.background(if (pagerState.currentPage == 1) Color(0xFFFFFFFF) else Color.Transparent)
.padding(horizontal = 16.dp, vertical = 8.dp)
.noRippleClickable {
// switch to moments
scope.launch {
pagerState.scrollToPage(1)
}
}
) {
Text(
text = "Moments",
fontSize = 16.sp,
fontWeight = FontWeight.W600,
color = Color.Black,
modifier = Modifier.padding(8.dp)
)
}
}
Spacer(modifier = Modifier.height(16.dp))
HorizontalPager(
state = pagerState,
modifier = Modifier
.padding(0.dp)
.height(900.dp),
beyondBoundsPageCount = 2,
verticalAlignment = Alignment.Top
) { page ->
when (page) {
0 -> Gallery()
1 -> MomentsList(nestedScrollConnection)
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun Gallery() {
val moments = MyProfileViewModel2.sharedFlow.collectAsLazyPagingItems()
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val itemWidth = (screenWidth / 2) - 16.dp - 2.dp
FlowRow(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
maxItemsInEachRow = 2,
) {
for (idx in 0 until moments.itemCount) {
val moment = moments[idx]
moment?.let {
Box(
modifier = Modifier
.width(itemWidth)
.aspectRatio(1f)
.background(Color.Gray)
) {
CustomAsyncImage(
LocalContext.current,
it.images[0].thumbnail,
modifier = Modifier
.fillMaxSize(),
contentDescription = "",
contentScale = ContentScale.Crop
)
}
}
}
BottomNavigationPlaceholder()
}
}
@Composable
fun MomentsList(nestedScrollConnection: NestedScrollConnection) {
val moments = MyProfileViewModel2.sharedFlow.collectAsLazyPagingItems()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.height(900.dp).nestedScroll(nestedScrollConnection),
) {
item {
for (idx in 0 until moments.itemCount) {
val moment = moments[idx]
moment?.let {
MomentPostUnit(it)
}
}
}
}
}

View File

@@ -1,134 +0,0 @@
package com.aiosman.riderpro.ui.index.tabs.profile.v2
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun Profile3() {
Scaffold(
) { it
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
var headHeight by remember { mutableStateOf(400.dp) }
val speedFactor = 2 // 调整速度因子
// val animatedHeadHeight by animateDpAsState(
// targetValue = headHeight,
// animationSpec = tween(durationMillis = 0)
// )
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = available.y
val newHeight = (headHeight + delta.dp).coerceIn(0.dp, 400.dp)
headHeight = newHeight
return Offset.Zero
}
}
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection)
) {
item {
// head
Box(
modifier = Modifier
.fillMaxWidth()
.height(headHeight)
.background(Color.DarkGray)
)
}
item {
LazyColumn(
modifier = Modifier.fillMaxWidth().height(screenHeight),
) {
items(100) { idx ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(64.dp)
.background(Color.Green)
) {
Text("Item $idx")
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}
// item {
// HorizontalPager(
// state = rememberPagerState(pageCount = { 2 }),
// modifier = Modifier
// .fillMaxWidth()
// .height(screenHeight)
// ) { page ->
// when (page) {
// 0 -> {
// LazyVerticalGrid(
// columns = GridCells.Fixed(2),
// verticalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp),
// horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp),
// modifier = Modifier.fillMaxSize(),
//// userScrollEnabled = headHeight < 2.dp
// ) {
// items(100) { idx ->
// Box(
// modifier = Modifier
// .fillMaxWidth()
// .height(64.dp)
// ) {
// Text("Item $idx")
// }
// }
// }
// }
// 1 -> {
// LazyColumn(
// modifier = Modifier.fillMaxSize(),
//// userScrollEnabled = headHeight < 2.dp
// ) {
// items(100) { idx ->
// Box(
// modifier = Modifier
// .fillMaxWidth()
// .height(64.dp)
// .background(Color.Green)
// ) {
// Text("Item $idx")
// }
// Spacer(modifier = Modifier.height(8.dp))
// }
// }
// }
// }
// }
// }
}
}
}

View File

@@ -1,134 +0,0 @@
package com.aiosman.riderpro.ui.index.tabs.profile.v2
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun Profile4() {
Scaffold(
) { it
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
var headHeight by remember { mutableStateOf(400.dp) }
val speedFactor = 2 // 调整速度因子
// val animatedHeadHeight by animateDpAsState(
// targetValue = headHeight,
// animationSpec = tween(durationMillis = 0)
// )
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = available.y
val newHeight = (headHeight + delta.dp).coerceIn(0.dp, 400.dp)
headHeight = newHeight
return Offset.Zero
}
}
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection)
) {
item {
// head
Box(
modifier = Modifier
.fillMaxWidth()
.height(headHeight)
.background(Color.DarkGray)
)
}
item {
LazyColumn(
modifier = Modifier.fillMaxWidth().height(screenHeight),
) {
items(100) { idx ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(64.dp)
.background(Color.Green)
) {
Text("Item $idx")
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}
// item {
// HorizontalPager(
// state = rememberPagerState(pageCount = { 2 }),
// modifier = Modifier
// .fillMaxWidth()
// .height(screenHeight)
// ) { page ->
// when (page) {
// 0 -> {
// LazyVerticalGrid(
// columns = GridCells.Fixed(2),
// verticalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp),
// horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp),
// modifier = Modifier.fillMaxSize(),
//// userScrollEnabled = headHeight < 2.dp
// ) {
// items(100) { idx ->
// Box(
// modifier = Modifier
// .fillMaxWidth()
// .height(64.dp)
// ) {
// Text("Item $idx")
// }
// }
// }
// }
// 1 -> {
// LazyColumn(
// modifier = Modifier.fillMaxSize(),
//// userScrollEnabled = headHeight < 2.dp
// ) {
// items(100) { idx ->
// Box(
// modifier = Modifier
// .fillMaxWidth()
// .height(64.dp)
// .background(Color.Green)
// ) {
// Text("Item $idx")
// }
// Spacer(modifier = Modifier.height(8.dp))
// }
// }
// }
// }
// }
// }
}
}
}

View File

@@ -1,879 +0,0 @@
package com.aiosman.riderpro.ui.index.tabs.profile.v2
import android.app.Activity
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.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.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.aiosman.riderpro.AppState
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.AccountProfileEntity
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.MenuItem
import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.navigateToChat
import com.aiosman.riderpro.ui.navigateToPost
import com.aiosman.riderpro.ui.post.NewPostViewModel
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ProfilePage() {
val model = MyProfileViewModel2
var expanded by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
MyProfileViewModel.loadProfile()
}
val navController: NavController = LocalNavController.current
val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(MyProfileViewModel.refreshing, onRefresh = {
MyProfileViewModel.loadProfile(pullRefresh = true)
})
val context = LocalContext.current
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
val pickBannerImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val uri = result.data?.data
uri?.let {
MyProfileViewModel.updateUserProfileBanner(it, context = context)
}
}
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.padding(bottom = with(LocalDensity.current) {
val da = WindowInsets.navigationBars
.getBottom(this)
.toDp() + 48.dp
da
})
.pullRefresh(state)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(400.dp)
.noRippleClickable {
Intent(Intent.ACTION_PICK).apply {
type = "image/*"
pickBannerImageLauncher.launch(this)
}
}
) {
val banner = MyProfileViewModel.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 = 8.dp,
end = 8.dp
)
) {
Box(
modifier = Modifier
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
.shadow(
elevation = 20.dp
)
.background(Color.White.copy(alpha = 0.7f))
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
modifier = Modifier.noRippleClickable {
expanded = true
},
tint = Color.Black
)
}
com.aiosman.riderpro.ui.composables.DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
width = 250,
menuItems = listOf(
MenuItem(
stringResource(R.string.logout),
R.mipmap.rider_pro_logout
) {
expanded = false
scope.launch {
MyProfileViewModel.logout()
navController.navigate(NavigationRoute.Login.route) {
popUpTo(NavigationRoute.Index.route) {
inclusive = true
}
}
}
},
MenuItem(
stringResource(R.string.change_password),
R.mipmap.rider_pro_change_password
) {
expanded = false
scope.launch {
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
}
},
MenuItem(
stringResource(R.string.favourites),
R.drawable.rider_pro_favourite
) {
expanded = false
scope.launch {
navController.navigate(NavigationRoute.FavouriteList.route)
}
}
),
)
}
}
Spacer(modifier = Modifier.height(32.dp))
MyProfileViewModel.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))
}
}
PullRefreshIndicator(MyProfileViewModel.refreshing, state, Modifier.align(Alignment.TopCenter))
}
}
@Composable
fun CarGroup() {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 54.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
CarTopInformation()
CarTopPicture()
}
}
@Composable
fun CarTopInformation() {
Row {
Text(
text = "BMW",
color = Color.Black,
fontSize = 12.sp,
style = TextStyle(fontWeight = FontWeight.Bold)
)
Text(
modifier = Modifier.padding(start = 4.dp),
text = "/",
color = Color.Gray,
fontSize = 12.sp
)
Text(
modifier = Modifier.padding(start = 4.dp),
text = "M1000RR",
color = Color.Gray,
fontSize = 12.sp
)
}
}
@Composable
fun CarTopPicture() {
Image(
modifier = Modifier
.size(width = 336.dp, height = 224.dp)
.padding(top = 42.dp),
painter = painterResource(id = R.drawable.default_profile_moto), contentDescription = ""
)
}
@Composable
fun UserInformation(
isSelf: Boolean = true,
accountProfileEntity: AccountProfileEntity,
onFollowClick: () -> Unit = {},
onEditProfileClick: () -> Unit = {}
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, start = 33.dp, end = 33.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(modifier = Modifier.fillMaxWidth()) {
val userInfoModifier = Modifier.weight(1f)
UserInformationFollowers(userInfoModifier, accountProfileEntity)
UserInformationBasic(userInfoModifier, accountProfileEntity)
UserInformationFollowing(userInfoModifier, accountProfileEntity)
}
UserInformationSlogan(accountProfileEntity)
CommunicationOperatorGroup(
isSelf = isSelf,
isFollowing = accountProfileEntity.isFollowing,
onFollowClick = onFollowClick,
onEditProfileClick = onEditProfileClick,
accountProfileEntity = accountProfileEntity
)
}
}
@Composable
fun UserInformationFollowers(modifier: Modifier, accountProfileEntity: AccountProfileEntity) {
val navController = LocalNavController.current
Column(modifier = modifier.padding(top = 31.dp)) {
Text(
modifier = Modifier
.padding(bottom = 5.dp)
.noRippleClickable {
navController.navigate(
NavigationRoute.FollowerList.route.replace(
"{id}",
accountProfileEntity.id.toString()
)
)
},
text = accountProfileEntity.followerCount.toString(),
fontSize = 24.sp,
color = Color.Black,
style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold)
)
Canvas(
modifier = Modifier
.size(width = 88.83.dp, height = 1.dp)
.padding(top = 2.dp, bottom = 5.dp)
) {
drawLine(
color = Color(0xFFCCCCCC),
start = Offset(0f, 0f),
end = Offset(size.width, 0f),
strokeWidth = 1f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f)
)
}
Text(
modifier = Modifier.padding(top = 5.dp),
text = stringResource(R.string.followers_upper),
fontSize = 12.sp,
color = Color.Black,
style = TextStyle(fontWeight = FontWeight.Bold)
)
}
}
@Composable
fun UserInformationBasic(modifier: Modifier, accountProfileEntity: AccountProfileEntity) {
val context = LocalContext.current
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier.size(width = 112.dp, height = 112.dp),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.drawable.avatar_bold), contentDescription = ""
)
CustomAsyncImage(
context,
accountProfileEntity.avatar,
modifier = Modifier
.size(width = 88.dp, height = 88.dp)
.clip(
RoundedCornerShape(88.dp)
),
contentDescription = "",
contentScale = ContentScale.Crop
)
}
Text(
modifier = Modifier
.widthIn(max = 220.dp)
.padding(top = 8.dp),
text = accountProfileEntity.nickName,
fontSize = 32.sp,
color = Color.Black,
style = TextStyle(fontWeight = FontWeight.Bold),
textAlign = TextAlign.Center
)
Text(
modifier = Modifier.padding(top = 4.dp),
text = accountProfileEntity.country,
fontSize = 12.sp,
color = Color.Gray
)
}
}
@Composable
fun UserInformationFollowing(modifier: Modifier, accountProfileEntity: AccountProfileEntity) {
val navController = LocalNavController.current
Column(
modifier = modifier.padding(top = 6.dp),
horizontalAlignment = Alignment.End
) {
Text(
modifier = Modifier
.padding(bottom = 5.dp)
.noRippleClickable {
navController.navigate(
NavigationRoute.FollowingList.route.replace(
"{id}",
accountProfileEntity.id.toString()
)
)
},
text = accountProfileEntity.followingCount.toString(),
fontSize = 24.sp,
color = Color.Black,
style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold)
)
Canvas(
modifier = Modifier
.size(width = 88.83.dp, height = 1.dp)
.padding(top = 2.dp, bottom = 5.dp)
) {
drawLine(
color = Color(0xFFCCCCCC),
start = Offset(0f, 0f),
end = Offset(size.width, 0f),
strokeWidth = 1f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f)
)
}
Text(
modifier = Modifier.padding(top = 5.dp),
text = stringResource(R.string.following_upper),
fontSize = 12.sp,
color = Color.Black,
style = TextStyle(fontWeight = FontWeight.Bold)
)
}
}
@Composable
fun UserInformationSlogan(accountProfileEntity: AccountProfileEntity) {
Text(
modifier = Modifier.padding(top = 23.dp),
text = accountProfileEntity.bio,
fontSize = 13.sp,
color = Color.Black,
style = TextStyle(fontWeight = FontWeight.Bold)
)
}
@Composable
fun CommunicationOperatorGroup(
accountProfileEntity: AccountProfileEntity,
isSelf: Boolean = true,
isFollowing: Boolean = false,
onFollowClick: () -> Unit = {},
onEditProfileClick: () -> Unit = {}
) {
val navController = LocalNavController.current
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp), horizontalArrangement = Arrangement.Center
) {
if (!isSelf && AppState.UserId != accountProfileEntity.id) {
Box(
modifier = Modifier
.size(width = 142.dp, height = 40.dp)
.noRippleClickable {
onFollowClick()
},
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.fillMaxSize(),
painter = if (isFollowing) painterResource(id = R.mipmap.rider_pro_follow_grey) else painterResource(
id = R.mipmap.rider_pro_follow_red
),
contentDescription = ""
)
Text(
text = if (isFollowing) stringResource(R.string.following_upper) else stringResource(
R.string.follow_upper
),
fontSize = 14.sp,
color = if (isFollowing) Color.Black else Color.White,
style = TextStyle(fontWeight = FontWeight.W600, fontStyle = FontStyle.Italic),
)
}
Spacer(modifier = Modifier.width(16.dp))
Box(
modifier = Modifier
.size(width = 142.dp, height = 40.dp)
.noRippleClickable {
navController.navigateToChat(accountProfileEntity.id.toString())
},
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey),
contentDescription = ""
)
Text(
text = "CHAT",
fontSize = 14.sp,
color = Color.Black,
fontWeight = FontWeight.Bold,
style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)
)
}
}
if (isSelf) {
Box(
modifier = Modifier
.size(width = 142.dp, height = 40.dp)
.noRippleClickable {
onEditProfileClick()
},
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey),
contentDescription = ""
)
Text(
text = stringResource(R.string.edit_profile),
fontSize = 14.sp,
color = Color.Black,
fontWeight = FontWeight.Bold,
style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)
)
}
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun RidingStyle() {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 40.dp, end = 24.dp),
horizontalAlignment = Alignment.Start
) {
Text(
text = "RIDING STYLES",
fontSize = 18.sp,
color = Color.Black,
style = TextStyle(fontWeight = FontWeight.Bold)
)
Image(
modifier = Modifier
.padding(top = 4.dp)
.height(8.dp),
painter = painterResource(id = R.drawable.rider_pro_profile_line),
contentDescription = ""
)
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp)
) {
RidingStyleItem(styleContent = "Cruiser")
RidingStyleItem(styleContent = "Bobber")
RidingStyleItem(styleContent = "Cafe")
RidingStyleItem(styleContent = "Chopper")
RidingStyleItem(styleContent = "Sport")
RidingStyleItem(styleContent = "Vintage")
RidingStyleItem(styleContent = "Trike")
RidingStyleItem(styleContent = "Touring")
}
}
}
@Composable
fun RidingStyleItem(styleContent: String) {
Box(
modifier = Modifier.padding(bottom = 8.dp, end = 8.dp),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.shadow(
ambientColor = Color.Gray,
spotColor = Color(0f, 0f, 0f, 0.2f),
elevation = 20.dp,
),
painter = painterResource(id = R.drawable.rider_pro_style_wrapper),
contentDescription = ""
)
Text(
modifier = Modifier.padding(start = 5.dp, end = 5.dp),
text = styleContent,
fontSize = 12.sp,
color = Color.Gray,
style = TextStyle(fontWeight = FontWeight.Bold)
)
}
}
@Composable
fun EmptyMomentPostUnit() {
TimeGroup(stringResource(R.string.empty_my_post_title))
ProfileEmptyMomentCard()
}
@Composable
fun ProfileEmptyMomentCard(
) {
var columnHeight by remember { mutableStateOf(0) }
val navController = LocalNavController.current
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 18.dp, end = 24.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
) {
Canvas(
modifier = Modifier
.height(with(LocalDensity.current) { columnHeight.toDp() })
.width(14.dp)
) {
drawLine(
color = Color(0xff899DA9),
start = Offset(0f, 0f),
end = Offset(0f, size.height),
strokeWidth = 4f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f)
)
}
Spacer(modifier = Modifier.width(10.dp))
Column(
modifier = Modifier
.weight(1f)
.onGloballyPositioned { coordinates ->
columnHeight = coordinates.size.height
}
) {
Text(stringResource(R.string.empty_my_post_content), fontSize = 16.sp)
Spacer(modifier = Modifier.height(24.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(3f / 2f)
.background(Color.White)
.padding(16.dp)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.noRippleClickable {
NewPostViewModel.asNewPost()
navController.navigate(NavigationRoute.NewPost.route)
}
) {
Icon(
Icons.Default.Add,
tint = Color(0xFFD8D8D8),
contentDescription = "New post",
modifier = Modifier
.size(32.dp)
.align(Alignment.Center)
)
}
}
}
}
}
}
@Composable
fun MomentPostUnit(momentEntity: MomentEntity) {
TimeGroup(momentEntity.time.formatPostTime2())
ProfileMomentCard(
momentEntity.momentTextContent,
momentEntity.images[0].thumbnail,
momentEntity.likeCount.toString(),
momentEntity.commentCount.toString(),
momentEntity = momentEntity
)
}
@Composable
fun TimeGroup(time: String = "2024.06.08 12:23") {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 40.dp, end = 24.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier
.height(16.dp)
.width(14.dp),
painter = painterResource(id = R.drawable.rider_pro_moment_time_flag),
contentDescription = ""
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = time,
fontSize = 16.sp,
color = Color.Black,
style = TextStyle(fontWeight = FontWeight.W600)
)
}
}
@Composable
fun ProfileMomentCard(
content: String,
imageUrl: String,
like: String,
comment: String,
momentEntity: MomentEntity
) {
var columnHeight by remember { mutableStateOf(0) }
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 18.dp, end = 24.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
) {
Canvas(
modifier = Modifier
.height(with(LocalDensity.current) { columnHeight.toDp() })
.width(14.dp)
) {
drawLine(
color = Color(0xff899DA9),
start = Offset(0f, 0f),
end = Offset(0f, size.height),
strokeWidth = 4f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f)
)
}
Spacer(modifier = Modifier.width(10.dp))
Column(
modifier = Modifier
.background(Color.White)
.weight(1f)
.onGloballyPositioned { coordinates ->
columnHeight = coordinates.size.height
}
) {
if (content.isNotEmpty()) {
MomentCardTopContent(content)
}
MomentCardPicture(imageUrl, momentEntity = momentEntity)
MomentCardOperation(like, comment)
}
}
}
}
@Composable
fun MomentCardTopContent(content: String) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.padding(top = 16.dp, bottom = 0.dp, start = 16.dp, end = 16.dp),
text = content, fontSize = 16.sp, color = Color.Black
)
}
}
@Composable
fun MomentCardPicture(imageUrl: String, momentEntity: MomentEntity) {
val navController = LocalNavController.current
val context = LocalContext.current
CustomAsyncImage(
context,
imageUrl,
modifier = Modifier
.fillMaxSize()
.aspectRatio(3f / 2f)
.padding(top = 16.dp)
.noRippleClickable {
navController.navigateToPost(
id = momentEntity.id,
highlightCommentId = 0,
initImagePagerIndex = 0
)
},
contentDescription = "",
contentScale = ContentScale.Crop
)
}
@Composable
fun MomentCardOperation(like: String, comment: String) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically
) {
// Spacer(modifier = Modifier.weight(1f))
MomentCardOperationItem(
drawable = R.drawable.rider_pro_like,
number = like,
modifier = Modifier.padding(end = 32.dp)
)
MomentCardOperationItem(
drawable = R.drawable.rider_pro_moment_comment,
number = comment,
modifier = Modifier.padding(end = 32.dp)
)
}
}
@Composable
fun MomentCardOperationItem(@DrawableRes drawable: Int, number: String, modifier: Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier.padding(start = 16.dp, end = 8.dp),
painter = painterResource(id = drawable), contentDescription = ""
)
Text(text = number)
}
}

View File

@@ -38,6 +38,7 @@ import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.ActionButton
import com.aiosman.riderpro.ui.composables.CheckboxWithLabel
import com.aiosman.riderpro.ui.composables.PolicyCheckbox
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
import com.aiosman.riderpro.ui.composables.TextInputField
import com.aiosman.riderpro.utils.Utils
@@ -241,15 +242,21 @@ fun EmailSignupScreen() {
rememberMe = it
}
Spacer(modifier = Modifier.height(16.dp))
CheckboxWithLabel(
PolicyCheckbox(
checked = acceptTerms,
checkSize = 16,
fontSize = 12,
label = stringResource(R.string.agree_terms_of_service),
error = termsError
) {
acceptTerms = it
}
// CheckboxWithLabel(
// checked = acceptTerms,
// checkSize = 16,
// fontSize = 12,
// label = stringResource(R.string.agree_terms_of_service),
// error = termsError
// ) {
// acceptTerms = it
// }
Spacer(modifier = Modifier.height(16.dp))
CheckboxWithLabel(
checked = acceptPromotions,
@@ -263,17 +270,23 @@ fun EmailSignupScreen() {
}
Spacer(modifier = Modifier.height(64.dp))
ActionButton(
modifier = Modifier
.width(345.dp)
.height(48.dp),
text = stringResource(R.string.lets_ride_upper),
backgroundImage = R.mipmap.rider_pro_signup_red_bg
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
scope.launch(Dispatchers.IO) {
registerUser()
ActionButton(
modifier = Modifier
.width(345.dp)
.height(48.dp),
text = stringResource(R.string.lets_ride_upper),
backgroundImage = R.mipmap.rider_pro_signup_red_bg
) {
scope.launch(Dispatchers.IO) {
registerUser()
}
}
}
}
}

View File

@@ -1,155 +0,0 @@
package com.aiosman.riderpro.ui.profile
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R
import com.aiosman.riderpro.exp.viewModelFactory
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
import com.aiosman.riderpro.ui.index.tabs.profile.v2.MomentPostUnit
import com.aiosman.riderpro.ui.index.tabs.profile.v2.UserInformation
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AccountProfile(id: String) {
val model: AccountProfileViewModel = viewModel(factory = viewModelFactory {
AccountProfileViewModel()
}, key = "viewModel_${id}")
val scope = rememberCoroutineScope()
val items = model.momentsFlow.collectAsLazyPagingItems()
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.loadProfile(id, pullRefresh = true)
})
val navController = LocalNavController.current
LaunchedEffect(Unit) {
model.loadProfile(id)
}
Box(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state)
.background(Color(0xFFF5F5F5))
) {
LazyColumn(
modifier = Modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.height(400.dp)
) {
if (model.profile != null) {
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.fillMaxWidth()
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
StatusBarSpacer()
Row(
modifier = Modifier.padding(vertical = 10.dp, horizontal = 18.dp)
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_back),
contentDescription = "",
modifier = Modifier
.size(24.dp)
.noRippleClickable {
navController.popBackStack()
},
colorFilter = ColorFilter.tint(Color.White)
)
}
}
}
}
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)
}
}
},
)
}
// RidingStyle()
}
items(items.itemCount) { idx ->
val momentItem = items[idx] ?: return@items
MomentPostUnit(momentItem)
}
item {
Spacer(modifier = Modifier.height(32.dp))
}
}
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
}
}

View File

@@ -0,0 +1,39 @@
package com.aiosman.riderpro.ui.profile
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.viewmodel.compose.viewModel
import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.exp.viewModelFactory
import com.aiosman.riderpro.ui.index.tabs.profile.Profile
import com.aiosman.riderpro.ui.navigateToChat
@Composable
fun AccountProfileV2(id: String){
val model: AccountProfileViewModel = viewModel(factory = viewModelFactory {
AccountProfileViewModel()
}, key = "viewModel_${id}")
val navController = LocalNavController.current
LaunchedEffect(Unit) {
model.loadProfile(id)
}
Profile(
sharedFlow = model.momentsFlow,
profile = model.profile,
isSelf = false,
onChatClick = {
model.profile?.let {
navController.navigateToChat(it.id.toString())
}
},
onFollowClick = {
model.profile?.let {
if (it.isFollowing) {
model.unFollowUser(id)
} else {
model.followUser(id)
}
}
}
)
}

View File

@@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class AccountProfileViewModel : ViewModel() {
var profileId by mutableStateOf(0)
val accountService: AccountService = AccountServiceImpl()