更新
This commit is contained in:
6
app/src/main/java/com/aiosman/riderpro/Const.kt
Normal file
6
app/src/main/java/com/aiosman/riderpro/Const.kt
Normal file
@@ -0,0 +1,6 @@
|
||||
package com.aiosman.riderpro
|
||||
|
||||
object ConstVars {
|
||||
// api 地址
|
||||
const val BASE_SERVER = "http://192.168.31.57:8088"
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -297,7 +297,7 @@ fun MomentTopRowGroup(momentEntity: MomentEntity) {
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
MomentName(momentEntity.nickname)
|
||||
MomentFollowBtn()
|
||||
// MomentFollowBtn()
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user