This commit is contained in:
2024-08-11 21:17:02 +08:00
parent 19527f17c3
commit 322a4320c7
9 changed files with 160 additions and 31 deletions

View File

@@ -0,0 +1,6 @@
package com.aiosman.riderpro
object ConstVars {
// api 地址
const val BASE_SERVER = "http://192.168.31.57:8088"
}

View File

@@ -1,9 +1,16 @@
package com.aiosman.riderpro.data
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.LoginUserRequestBody
import com.aiosman.riderpro.data.api.RegisterRequestBody
import com.aiosman.riderpro.test.TestDatabase
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
data class AccountProfileEntity(
val id: Int,
@@ -15,6 +22,7 @@ data class AccountProfileEntity(
val country: String,
val isFollowing: Boolean
)
//{
// "id": 1,
// "username": "root",
@@ -23,7 +31,7 @@ data class AccountProfileEntity(
// "followingCount": 1,
// "followerCount": 0
//}
data class AccountProfile (
data class AccountProfile(
val id: Int,
val username: String,
val nickname: String,
@@ -38,13 +46,14 @@ data class AccountProfile (
followerCount = followerCount,
followingCount = followingCount,
nickName = nickname,
avatar = ApiClient.BASE_SERVER + avatar,
avatar = ApiClient.BASE_SERVER + avatar + "?token=${AppStore.token}",
bio = "",
country = "Worldwide",
isFollowing = isFollowing
)
}
}
interface AccountService {
suspend fun getMyAccountProfile(): AccountProfileEntity
suspend fun getAccountProfileById(id: Int): AccountProfileEntity
@@ -52,7 +61,7 @@ interface AccountService {
suspend fun loginUserWithPassword(loginName: String, password: String): UserAuth
suspend fun logout()
suspend fun updateAvatar(uri: String)
suspend fun updateProfile(nickName: String, bio: String)
suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?)
suspend fun registerUserWithPassword(loginName: String, password: String)
}
@@ -95,14 +104,17 @@ class TestAccountServiceImpl : AccountService {
}
}
override suspend fun updateProfile(nickName: String, bio: String) {
TestDatabase.accountData = TestDatabase.accountData.map {
if (it.id == 1) {
it.copy(nickName = nickName, bio = bio)
} else {
it
}
fun createMultipartBody(file: File, filename:String,name: String): MultipartBody.Part {
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
return MultipartBody.Part.createFormData(name, filename, requestFile)
}
override suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?) {
val nicknameField: RequestBody? = nickName?.toRequestBody("text/plain".toMediaTypeOrNull())
val avatarField: MultipartBody.Part? = avatar?.let {
createMultipartBody(it.file,it.filename, "avatar")
}
ApiClient.api.updateProfile(avatarField, nicknameField)
}
override suspend fun registerUserWithPassword(loginName: String, password: String) {

View File

@@ -2,6 +2,7 @@ package com.aiosman.riderpro.data
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.CommentRequestBody
import com.aiosman.riderpro.test.TestDatabase
@@ -52,7 +53,7 @@ data class Comment(
likes = likeCount,
replies = emptyList(),
postId = 0,
avatar = ApiClient.BASE_SERVER + user.avatar,
avatar = ApiClient.BASE_SERVER + user.avatar + "?token=${AppStore.token}",
author = user.id,
liked = isLiked
)

View File

@@ -43,9 +43,10 @@ data class Moment(
val time: String
) {
fun toMomentItem(): MomentEntity {
val avatar = ApiClient.BASE_SERVER + user.avatar + "?token=${AppStore.token}"
return MomentEntity(
id = id.toInt(),
avatar = ApiClient.BASE_SERVER + user.avatar,
avatar = ApiClient.BASE_SERVER + user.avatar + "?token=${AppStore.token}",
nickname = user.nickName,
location = "Worldwide",
time = time,

View File

@@ -1,6 +1,7 @@
package com.aiosman.riderpro.data.api
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.ConstVars
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
@@ -16,7 +17,7 @@ class AuthInterceptor() : Interceptor {
}
object ApiClient {
const val BASE_SERVER = "http://192.168.31.57:8088"
const val BASE_SERVER = ConstVars.BASE_SERVER
const val BASE_API_URL = "${BASE_SERVER}/api/v1"
const val RETROFIT_URL = "${BASE_API_URL}/"
private val okHttpClient: OkHttpClient by lazy {

View File

@@ -13,6 +13,7 @@ import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
@@ -128,6 +129,13 @@ interface RiderProAPI {
@GET("account/my")
suspend fun getMyAccount(): Response<DataContainer<AccountProfile>>
@Multipart
@PATCH("account/my/profile")
suspend fun updateProfile(
@Part avatar: MultipartBody.Part?,
@Part("nickname") nickname: RequestBody?,
): Response<Unit>
@GET("profile/{id}")
suspend fun getAccountProfileById(
@Path("id") id: Int

View File

@@ -2,6 +2,8 @@ package com.aiosman.riderpro.ui.account
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
@@ -29,14 +31,17 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.aiosman.riderpro.data.AccountProfileEntity
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UploadImage
import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.post.NewPostViewModel.uriToFile
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@@ -46,12 +51,14 @@ fun AccountEditScreen() {
val accountService: AccountService = TestAccountServiceImpl()
var name by remember { mutableStateOf("") }
var bio by remember { mutableStateOf("") }
var imageUrl by remember { mutableStateOf<Uri?>(null) }
var profile by remember {
mutableStateOf<AccountProfileEntity?>(
null
)
}
val scope = rememberCoroutineScope()
val context = LocalContext.current
suspend fun reloadProfile() {
accountService.getMyAccountProfile().let {
@@ -72,7 +79,25 @@ fun AccountEditScreen() {
}
fun updateUserProfile() {
scope.launch {
accountService.updateProfile(name, bio)
val newAvatar = imageUrl?.let {
val cursor = context.contentResolver.query(it, null, null, null, null)
var newAvatar: 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()}")
newAvatar = UploadImage(file, displayName, it.toString(), extension)
}
}
newAvatar
}
val newName = if (name == profile?.nickName) null else name
accountService.updateProfile(newAvatar, newName, bio)
reloadProfile()
}
}
@@ -83,7 +108,7 @@ fun AccountEditScreen() {
if (result.resultCode == Activity.RESULT_OK) {
val uri = result.data?.data
uri?.let {
updateUserAvatar(it.toString())
imageUrl = it
}
}
}
@@ -120,7 +145,11 @@ fun AccountEditScreen() {
horizontalAlignment = Alignment.CenterHorizontally
) {
AsyncImage(
it.avatar,
if (imageUrl != null) {
imageUrl.toString()
} else {
it.avatar
},
contentDescription = null,
modifier = Modifier
.size(100.dp)

View File

@@ -297,7 +297,7 @@ fun MomentTopRowGroup(momentEntity: MomentEntity) {
verticalAlignment = Alignment.CenterVertically
) {
MomentName(momentEntity.nickname)
MomentFollowBtn()
// MomentFollowBtn()
}
Row(
modifier = Modifier

View File

@@ -84,6 +84,8 @@ import com.aiosman.riderpro.data.TestCommentServiceImpl
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.TestAccountServiceImpl
import com.aiosman.riderpro.data.TestMomentServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl
import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.model.MomentEntity
import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
@@ -103,6 +105,7 @@ class PostViewModel(
) : ViewModel() {
var service: MomentService = TestMomentServiceImpl()
var commentService: CommentService = TestCommentServiceImpl()
var userService : UserService = TestUserServiceImpl()
private var _commentsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty())
val commentsFlow = _commentsFlow.asStateFlow()
@@ -138,7 +141,7 @@ class PostViewModel(
val currentPagingData = commentsFlow.value
val updatedPagingData = currentPagingData.map { comment ->
if (comment.id == commentId) {
comment.copy(liked = !comment.liked)
comment.copy(liked = !comment.liked, likes = comment.likes + 1)
} else {
comment
}
@@ -151,7 +154,7 @@ class PostViewModel(
val currentPagingData = commentsFlow.value
val updatedPagingData = currentPagingData.map { comment ->
if (comment.id == commentId) {
comment.copy(liked = !comment.liked)
comment.copy(liked = !comment.liked, likes = comment.likes - 1)
} else {
comment
}
@@ -180,6 +183,39 @@ class PostViewModel(
MomentViewModel.updateDislikeMomentById(it.id)
}
}
suspend fun favoriteMoment() {
moment?.let {
service.favoriteMoment(it.id)
moment =
moment?.copy(favoriteCount = moment?.favoriteCount?.plus(1) ?: 0, isFavorite = true)
}
}
suspend fun unfavoriteMoment() {
moment?.let {
service.unfavoriteMoment(it.id)
moment = moment?.copy(
favoriteCount = moment?.favoriteCount?.minus(1) ?: 0,
isFavorite = false
)
}
}
suspend fun followUser() {
accountProfileEntity?.let {
userService.followUser(it.id.toString())
accountProfileEntity = accountProfileEntity?.copy(isFollowing = true)
}
}
suspend fun unfollowUser() {
accountProfileEntity?.let {
userService.unFollowUser(it.id.toString())
accountProfileEntity = accountProfileEntity?.copy(isFollowing = false)
}
}
}
@Composable
@@ -224,6 +260,15 @@ fun PostScreen(
commentsPagging.refresh()
}
},
onFavoriteClick = {
scope.launch {
if (viewModel.moment?.isFavorite == true) {
viewModel.unfavoriteMoment()
} else {
viewModel.favoriteMoment()
}
}
},
momentEntity = viewModel.moment
)
}
@@ -236,7 +281,17 @@ fun PostScreen(
Header(
avatar = viewModel.moment?.avatar,
nickname = viewModel.moment?.nickname,
userId = viewModel.moment?.authorId
userId = viewModel.moment?.authorId,
isFollowing = viewModel.accountProfileEntity?.isFollowing ?: false,
onFollowClick = {
scope.launch {
if (viewModel.accountProfileEntity?.isFollowing == true) {
viewModel.unfollowUser()
} else {
viewModel.followUser()
}
}
}
)
Column(modifier = Modifier.animateContentSize()) {
AnimatedVisibility(visible = showCollapseContent) {
@@ -290,7 +345,13 @@ fun PostScreen(
}
@Composable
fun Header(avatar: String?, nickname: String?, userId: Int?) {
fun Header(
avatar: String?,
nickname: String?,
userId: Int?,
isFollowing: Boolean,
onFollowClick: () -> Unit
) {
val navController = LocalNavController.current
Row(
modifier = Modifier
@@ -336,7 +397,9 @@ fun Header(avatar: String?, nickname: String?, userId: Int?) {
modifier = Modifier
.height(20.dp)
.wrapContentWidth()
.padding(start = 6.dp),
.padding(start = 6.dp).noRippleClickable {
onFollowClick()
},
contentAlignment = Alignment.Center
) {
Image(
@@ -345,7 +408,7 @@ fun Header(avatar: String?, nickname: String?, userId: Int?) {
contentDescription = ""
)
Text(
text = "FOLLOW",
text = if (isFollowing) "Following" else "Follow",
fontSize = 12.sp,
color = Color.White,
style = TextStyle(fontWeight = FontWeight.Bold)
@@ -530,6 +593,7 @@ fun CommentItem(commentEntity: CommentEntity, onLike: () -> Unit = {}) {
fun BottomNavigationBar(
onCreateComment: (String) -> Unit = {},
onLikeClick: () -> Unit = {},
onFavoriteClick: () -> Unit = {},
momentEntity: MomentEntity?
) {
val systemUiController = rememberSystemUiController()
@@ -593,15 +657,22 @@ fun BottomNavigationBar(
}
Text(text = momentEntity?.likeCount.toString())
IconButton(
onClick = { /*TODO*/ }) {
Icon(Icons.Filled.Star, contentDescription = "Send")
onClick = {
onFavoriteClick()
}
) {
Icon(
Icons.Filled.Star,
contentDescription = "Favourite",
tint = if (momentEntity?.isFavorite == true) Color.Red else Color.Gray
)
}
Text(text = "2077")
IconButton(
onClick = { /*TODO*/ }) {
Icon(Icons.Filled.CheckCircle, contentDescription = "Send")
}
Text(text = "2077")
Text(text = momentEntity?.favoriteCount.toString())
// IconButton(
// onClick = { /*TODO*/ }) {
// Icon(Icons.Filled.CheckCircle, contentDescription = "Send")
// }
// Text(text = "2077")
}
BottomNavigationPlaceholder(