整理代码

This commit is contained in:
2024-08-24 23:11:20 +08:00
parent 367d1c9f3a
commit b4004663cd
40 changed files with 898 additions and 801 deletions

View File

@@ -1,14 +1,16 @@
package com.aiosman.riderpro.data package com.aiosman.riderpro.data
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.data.api.ApiClient import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.ChangePasswordRequestBody import com.aiosman.riderpro.data.api.ChangePasswordRequestBody
import com.aiosman.riderpro.data.api.GoogleRegisterRequestBody import com.aiosman.riderpro.data.api.GoogleRegisterRequestBody
import com.aiosman.riderpro.data.api.LoginUserRequestBody import com.aiosman.riderpro.data.api.LoginUserRequestBody
import com.aiosman.riderpro.data.api.RegisterRequestBody import com.aiosman.riderpro.data.api.RegisterRequestBody
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
import com.aiosman.riderpro.test.TestDatabase import com.aiosman.riderpro.entity.AccountFavouriteEntity
import com.aiosman.riderpro.entity.AccountLikeEntity
import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.entity.NoticePostEntity
import com.aiosman.riderpro.entity.NoticeUserEntity
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody import okhttp3.MultipartBody
@@ -16,41 +18,29 @@ import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File import java.io.File
import java.io.IOException
import java.util.Date
data class AccountLikeEntity(
val post: NoticePostEntity,
val user: NoticeUserEntity,
val likeTime: Date,
)
data class AccountFavouriteEntity(
val post: NoticePostEntity,
val user: NoticeUserEntity,
val favoriteTime: Date,
)
data class AccountProfileEntity(
val id: Int,
val followerCount: Int,
val followingCount: Int,
val nickName: String,
val avatar: String,
val bio: String,
val country: String,
val isFollowing: Boolean
)
/**
* 用户资料
*/
data class AccountProfile( data class AccountProfile(
// 用户ID
val id: Int, val id: Int,
// 用户名
val username: String, val username: String,
// 昵称
val nickname: String, val nickname: String,
// 头像
val avatar: String, val avatar: String,
// 关注数
val followingCount: Int, val followingCount: Int,
// 粉丝数
val followerCount: Int, val followerCount: Int,
// 是否关注
val isFollowing: Boolean val isFollowing: Boolean
) { ) {
/**
* 转换为Entity
*/
fun toAccountProfileEntity(): AccountProfileEntity { fun toAccountProfileEntity(): AccountProfileEntity {
return AccountProfileEntity( return AccountProfileEntity(
id = id, id = id,
@@ -65,23 +55,27 @@ data class AccountProfile(
} }
} }
data class NoticePostEntity( /**
val id: Int, * 消息关联资料
val textContent: String, */
val images: List<Image>,
val time: Date,
)
data class NoticePost( data class NoticePost(
// 动态ID
@SerializedName("id") @SerializedName("id")
val id: Int, val id: Int,
// 动态内容
@SerializedName("textContent") @SerializedName("textContent")
// 动态图片
val textContent: String, val textContent: String,
// 动态图片
@SerializedName("images") @SerializedName("images")
val images: List<Image>, val images: List<Image>,
// 动态时间
@SerializedName("time") @SerializedName("time")
val time: String, val time: String,
) { ) {
/**
* 转换为Entity
*/
fun toNoticePostEntity(): NoticePostEntity { fun toNoticePostEntity(): NoticePostEntity {
return NoticePostEntity( return NoticePostEntity(
id = id, id = id,
@@ -97,20 +91,23 @@ data class NoticePost(
} }
} }
data class NoticeUserEntity( /**
val id: Int, * 消息关联用户
val nickName: String, */
val avatar: String,
)
data class NoticeUser( data class NoticeUser(
// 用户ID
@SerializedName("id") @SerializedName("id")
val id: Int, val id: Int,
// 昵称
@SerializedName("nickName") @SerializedName("nickName")
val nickName: String, val nickName: String,
// 头像
@SerializedName("avatar") @SerializedName("avatar")
val avatar: String, val avatar: String,
) { ) {
/**
* 转换为Entity
*/
fun toNoticeUserEntity(): NoticeUserEntity { fun toNoticeUserEntity(): NoticeUserEntity {
return NoticeUserEntity( return NoticeUserEntity(
id = id, id = id,
@@ -120,13 +117,20 @@ data class NoticeUser(
} }
} }
/**
* 点赞消息通知
*/
data class AccountLike( data class AccountLike(
// 是否未读
@SerializedName("isUnread") @SerializedName("isUnread")
val isUnread: Boolean, val isUnread: Boolean,
// 动态
@SerializedName("post") @SerializedName("post")
val post: NoticePost, val post: NoticePost,
// 点赞用户
@SerializedName("user") @SerializedName("user")
val user: NoticeUser, val user: NoticeUser,
// 点赞时间
@SerializedName("likeTime") @SerializedName("likeTime")
val likeTime: String, val likeTime: String,
) { ) {
@@ -192,108 +196,93 @@ data class AccountNotice(
) )
class LikeItemPagingSource(
private val accountService: AccountService,
) : PagingSource<Int, AccountLikeEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AccountLikeEntity> {
return try {
val currentPage = params.key ?: 1
val likes = accountService.getMyLikeNotice(
page = currentPage,
pageSize = 20,
)
LoadResult.Page(
data = likes.list.map {
it.toAccountLikeEntity()
},
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (likes.list.isEmpty()) null else likes.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, AccountLikeEntity>): Int? {
return state.anchorPosition
}
}
class FavoriteItemPagingSource(
private val accountService: AccountService,
) : PagingSource<Int, AccountFavouriteEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AccountFavouriteEntity> {
return try {
val currentPage = params.key ?: 1
val favouriteListContainer = accountService.getMyFavouriteNotice(
page = currentPage,
pageSize = 20,
)
LoadResult.Page(
data = favouriteListContainer.list.map {
it.toAccountFavouriteEntity()
},
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (favouriteListContainer.list.isEmpty()) null else favouriteListContainer.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, AccountFavouriteEntity>): Int? {
return state.anchorPosition
}
}
class FollowItemPagingSource(
private val accountService: AccountService,
) : PagingSource<Int, AccountFollow>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AccountFollow> {
return try {
val currentPage = params.key ?: 1
val followListContainer = accountService.getMyFollowNotice(
page = currentPage,
pageSize = 20,
)
LoadResult.Page(
data = followListContainer.list.map {
it.copy(
avatar = "${ApiClient.BASE_SERVER}${it.avatar}",
)
},
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (followListContainer.list.isEmpty()) null else followListContainer.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, AccountFollow>): Int? {
return state.anchorPosition
}
}
interface AccountService { interface AccountService {
/**
* 获取登录当前用户的资料
*/
suspend fun getMyAccountProfile(): AccountProfileEntity suspend fun getMyAccountProfile(): AccountProfileEntity
suspend fun getAccountProfileById(id: Int): AccountProfileEntity
/**
* 获取登录的用户认证信息
*/
suspend fun getMyAccount(): UserAuth suspend fun getMyAccount(): UserAuth
/**
* 使用用户名密码登录
* @param loginName 用户名
* @param password 密码
*/
suspend fun loginUserWithPassword(loginName: String, password: String): UserAuth suspend fun loginUserWithPassword(loginName: String, password: String): UserAuth
/**
* 使用google登录
* @param googleId googleId
*/
suspend fun loginUserWithGoogle(googleId: String): UserAuth suspend fun loginUserWithGoogle(googleId: String): UserAuth
/**
* 退出登录
*/
suspend fun logout() suspend fun logout()
suspend fun updateAvatar(uri: String)
/**
* 更新用户资料
* @param avatar 头像
* @param nickName 昵称
* @param bio 简介
*/
suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?) suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?)
/**
* 注册用户
* @param loginName 用户名
* @param password 密码
*/
suspend fun registerUserWithPassword(loginName: String, password: String) suspend fun registerUserWithPassword(loginName: String, password: String)
/**
* 使用google账号注册
* @param idToken googleIdToken
*/
suspend fun regiterUserWithGoogleAccount(idToken: String) suspend fun regiterUserWithGoogleAccount(idToken: String)
/**
* 修改密码
* @param oldPassword 旧密码
* @param newPassword 新密码
*/
suspend fun changeAccountPassword(oldPassword: String, newPassword: String) suspend fun changeAccountPassword(oldPassword: String, newPassword: String)
/**
* 获取我的点赞通知
* @param page 页码
* @param pageSize 每页数量
*/
suspend fun getMyLikeNotice(page: Int, pageSize: Int): ListContainer<AccountLike> suspend fun getMyLikeNotice(page: Int, pageSize: Int): ListContainer<AccountLike>
/**
* 获取我的关注通知
* @param page 页码
* @param pageSize 每页数量
*/
suspend fun getMyFollowNotice(page: Int, pageSize: Int): ListContainer<AccountFollow> suspend fun getMyFollowNotice(page: Int, pageSize: Int): ListContainer<AccountFollow>
/**
* 获取我的收藏通知
* @param page 页码
* @param pageSize 每页数量
*/
suspend fun getMyFavouriteNotice(page: Int, pageSize: Int): ListContainer<AccountFavourite> suspend fun getMyFavouriteNotice(page: Int, pageSize: Int): ListContainer<AccountFavourite>
/**
* 获取我的通知信息
*/
suspend fun getMyNoticeInfo(): AccountNotice suspend fun getMyNoticeInfo(): AccountNotice
/**
* 更新通知信息,更新最后一次查看时间
* @param payload 通知信息
*/
suspend fun updateNotice(payload: UpdateNoticeRequestBody) suspend fun updateNotice(payload: UpdateNoticeRequestBody)
} }
@@ -304,12 +293,6 @@ class AccountServiceImpl : AccountService {
return body.data.toAccountProfileEntity() return body.data.toAccountProfileEntity()
} }
override suspend fun getAccountProfileById(id: Int): AccountProfileEntity {
val resp = ApiClient.api.getAccountProfileById(id)
val body = resp.body() ?: throw ServiceException("Failed to get account")
return body.data.toAccountProfileEntity()
}
override suspend fun getMyAccount(): UserAuth { override suspend fun getMyAccount(): UserAuth {
val resp = ApiClient.api.checkToken() val resp = ApiClient.api.checkToken()
val body = resp.body() ?: throw ServiceException("Failed to get account") val body = resp.body() ?: throw ServiceException("Failed to get account")
@@ -323,7 +306,7 @@ class AccountServiceImpl : AccountService {
} }
override suspend fun loginUserWithGoogle(googleId: String): UserAuth { override suspend fun loginUserWithGoogle(googleId: String): UserAuth {
val resp = ApiClient.api.login(LoginUserRequestBody(googleId=googleId)) val resp = ApiClient.api.login(LoginUserRequestBody(googleId = googleId))
val body = resp.body() ?: throw ServiceException("Failed to login") val body = resp.body() ?: throw ServiceException("Failed to login")
return UserAuth(0, body.token) return UserAuth(0, body.token)
} }
@@ -339,15 +322,6 @@ class AccountServiceImpl : AccountService {
// do nothing // do nothing
} }
override suspend fun updateAvatar(uri: String) {
TestDatabase.accountData = TestDatabase.accountData.map {
if (it.id == 1) {
it.copy(avatar = uri)
} else {
it
}
}
}
fun createMultipartBody(file: File, filename: String, name: String): MultipartBody.Part { fun createMultipartBody(file: File, filename: String, name: String): MultipartBody.Part {
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull()) val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())

View File

@@ -1,18 +1,22 @@
package com.aiosman.riderpro.data 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.ApiClient
import com.aiosman.riderpro.data.api.CommentRequestBody import com.aiosman.riderpro.data.api.CommentRequestBody
import com.aiosman.riderpro.test.TestDatabase import com.aiosman.riderpro.entity.CommentEntity
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import java.io.IOException
import java.util.Calendar
import java.util.Date
import kotlin.math.min
/**
* 评论相关 Service
*/
interface CommentService { interface CommentService {
/**
* 获取动态
* @param pageNumber 页码
* @param postId 动态ID,过滤条件
* @param postUser 动态作者ID,获取某个用户所有动态下的评论
* @param selfNotice 是否是自己的通知
* @return 评论列表
*/
suspend fun getComments( suspend fun getComments(
pageNumber: Int, pageNumber: Int,
postId: Int? = null, postId: Int? = null,
@@ -20,32 +24,67 @@ interface CommentService {
selfNotice: Boolean? = null selfNotice: Boolean? = null
): ListContainer<CommentEntity> ): ListContainer<CommentEntity>
/**
* 创建评论
* @param postId 动态ID
* @param content 评论内容
*/
suspend fun createComment(postId: Int, content: String) suspend fun createComment(postId: Int, content: String)
/**
* 点赞评论
* @param commentId 评论ID
*/
suspend fun likeComment(commentId: Int) suspend fun likeComment(commentId: Int)
/**
* 取消点赞评论
* @param commentId 评论ID
*/
suspend fun dislikeComment(commentId: Int) suspend fun dislikeComment(commentId: Int)
/**
* 更新评论已读状态
* @param commentId 评论ID
*/
suspend fun updateReadStatus(commentId: Int) suspend fun updateReadStatus(commentId: Int)
} }
/**
* 评论
*/
data class Comment( data class Comment(
// 评论ID
@SerializedName("id") @SerializedName("id")
val id: Int, val id: Int,
// 评论内容
@SerializedName("content") @SerializedName("content")
val content: String, val content: String,
// 评论用户
@SerializedName("user") @SerializedName("user")
val user: User, val user: User,
// 点赞数
@SerializedName("likeCount") @SerializedName("likeCount")
val likeCount: Int, val likeCount: Int,
// 是否点赞
@SerializedName("isLiked") @SerializedName("isLiked")
val isLiked: Boolean, val isLiked: Boolean,
// 创建时间
@SerializedName("createdAt") @SerializedName("createdAt")
val createdAt: String, val createdAt: String,
// 动态ID
@SerializedName("postId") @SerializedName("postId")
val postId: Int, val postId: Int,
// 动态
@SerializedName("post") @SerializedName("post")
val post: NoticePost?, val post: NoticePost?,
// 是否未读
@SerializedName("isUnread") @SerializedName("isUnread")
val isUnread: Boolean val isUnread: Boolean
) { ) {
/**
* 转换为Entity
*/
fun toCommentEntity(): CommentEntity { fun toCommentEntity(): CommentEntity {
return CommentEntity( return CommentEntity(
id = id, id = id,
@@ -73,52 +112,6 @@ data class Comment(
} }
} }
data class CommentEntity(
val id: Int,
val name: String,
val comment: String,
val date: Date,
val likes: Int,
val replies: List<CommentEntity>,
val postId: Int = 0,
val avatar: String,
val author: Long,
var liked: Boolean,
var unread: Boolean = false,
var post: NoticePost?
)
class CommentPagingSource(
private val remoteDataSource: CommentRemoteDataSource,
private val postId: Int? = null,
private val postUser: Int? = null,
private val selfNotice: Boolean? = null
) : PagingSource<Int, CommentEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CommentEntity> {
return try {
val currentPage = params.key ?: 1
val comments = remoteDataSource.getComments(
pageNumber = currentPage,
postId = postId,
postUser = postUser,
selfNotice = selfNotice
)
LoadResult.Page(
data = comments.list,
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (comments.list.isEmpty()) null else comments.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, CommentEntity>): Int? {
return state.anchorPosition
}
}
class CommentRemoteDataSource( class CommentRemoteDataSource(
private val commentService: CommentService, private val commentService: CommentService,
) { ) {
@@ -138,7 +131,7 @@ class CommentRemoteDataSource(
} }
class TestCommentServiceImpl : CommentService { class CommentServiceImpl : CommentService {
override suspend fun getComments( override suspend fun getComments(
pageNumber: Int, pageNumber: Int,
postId: Int?, postId: Int?,
@@ -182,7 +175,4 @@ class TestCommentServiceImpl : CommentService {
return return
} }
companion object {
const val DataBatchSize = 5
}
} }

View File

@@ -1,5 +1,8 @@
package com.aiosman.riderpro.data package com.aiosman.riderpro.data
/**
* 通用接口返回数据
*/
data class DataContainer<T>( data class DataContainer<T>(
val data: T val data: T
) )

View File

@@ -1,5 +1,8 @@
package com.aiosman.riderpro.data package com.aiosman.riderpro.data
/**
* 错误返回
*/
class ServiceException( class ServiceException(
override val message: String, override val message: String,
val code: Int = 0, val code: Int = 0,

View File

@@ -3,13 +3,20 @@ package com.aiosman.riderpro.data
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
/**
* 通用列表接口返回
*/
data class ListContainer<T>( data class ListContainer<T>(
// 总数
@SerializedName("total") @SerializedName("total")
val total: Int, val total: Int,
// 当前页
@SerializedName("page") @SerializedName("page")
val page: Int, val page: Int,
// 每页数量
@SerializedName("pageSize") @SerializedName("pageSize")
val pageSize: Int, val pageSize: Int,
// 列表
@SerializedName("list") @SerializedName("list")
val list: List<T> val list: List<T>
) )

View File

@@ -1,23 +1,11 @@
package com.aiosman.riderpro.data package com.aiosman.riderpro.data
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.api.ApiClient import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.model.MomentImageEntity import com.aiosman.riderpro.entity.MomentImageEntity
import com.aiosman.riderpro.test.TestDatabase
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.net.URL
data class Moment( data class Moment(
@SerializedName("id") @SerializedName("id")
@@ -100,9 +88,32 @@ data class UploadImage(
) )
interface MomentService { interface MomentService {
/**
* 获取动态详情
* @param id 动态ID
*/
suspend fun getMomentById(id: Int): MomentEntity suspend fun getMomentById(id: Int): MomentEntity
/**
* 点赞动态
* @param id 动态ID
*/
suspend fun likeMoment(id: Int) suspend fun likeMoment(id: Int)
/**
* 取消点赞动态
* @param id 动态ID
*/
suspend fun dislikeMoment(id: Int) suspend fun dislikeMoment(id: Int)
/**
* 获取动态列表
* @param pageNumber 页码
* @param author 作者ID,过滤条件
* @param timelineId 用户时间线ID,指定用户 ID 的时间线
* @param contentSearch 内容搜索,过滤条件
* @return 动态列表
*/
suspend fun getMoments( suspend fun getMoments(
pageNumber: Int, pageNumber: Int,
author: Int? = null, author: Int? = null,
@@ -110,6 +121,13 @@ interface MomentService {
contentSearch: String? = null contentSearch: String? = null
): ListContainer<MomentEntity> ): ListContainer<MomentEntity>
/**
* 创建动态
* @param content 动态内容
* @param authorId 作者ID
* @param images 图片列表
* @param relPostId 关联动态ID
*/
suspend fun createMoment( suspend fun createMoment(
content: String, content: String,
authorId: Int, authorId: Int,
@@ -117,169 +135,17 @@ interface MomentService {
relPostId: Int? = null relPostId: Int? = null
): MomentEntity ): MomentEntity
/**
* 收藏动态
* @param id 动态ID
*/
suspend fun favoriteMoment(id: Int) suspend fun favoriteMoment(id: Int)
/**
* 取消收藏动态
* @param id 动态ID
*/
suspend fun unfavoriteMoment(id: Int) suspend fun unfavoriteMoment(id: Int)
} }
class MomentPagingSource(
private val remoteDataSource: MomentRemoteDataSource,
private val author: Int? = null,
private val timelineId: Int? = null,
private val contentSearch: String? = null
) : PagingSource<Int, MomentEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MomentEntity> {
return try {
val currentPage = params.key ?: 1
val moments = remoteDataSource.getMoments(
pageNumber = currentPage,
author = author,
timelineId = timelineId,
contentSearch = contentSearch
)
LoadResult.Page(
data = moments.list,
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (moments.list.isEmpty()) null else moments.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, MomentEntity>): Int? {
return state.anchorPosition
}
}
class MomentRemoteDataSource(
private val momentService: MomentService,
) {
suspend fun getMoments(
pageNumber: Int,
author: Int?,
timelineId: Int?,
contentSearch: String?
): ListContainer<MomentEntity> {
return momentService.getMoments(pageNumber, author, timelineId, contentSearch)
}
}
class TestMomentServiceImpl() : MomentService {
val testMomentBackend = TestMomentBackend()
override suspend fun getMoments(
pageNumber: Int,
author: Int?,
timelineId: Int?,
contentSearch: String?
): ListContainer<MomentEntity> {
return testMomentBackend.fetchMomentItems(pageNumber, author, timelineId, contentSearch)
}
override suspend fun getMomentById(id: Int): MomentEntity {
return testMomentBackend.getMomentById(id)
}
override suspend fun likeMoment(id: Int) {
testMomentBackend.likeMoment(id)
}
override suspend fun dislikeMoment(id: Int) {
testMomentBackend.dislikeMoment(id)
}
override suspend fun createMoment(
content: String,
authorId: Int,
images: List<UploadImage>,
relPostId: Int?
): MomentEntity {
return testMomentBackend.createMoment(content, authorId, images, relPostId)
}
override suspend fun favoriteMoment(id: Int) {
testMomentBackend.favoriteMoment(id)
}
override suspend fun unfavoriteMoment(id: Int) {
testMomentBackend.unfavoriteMoment(id)
}
}
class TestMomentBackend(
private val loadDelay: Long = 500,
) {
val DataBatchSize = 5
suspend fun fetchMomentItems(
pageNumber: Int,
author: Int? = null,
timelineId: Int?,
contentSearch: String?
): ListContainer<MomentEntity> {
val resp = ApiClient.api.getPosts(
pageSize = DataBatchSize,
page = pageNumber,
timelineId = timelineId,
authorId = author,
contentSearch = contentSearch
)
val body = resp.body() ?: throw ServiceException("Failed to get moments")
return ListContainer(
total = body.total,
page = pageNumber,
pageSize = DataBatchSize,
list = body.list.map { it.toMomentItem() }
)
}
suspend fun getMomentById(id: Int): MomentEntity {
var resp = ApiClient.api.getPost(id)
var body = resp.body()?.data ?: throw ServiceException("Failed to get moment")
return body.toMomentItem()
}
suspend fun likeMoment(id: Int) {
ApiClient.api.likePost(id)
}
suspend fun dislikeMoment(id: Int) {
ApiClient.api.dislikePost(id)
}
fun createMultipartBody(file: File, name: String): MultipartBody.Part {
val requestFile = RequestBody.create("image/*".toMediaTypeOrNull(), file)
return MultipartBody.Part.createFormData(name, file.name, requestFile)
}
suspend fun createMoment(
content: String,
authorId: Int,
imageUriList: List<UploadImage>,
relPostId: Int?
): MomentEntity {
val textContent = content.toRequestBody("text/plain".toMediaTypeOrNull())
val imageList = imageUriList.map { item ->
val file = item.file
createMultipartBody(file, "image")
}
val response = ApiClient.api.createPost(imageList, textContent = textContent)
val body = response.body()?.data ?: throw ServiceException("Failed to create moment")
return body.toMomentItem()
}
suspend fun favoriteMoment(id: Int) {
ApiClient.api.favoritePost(id)
}
suspend fun unfavoriteMoment(id: Int) {
ApiClient.api.unfavoritePost(id)
}
}

View File

@@ -1,48 +1,43 @@
package com.aiosman.riderpro.data package com.aiosman.riderpro.data
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.data.api.ApiClient import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.test.TestDatabase
import java.io.IOException
data class UserAuth( data class UserAuth(
val id: Int, val id: Int,
val token: String? = null val token: String? = null
) )
class AccountPagingSource( /**
private val userService: UserService, * 用户相关 Service
private val nickname: String? = null */
) : PagingSource<Int, AccountProfileEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AccountProfileEntity> {
return try {
val currentPage = params.key ?: 1
val users = userService.getUsers(
page = currentPage,
nickname = nickname
)
LoadResult.Page(
data = users.list,
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (users.list.isEmpty()) null else users.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, AccountProfileEntity>): Int? {
return state.anchorPosition
}
}
interface UserService { interface UserService {
/**
* 获取用户信息
* @param id 用户ID
* @return 用户信息
*/
suspend fun getUserProfile(id: String): AccountProfileEntity suspend fun getUserProfile(id: String): AccountProfileEntity
/**
* 关注用户
* @param id 用户ID
*/
suspend fun followUser(id: String) suspend fun followUser(id: String)
/**
* 取消关注用户
* @param id 用户ID
*/
suspend fun unFollowUser(id: String) suspend fun unFollowUser(id: String)
/**
* 获取用户列表
* @param pageSize 分页大小
* @param page 页码
* @param nickname 昵称搜索
* @return 用户列表
*/
suspend fun getUsers( suspend fun getUsers(
pageSize: Int = 20, pageSize: Int = 20,
page: Int = 1, page: Int = 1,
@@ -51,7 +46,7 @@ interface UserService {
} }
class TestUserServiceImpl : UserService { class UserServiceImpl : UserService {
override suspend fun getUserProfile(id: String): AccountProfileEntity { override suspend fun getUserProfile(id: String): AccountProfileEntity {
val resp = ApiClient.api.getAccountProfileById(id.toInt()) val resp = ApiClient.api.getAccountProfileById(id.toInt())
val body = resp.body() ?: throw ServiceException("Failed to get account") val body = resp.body() ?: throw ServiceException("Failed to get account")

View File

@@ -5,7 +5,6 @@ import com.aiosman.riderpro.data.AccountFollow
import com.aiosman.riderpro.data.AccountLike import com.aiosman.riderpro.data.AccountLike
import com.aiosman.riderpro.data.AccountNotice import com.aiosman.riderpro.data.AccountNotice
import com.aiosman.riderpro.data.AccountProfile import com.aiosman.riderpro.data.AccountProfile
import com.aiosman.riderpro.data.AccountProfileEntity
import com.aiosman.riderpro.data.Comment import com.aiosman.riderpro.data.Comment
import com.aiosman.riderpro.data.DataContainer import com.aiosman.riderpro.data.DataContainer
import com.aiosman.riderpro.data.ListContainer import com.aiosman.riderpro.data.ListContainer

View File

@@ -0,0 +1,176 @@
package com.aiosman.riderpro.entity
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.data.AccountFollow
import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.Image
import com.aiosman.riderpro.data.api.ApiClient
import java.io.IOException
import java.util.Date
/**
* 用户点赞
*/
data class AccountLikeEntity(
// 动态
val post: NoticePostEntity,
// 点赞用户
val user: NoticeUserEntity,
// 点赞时间
val likeTime: Date,
)
/**
* 用户收藏
*/
data class AccountFavouriteEntity(
// 动态
val post: NoticePostEntity,
// 收藏用户
val user: NoticeUserEntity,
// 收藏时间
val favoriteTime: Date,
)
/**
* 用户信息
*/
data class AccountProfileEntity(
// 用户ID
val id: Int,
// 粉丝数
val followerCount: Int,
// 关注数
val followingCount: Int,
// 昵称
val nickName: String,
// 头像
val avatar: String,
// 个人简介
val bio: String,
// 国家
val country: String,
// 是否关注,针对当前登录用户
val isFollowing: Boolean
)
/**
* 消息关联的动态
*/
data class NoticePostEntity(
// 动态ID
val id: Int,
// 动态内容
val textContent: String,
// 动态图片
val images: List<Image>,
// 时间
val time: Date,
)
/**
* 消息关联的用户
*/
data class NoticeUserEntity(
// 用户ID
val id: Int,
// 昵称
val nickName: String,
// 头像
val avatar: String,
)
/**
* 用户点赞消息分页数据加载器
*/
class LikeItemPagingSource(
private val accountService: AccountService,
) : PagingSource<Int, AccountLikeEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AccountLikeEntity> {
return try {
val currentPage = params.key ?: 1
val likes = accountService.getMyLikeNotice(
page = currentPage,
pageSize = 20,
)
LoadResult.Page(
data = likes.list.map {
it.toAccountLikeEntity()
},
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (likes.list.isEmpty()) null else likes.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, AccountLikeEntity>): Int? {
return state.anchorPosition
}
}
/**
* 用户收藏消息分页数据加载器
*/
class FavoriteItemPagingSource(
private val accountService: AccountService,
) : PagingSource<Int, AccountFavouriteEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AccountFavouriteEntity> {
return try {
val currentPage = params.key ?: 1
val favouriteListContainer = accountService.getMyFavouriteNotice(
page = currentPage,
pageSize = 20,
)
LoadResult.Page(
data = favouriteListContainer.list.map {
it.toAccountFavouriteEntity()
},
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (favouriteListContainer.list.isEmpty()) null else favouriteListContainer.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, AccountFavouriteEntity>): Int? {
return state.anchorPosition
}
}
/**
* 用户关注消息分页数据加载器
*/
class FollowItemPagingSource(
private val accountService: AccountService,
) : PagingSource<Int, AccountFollow>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AccountFollow> {
return try {
val currentPage = params.key ?: 1
val followListContainer = accountService.getMyFollowNotice(
page = currentPage,
pageSize = 20,
)
LoadResult.Page(
data = followListContainer.list.map {
it.copy(
avatar = "${ApiClient.BASE_SERVER}${it.avatar}",
)
},
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (followListContainer.list.isEmpty()) null else followListContainer.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, AccountFollow>): Int? {
return state.anchorPosition
}
}

View File

@@ -0,0 +1,54 @@
package com.aiosman.riderpro.entity
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.NoticePost
import java.io.IOException
import java.util.Date
data class CommentEntity(
val id: Int,
val name: String,
val comment: String,
val date: Date,
val likes: Int,
val replies: List<CommentEntity>,
val postId: Int = 0,
val avatar: String,
val author: Long,
var liked: Boolean,
var unread: Boolean = false,
var post: NoticePost?
)
class CommentPagingSource(
private val remoteDataSource: CommentRemoteDataSource,
private val postId: Int? = null,
private val postUser: Int? = null,
private val selfNotice: Boolean? = null
) : PagingSource<Int, CommentEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CommentEntity> {
return try {
val currentPage = params.key ?: 1
val comments = remoteDataSource.getComments(
pageNumber = currentPage,
postId = postId,
postUser = postUser,
selfNotice = selfNotice
)
LoadResult.Page(
data = comments.list,
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (comments.list.isEmpty()) null else comments.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, CommentEntity>): Int? {
return state.anchorPosition
}
}

View File

@@ -0,0 +1,236 @@
package com.aiosman.riderpro.entity
import androidx.annotation.DrawableRes
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.data.ListContainer
import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.ServiceException
import com.aiosman.riderpro.data.UploadImage
import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.entity.MomentEntity
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.io.IOException
import java.util.Date
/**
* 动态分页加载器
*/
class MomentPagingSource(
private val remoteDataSource: MomentRemoteDataSource,
private val author: Int? = null,
private val timelineId: Int? = null,
private val contentSearch: String? = null
) : PagingSource<Int, MomentEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MomentEntity> {
return try {
val currentPage = params.key ?: 1
val moments = remoteDataSource.getMoments(
pageNumber = currentPage,
author = author,
timelineId = timelineId,
contentSearch = contentSearch
)
LoadResult.Page(
data = moments.list,
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (moments.list.isEmpty()) null else moments.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, MomentEntity>): Int? {
return state.anchorPosition
}
}
class MomentRemoteDataSource(
private val momentService: MomentService,
) {
suspend fun getMoments(
pageNumber: Int,
author: Int?,
timelineId: Int?,
contentSearch: String?
): ListContainer<MomentEntity> {
return momentService.getMoments(pageNumber, author, timelineId, contentSearch)
}
}
class MomentServiceImpl() : MomentService {
val momentBackend = MomentBackend()
override suspend fun getMoments(
pageNumber: Int,
author: Int?,
timelineId: Int?,
contentSearch: String?
): ListContainer<MomentEntity> {
return momentBackend.fetchMomentItems(pageNumber, author, timelineId, contentSearch)
}
override suspend fun getMomentById(id: Int): MomentEntity {
return momentBackend.getMomentById(id)
}
override suspend fun likeMoment(id: Int) {
momentBackend.likeMoment(id)
}
override suspend fun dislikeMoment(id: Int) {
momentBackend.dislikeMoment(id)
}
override suspend fun createMoment(
content: String,
authorId: Int,
images: List<UploadImage>,
relPostId: Int?
): MomentEntity {
return momentBackend.createMoment(content, authorId, images, relPostId)
}
override suspend fun favoriteMoment(id: Int) {
momentBackend.favoriteMoment(id)
}
override suspend fun unfavoriteMoment(id: Int) {
momentBackend.unfavoriteMoment(id)
}
}
class MomentBackend {
val DataBatchSize = 20
suspend fun fetchMomentItems(
pageNumber: Int,
author: Int? = null,
timelineId: Int?,
contentSearch: String?
): ListContainer<MomentEntity> {
val resp = ApiClient.api.getPosts(
pageSize = DataBatchSize,
page = pageNumber,
timelineId = timelineId,
authorId = author,
contentSearch = contentSearch
)
val body = resp.body() ?: throw ServiceException("Failed to get moments")
return ListContainer(
total = body.total,
page = pageNumber,
pageSize = DataBatchSize,
list = body.list.map { it.toMomentItem() }
)
}
suspend fun getMomentById(id: Int): MomentEntity {
var resp = ApiClient.api.getPost(id)
var body = resp.body()?.data ?: throw ServiceException("Failed to get moment")
return body.toMomentItem()
}
suspend fun likeMoment(id: Int) {
ApiClient.api.likePost(id)
}
suspend fun dislikeMoment(id: Int) {
ApiClient.api.dislikePost(id)
}
fun createMultipartBody(file: File, name: String): MultipartBody.Part {
val requestFile = RequestBody.create("image/*".toMediaTypeOrNull(), file)
return MultipartBody.Part.createFormData(name, file.name, requestFile)
}
suspend fun createMoment(
content: String,
authorId: Int,
imageUriList: List<UploadImage>,
relPostId: Int?
): MomentEntity {
val textContent = content.toRequestBody("text/plain".toMediaTypeOrNull())
val imageList = imageUriList.map { item ->
val file = item.file
createMultipartBody(file, "image")
}
val response = ApiClient.api.createPost(imageList, textContent = textContent)
val body = response.body()?.data ?: throw ServiceException("Failed to create moment")
return body.toMomentItem()
}
suspend fun favoriteMoment(id: Int) {
ApiClient.api.favoritePost(id)
}
suspend fun unfavoriteMoment(id: Int) {
ApiClient.api.unfavoritePost(id)
}
}
/**
* 动态图片
*/
data class MomentImageEntity(
// 图片ID
val id: Long,
// 图片URL
val url: String,
// 缩略图URL
val thumbnail: String,
// 图片BlurHash
val blurHash: String? = null
)
/**
* 动态
*/
data class MomentEntity(
// 动态ID
val id: Int,
// 作者头像
val avatar: String,
// 作者昵称
val nickname: String,
// 区域
val location: String,
// 动态时间
val time: Date,
// 是否关注
val followStatus: Boolean,
// 动态内容
val momentTextContent: String,
// 动态图片
@DrawableRes val momentPicture: Int,
// 点赞数
val likeCount: Int,
// 评论数
val commentCount: Int,
// 分享数
val shareCount: Int,
// 收藏数
val favoriteCount: Int,
// 动态图片列表
val images: List<MomentImageEntity> = emptyList(),
// 作者ID
val authorId: Int = 0,
// 是否点赞
var liked: Boolean = false,
// 关联动态ID
var relPostId: Int? = null,
// 关联动态
var relMoment: MomentEntity? = null,
// 是否收藏
var isFavorite: Boolean = false
)

View File

@@ -0,0 +1,36 @@
package com.aiosman.riderpro.entity
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.data.UserService
import java.io.IOException
/**
* 用户信息分页加载器
*/
class AccountPagingSource(
private val userService: UserService,
private val nickname: String? = null
) : PagingSource<Int, AccountProfileEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AccountProfileEntity> {
return try {
val currentPage = params.key ?: 1
val users = userService.getUsers(
page = currentPage,
nickname = nickname
)
LoadResult.Page(
data = users.list,
prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (users.list.isEmpty()) null else users.page + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, AccountProfileEntity>): Int? {
return state.anchorPosition
}
}

View File

@@ -6,6 +6,9 @@ import com.aiosman.riderpro.data.api.ApiClient
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
/**
* 格式化时间为 xx 前
*/
fun Date.timeAgo(): String { fun Date.timeAgo(): String {
val now = Date() val now = Date()
val diffInMillis = now.time - this.time val diffInMillis = now.time - this.time
@@ -25,6 +28,9 @@ fun Date.timeAgo(): String {
} }
} }
/**
* 格式化时间为 xx-xx
*/
fun Date.formatPostTime(): String { fun Date.formatPostTime(): String {
val now = Calendar.getInstance() val now = Calendar.getInstance()
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()

View File

@@ -1,32 +0,0 @@
package com.aiosman.riderpro.model
import androidx.annotation.DrawableRes
import java.util.Date
data class MomentImageEntity(
val id: Long,
val url: String,
val thumbnail: String,
val blurHash: String? = null
)
data class MomentEntity(
val id: Int,
val avatar: String,
val nickname: String,
val location: String,
val time: Date,
val followStatus: Boolean,
val momentTextContent: String,
@DrawableRes val momentPicture: Int,
val likeCount: Int,
val commentCount: Int,
val shareCount: Int,
val favoriteCount: Int,
val images: List<MomentImageEntity> = emptyList(),
val authorId: Int = 0,
var liked: Boolean = false,
var relPostId: Int? = null,
var relMoment: MomentEntity? = null,
var isFavorite: Boolean = false
)

View File

@@ -1,31 +0,0 @@
package com.aiosman.riderpro.test
import kotlin.math.min
class MockDataContainer<T>(
val success: Boolean,
val data: T?
) {
}
class MockListContainer<T>(
val total: Int,
val page: Int,
val pageSize: Int,
val list: List<T>
) {
}
abstract class MockDataSource<T> {
var list = mutableListOf<T>()
suspend fun fetchData(page: Int, pageSize: Int): MockDataContainer<MockListContainer<T>> {
// over page return empty
if (page * pageSize > list.size) {
return MockDataContainer(false, MockListContainer(0, page, pageSize, emptyList()))
}
val backData = list.subList((page - 1) * pageSize, min(page * pageSize, list.size))
return MockDataContainer(true, MockListContainer(list.size, page, pageSize, backData))
}
}

View File

@@ -1,51 +0,0 @@
package com.aiosman.riderpro.test
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.riderpro.model.MomentEntity
import kotlinx.coroutines.delay
import kotlin.math.ceil
class TestBackend(
private val backendDataList: List<MomentEntity>,
private val loadDelay: Long = 500,
) {
val DataBatchSize = 5
class DesiredLoadResultPageResponse(val data: List<MomentEntity>)
/** Returns [DataBatchSize] items for a key */
fun searchItemsByKey(key: Int): DesiredLoadResultPageResponse {
val maxKey = ceil(backendDataList.size.toFloat() / DataBatchSize).toInt()
if (key >= maxKey) {
return DesiredLoadResultPageResponse(emptyList())
}
val from = key * DataBatchSize
val to = minOf((key + 1) * DataBatchSize, backendDataList.size)
val currentSublist = backendDataList.subList(from, to)
return DesiredLoadResultPageResponse(currentSublist)
}
fun getAllData() = TestPagingSource(this, loadDelay)
}
class TestPagingSource(
private val backend: TestBackend,
private val loadDelay: Long,
) : PagingSource<Int, MomentEntity>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MomentEntity> {
// Simulate latency
delay(loadDelay)
val pageNumber = params.key ?: 0
val response = backend.searchItemsByKey(pageNumber)
// Since 0 is the lowest page number, return null to signify no more pages should
// be loaded before it.
val prevKey = if (pageNumber > 0) pageNumber - 1 else null
// This API defines that it's out of data when a page returns empty. When out of
// data, we return `null` to signify no more pages should be loaded
val nextKey = if (response.data.isNotEmpty()) pageNumber + 1 else null
return LoadResult.Page(data = response.data, prevKey = prevKey, nextKey = nextKey)
}
override fun getRefreshKey(state: PagingState<Int, MomentEntity>): Int? {
return state.anchorPosition?.let {
state.closestPageToPosition(it)?.prevKey?.plus(1)
?: state.closestPageToPosition(it)?.nextKey?.minus(1)
}
}
}

View File

@@ -1,163 +0,0 @@
package com.aiosman.riderpro.test
import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountProfileEntity
import com.aiosman.riderpro.data.CommentEntity
import com.aiosman.riderpro.model.MomentEntity
import com.aiosman.riderpro.model.MomentImageEntity
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import io.github.serpro69.kfaker.faker
import java.io.File
import java.util.Calendar
import java.util.Date
object TestDatabase {
var momentData = emptyList<MomentEntity>()
var accountData = emptyList<AccountProfileEntity>()
var commentEntity = emptyList<CommentEntity>()
var commentIdCounter = 0
var momentIdCounter = 0
var selfId = 1
var imageList = listOf(
"https://img.freepik.com/free-photo/white-billboard-template_23-2147726635.jpg?t=st=1722150015~exp=1722153615~hmac=5540620196d7898215d822be26353c87a63d51bbfb2b814e032626e1948a1583&w=740",
"https://img.freepik.com/free-photo/minimal-clothing-label-fashion-brands_53876-111053.jpg?w=1060&t=st=1722150122~exp=1722150722~hmac=67f8a2b6abfe3d08714cf0cc0085485c3221e1ba00dda14378b03753dce39153",
"https://img.freepik.com/free-photo/marketing-strategy-planning-strategy-concept_53876-42950.jpg",
"https://t4.ftcdn.net/jpg/02/27/00/89/240_F_227008949_5O7yXuEqTwUgs3BGqdcvrNutM5MSxs1t.jpg",
"https://t4.ftcdn.net/jpg/01/86/86/49/240_F_186864971_NixcoDg1zBjjN7soUNhpEVraI4vdzOFD.jpg",
"https://t3.ftcdn.net/jpg/00/84/01/30/240_F_84013057_fsOdzBgskSFUyWyD6YKjIAdtKdBPiKRD.jpg",
"https://t4.ftcdn.net/jpg/00/93/89/23/240_F_93892312_SNyGGruVaWKpJQiVG314gIQmS4EAghdy.jpg",
"https://t3.ftcdn.net/jpg/02/94/56/58/240_F_294565895_IOqZC2OpcHGEibWF04MPEP09KZaewEl5.jpg",
"https://t3.ftcdn.net/jpg/01/01/66/84/240_F_101668484_FopHBSMBq4t6BlvwI9awPMzUdi501sJ7.jpg",
"https://t3.ftcdn.net/jpg/05/65/11/60/240_F_565116019_oHbZ6Hc8VYCMcZWpexXF7Z5lOWeNNYtD.jpg",
"https://t3.ftcdn.net/jpg/03/52/21/48/240_F_352214843_dQ3JtTJrKyqrh2yd1emYCDPSrzrwqaNK.jpg",
"https://t3.ftcdn.net/jpg/07/22/47/16/240_F_722471661_T25r329RFRxgK88S6oBJ9dUksOC2arLl.jpg",
"https://t3.ftcdn.net/jpg/02/18/11/26/240_F_218112603_jBChzLJGuz8smPZsdFsy17wB0O0QF3Xo.jpg",
"https://t4.ftcdn.net/jpg/04/11/49/07/240_F_411490703_KRvV0aRyxHWYVUO8bGXxuQGo2mHblYnv.jpg",
"https://img.freepik.com/premium-photo/man-wearing-orange-helmet-white-background_466494-5539.jpg?ga=GA1.1.1334458544.1722150011&semt=sph",
"https://img.freepik.com/premium-photo/motorcycle-vehicle-3d-modelling_274824-502.jpg?ga=GA1.1.1334458544.1722150011&semt=sph",
"https://t3.ftcdn.net/jpg/01/68/26/06/240_F_168260687_UfaDjjs6TxcIB6BdsquSeCmYWEFmN1Sh.jpg",
"https://t3.ftcdn.net/jpg/03/48/50/34/240_F_348503435_On7Tt5Eqn7IP9QWYTQL0H1smubU8gvLv.jpg",
"https://t3.ftcdn.net/jpg/02/76/70/70/240_F_276707060_WpP9bwHWv0Wdqqn0pEgtSuIgXUvgkbs7.jpg",
"https://t3.ftcdn.net/jpg/02/65/43/04/240_F_265430460_DIHqnrziar7WL2rmW0qbDO07TbxjlPQo.jpg"
)
var followList = emptyList<Pair<Int, Int>>()
var likeCommentList = emptyList<Pair<Int, Int>>()
var likeMomentList = emptyList<Pair<Int, Int>>()
init {
val faker = faker {
this.fakerConfig {
locale = "en"
}
}
accountData = (0..20).toList().mapIndexed { idx, _ ->
AccountProfileEntity(
id = idx,
followerCount = 0,
followingCount = 0,
nickName = faker.name.name(),
avatar = imageList.random(),
bio = "I am a software engineer",
country = faker.address.country(),
isFollowing = false
)
}
// make a random follow rel
for (i in 0..100) {
var person1 = accountData.random()
var persion2 = accountData.random()
followList += Pair(person1.id, persion2.id)
// update followerCount and followingCount
accountData = accountData.map {
if (it.id == person1.id) {
it.copy(followingCount = it.followingCount + 1)
} else if (it.id == persion2.id) {
it.copy(followerCount = it.followerCount + 1)
} else {
it
}
}
}
momentData = (0..60).toList().mapIndexed { idx, _ ->
momentIdCounter += 1
val person = accountData.random()
// make fake comment
val commentCount = faker.random.nextInt(0, 50)
for (i in 0..commentCount) {
commentIdCounter += 1
val commentPerson = accountData.random()
var newCommentEntity = CommentEntity(
name = commentPerson.nickName,
comment = "this is comment ${commentIdCounter}",
date = Calendar.getInstance().time,
likes = 0,
replies = emptyList(),
postId = momentIdCounter,
avatar = commentPerson.avatar,
author = commentPerson.id.toLong(),
id = commentIdCounter,
liked = false,
unread = false,
post = null
)
// generate like comment list
for (likeIdx in 0..faker.random.nextInt(0, 5)) {
val likePerson = accountData.random()
likeCommentList += Pair(commentIdCounter, likePerson.id)
newCommentEntity = newCommentEntity.copy(likes = newCommentEntity.likes + 1)
}
commentEntity += newCommentEntity
}
val likeCount = faker.random.nextInt(0, 5)
for (i in 0..likeCount) {
val likePerson = accountData.random()
likeMomentList += Pair(momentIdCounter, likePerson.id)
}
MomentEntity(
id = momentIdCounter,
avatar = person.avatar,
nickname = person.nickName,
location = person.country,
time = Date(),
followStatus = false,
momentTextContent = "By strongarming Ducati into giving him the factory seat.Marquez effectively …",
momentPicture = R.drawable.default_moment_img,
likeCount = likeCount,
commentCount = commentCount + 1,
shareCount = faker.random.nextInt(0, 100),
favoriteCount = faker.random.nextInt(0, 100),
images = imageList.shuffled().take(3).map {
MomentImageEntity(
id = faker.random.nextLong(),
url = it,
thumbnail = it
)
},
authorId = person.id
)
}
}
fun updateMomentById(id: Int, momentEntity: MomentEntity) {
momentData = momentData.map {
if (it.id == id) {
momentEntity
} else {
it
}
}
}
fun saveResultToJsonFile() {
val gson: Gson = GsonBuilder().setPrettyPrinting().create()
// save accountData to json file
File("accountData.json").writeText(accountData.toString())
// save momentData to json file
// save comment to json file
}
}

View File

@@ -9,16 +9,27 @@ import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* 修改密码页面的 ViewModel
*/
class ChangePasswordViewModel { class ChangePasswordViewModel {
val accountService :AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
/**
* 修改密码
* @param currentPassword 当前密码
* @param newPassword 新密码
*/
suspend fun changePassword(currentPassword: String, newPassword: String) { suspend fun changePassword(currentPassword: String, newPassword: String) {
accountService.changeAccountPassword(currentPassword, newPassword) accountService.changeAccountPassword(currentPassword, newPassword)
} }
} }
@Composable
fun ChangePasswordScreen(
) { /**
* 修改密码页面
*/
@Composable
fun ChangePasswordScreen() {
val viewModel = remember { ChangePasswordViewModel() } val viewModel = remember { ChangePasswordViewModel() }
var currentPassword by remember { mutableStateOf("") } var currentPassword by remember { mutableStateOf("") }
var newPassword by remember { mutableStateOf("") } var newPassword by remember { mutableStateOf("") }

View File

@@ -33,10 +33,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.aiosman.riderpro.data.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl import com.aiosman.riderpro.data.UserServiceImpl
import com.aiosman.riderpro.data.UploadImage import com.aiosman.riderpro.data.UploadImage
import com.aiosman.riderpro.data.UserService import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.ui.composables.CustomAsyncImage import com.aiosman.riderpro.ui.composables.CustomAsyncImage
@@ -44,10 +44,12 @@ import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.post.NewPostViewModel.uriToFile import com.aiosman.riderpro.ui.post.NewPostViewModel.uriToFile
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* 编辑用户资料界面
*/
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AccountEditScreen() { fun AccountEditScreen() {
val userService: UserService = TestUserServiceImpl()
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
var name by remember { mutableStateOf("") } var name by remember { mutableStateOf("") }
var bio by remember { mutableStateOf("") } var bio by remember { mutableStateOf("") }
@@ -59,8 +61,11 @@ fun AccountEditScreen() {
} }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
suspend fun reloadProfile() {
/**
* 加载用户资料
*/
suspend fun reloadProfile() {
accountService.getMyAccountProfile().let { accountService.getMyAccountProfile().let {
profile = it profile = it
name = it.nickName name = it.nickName
@@ -70,19 +75,12 @@ fun AccountEditScreen() {
} }
fun updateUserAvatar(uri: String) {
scope.launch {
accountService.updateAvatar(uri)
reloadProfile()
}
}
fun updateUserProfile() { fun updateUserProfile() {
scope.launch { scope.launch {
val newAvatar = imageUrl?.let { val newAvatar = imageUrl?.let {
val cursor = context.contentResolver.query(it, null, null, null, null) val cursor = context.contentResolver.query(it, null, null, null, null)
var newAvatar: UploadImage? = null var newAvatar: UploadImage? = null
cursor?.use {cur -> cursor?.use { cur ->
if (cur.moveToFirst()) { if (cur.moveToFirst()) {
val displayName = cur.getString(cur.getColumnIndex("_display_name")) val displayName = cur.getString(cur.getColumnIndex("_display_name"))
val extension = displayName.substringAfterLast(".") val extension = displayName.substringAfterLast(".")
@@ -135,13 +133,13 @@ fun AccountEditScreen() {
) )
} }
} }
) { ) { padding ->
padding ->
profile?.let { profile?.let {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(padding).padding(horizontal = 24.dp), .padding(padding)
.padding(horizontal = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
CustomAsyncImage( CustomAsyncImage(

View File

@@ -49,19 +49,22 @@ import androidx.paging.cachedIn
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.ui.post.CommentsSection import com.aiosman.riderpro.ui.post.CommentsSection
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.CommentEntity import com.aiosman.riderpro.entity.CommentEntity
import com.aiosman.riderpro.data.CommentPagingSource import com.aiosman.riderpro.entity.CommentPagingSource
import com.aiosman.riderpro.data.CommentRemoteDataSource import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.CommentService import com.aiosman.riderpro.data.CommentService
import com.aiosman.riderpro.data.TestCommentServiceImpl import com.aiosman.riderpro.data.CommentServiceImpl
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* 评论弹窗的 ViewModel
*/
class CommentModalViewModel( class CommentModalViewModel(
val postId: Int? val postId: Int?
) : ViewModel() { ) : ViewModel() {
val commentService: CommentService = TestCommentServiceImpl() val commentService: CommentService = CommentServiceImpl()
val commentsFlow: Flow<PagingData<CommentEntity>> = Pager( val commentsFlow: Flow<PagingData<CommentEntity>> = Pager(
config = PagingConfig(pageSize = 20, enablePlaceholders = false), config = PagingConfig(pageSize = 20, enablePlaceholders = false),
pagingSourceFactory = { pagingSourceFactory = {
@@ -72,6 +75,9 @@ class CommentModalViewModel(
} }
).flow.cachedIn(viewModelScope) ).flow.cachedIn(viewModelScope)
/**
* 创建评论
*/
suspend fun createComment(content: String) { suspend fun createComment(content: String) {
postId?.let { postId?.let {
commentService.createComment(postId, content) commentService.createComment(postId, content)
@@ -79,7 +85,13 @@ class CommentModalViewModel(
} }
} }
@Preview
/**
* 评论弹窗
* @param postId 帖子ID
* @param onCommentAdded 评论添加回调
* @param onDismiss 关闭回调
*/
@Composable @Composable
fun CommentModalContent( fun CommentModalContent(
postId: Int? = null, postId: Int? = null,

View File

@@ -10,7 +10,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.ui.index.tabs.moment.MomentTopRowGroup import com.aiosman.riderpro.ui.index.tabs.moment.MomentTopRowGroup
@Composable @Composable

View File

@@ -7,9 +7,9 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.aiosman.riderpro.data.AccountFavouriteEntity import com.aiosman.riderpro.entity.AccountFavouriteEntity
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.FavoriteItemPagingSource import com.aiosman.riderpro.entity.FavoriteItemPagingSource
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.api.ApiClient import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
@@ -18,6 +18,9 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* 收藏消息列表的 ViewModel
*/
object FavouritePageViewModel : ViewModel() { object FavouritePageViewModel : ViewModel() {
private val accountService: AccountService = AccountServiceImpl() private val accountService: AccountService = AccountServiceImpl()
private val _favouriteItemsFlow = private val _favouriteItemsFlow =
@@ -38,6 +41,7 @@ object FavouritePageViewModel : ViewModel() {
} }
} }
} }
// 更新收藏消息的查看时间
suspend fun updateNotice() { suspend fun updateNotice() {
var now = Calendar.getInstance().time var now = Calendar.getInstance().time
accountService.updateNotice( accountService.updateNotice(

View File

@@ -37,11 +37,12 @@ import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
import com.aiosman.riderpro.ui.like.ActionNoticeItem import com.aiosman.riderpro.ui.like.ActionNoticeItem
import com.aiosman.riderpro.ui.like.LikePageViewModel import com.aiosman.riderpro.ui.like.LikePageViewModel
@Preview /**
* 收藏消息界面
*/
@Composable @Composable
fun FavouriteScreen() { fun FavouriteScreen() {
val model = FavouritePageViewModel val model = FavouritePageViewModel
val coroutineScope = rememberCoroutineScope()
val listState = rememberLazyListState() val listState = rememberLazyListState()
var dataFlow = model.favouriteItemsFlow var dataFlow = model.favouriteItemsFlow
var favourites = dataFlow.collectAsLazyPagingItems() var favourites = dataFlow.collectAsLazyPagingItems()
@@ -59,7 +60,9 @@ fun FavouriteScreen() {
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) { ) {
Box( Box(
modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp) modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) { ) {
NoticeScreenHeader( NoticeScreenHeader(
"FAVOURITE", "FAVOURITE",

View File

@@ -35,7 +35,9 @@ import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Preview /**
* 关注消息列表
*/
@Composable @Composable
fun FollowerScreen() { fun FollowerScreen() {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()

View File

@@ -10,9 +10,9 @@ import androidx.paging.cachedIn
import androidx.paging.map import androidx.paging.map
import com.aiosman.riderpro.data.AccountFollow import com.aiosman.riderpro.data.AccountFollow
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.FollowItemPagingSource import com.aiosman.riderpro.entity.FollowItemPagingSource
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl import com.aiosman.riderpro.data.UserServiceImpl
import com.aiosman.riderpro.data.UserService import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.data.api.ApiClient import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
@@ -21,9 +21,12 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* 关注消息列表的 ViewModel
*/
object FollowerViewModel : ViewModel() { object FollowerViewModel : ViewModel() {
private val accountService: AccountService = AccountServiceImpl() private val accountService: AccountService = AccountServiceImpl()
private val userService: UserService = TestUserServiceImpl() private val userService: UserService = UserServiceImpl()
private val _followerItemsFlow = private val _followerItemsFlow =
MutableStateFlow<PagingData<AccountFollow>>(PagingData.empty()) MutableStateFlow<PagingData<AccountFollow>>(PagingData.empty())
val followerItemsFlow = _followerItemsFlow.asStateFlow() val followerItemsFlow = _followerItemsFlow.asStateFlow()

View File

@@ -1,6 +1,6 @@
package com.aiosman.riderpro.ui.imageviewer package com.aiosman.riderpro.ui.imageviewer
import com.aiosman.riderpro.model.MomentImageEntity import com.aiosman.riderpro.entity.MomentImageEntity
object ImageViewerViewModel { object ImageViewerViewModel {
var imageList = mutableListOf<MomentImageEntity>() var imageList = mutableListOf<MomentImageEntity>()

View File

@@ -60,7 +60,7 @@ fun IndexScreen() {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
// systemUiController.setNavigationBarColor(Color.Transparent) systemUiController.setNavigationBarColor(Color.Transparent)
} }
Scaffold( Scaffold(
bottomBar = { bottomBar = {

View File

@@ -32,17 +32,18 @@ import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.CommentEntity import com.aiosman.riderpro.entity.CommentEntity
import com.aiosman.riderpro.exp.timeAgo import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
import com.aiosman.riderpro.ui.composables.CustomAsyncImage import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Preview(showBackground = true) /**
* 消息列表界面
*/
@Composable @Composable
fun NotificationsScreen() { fun NotificationsScreen() {
val model = MessageListViewModel val model = MessageListViewModel

View File

@@ -12,12 +12,12 @@ import androidx.paging.cachedIn
import androidx.paging.map import androidx.paging.map
import com.aiosman.riderpro.data.AccountNotice import com.aiosman.riderpro.data.AccountNotice
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.CommentEntity import com.aiosman.riderpro.entity.CommentEntity
import com.aiosman.riderpro.data.CommentPagingSource import com.aiosman.riderpro.entity.CommentPagingSource
import com.aiosman.riderpro.data.CommentRemoteDataSource import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.CommentService import com.aiosman.riderpro.data.CommentService
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestCommentServiceImpl import com.aiosman.riderpro.data.CommentServiceImpl
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@@ -27,7 +27,7 @@ object MessageListViewModel : ViewModel() {
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
var noticeInfo by mutableStateOf<AccountNotice?>(null) var noticeInfo by mutableStateOf<AccountNotice?>(null)
private val commentService: CommentService = TestCommentServiceImpl() private val commentService: CommentService = CommentServiceImpl()
private val _commentItemsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty()) private val _commentItemsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty())
val commentItemsFlow = _commentItemsFlow.asStateFlow() val commentItemsFlow = _commentItemsFlow.asStateFlow()

View File

@@ -42,7 +42,6 @@ import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -65,9 +64,9 @@ import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.entity.MomentImageEntity
import com.aiosman.riderpro.exp.timeAgo import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.model.MomentEntity
import com.aiosman.riderpro.model.MomentImageEntity
import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.comment.CommentModalContent import com.aiosman.riderpro.ui.comment.CommentModalContent
import com.aiosman.riderpro.ui.composables.AnimatedCounter import com.aiosman.riderpro.ui.composables.AnimatedCounter
@@ -78,9 +77,11 @@ import com.aiosman.riderpro.ui.composables.RelPostCard
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.aiosman.riderpro.ui.post.NewPostViewModel import com.aiosman.riderpro.ui.post.NewPostViewModel
import com.aiosman.riderpro.ui.post.PostViewModel import com.aiosman.riderpro.ui.post.PostViewModel
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* 动态列表
*/
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun MomentsList() { fun MomentsList() {
@@ -370,21 +371,21 @@ fun PostImageView(
// ) // )
// .fillMaxSize() // .fillMaxSize()
// ) // )
CustomAsyncImage( CustomAsyncImage(
context, context,
image.thumbnail, image.thumbnail,
contentDescription = "Image", contentDescription = "Image",
blurHash = image.blurHash, blurHash = image.blurHash,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
// .noRippleClickable { // .noRippleClickable {
// ImageViewerViewModel.asNew(images, page) // ImageViewerViewModel.asNew(images, page)
// navController.navigate( // navController.navigate(
// NavigationRoute.ImageViewer.route // NavigationRoute.ImageViewer.route
// ) // )
// } // }
) )
} }

View File

@@ -8,12 +8,12 @@ import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import androidx.paging.map import androidx.paging.map
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.MomentPagingSource import com.aiosman.riderpro.entity.MomentPagingSource
import com.aiosman.riderpro.data.MomentRemoteDataSource import com.aiosman.riderpro.entity.MomentRemoteDataSource
import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.entity.MomentServiceImpl
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@@ -21,7 +21,7 @@ import kotlinx.coroutines.launch
object MomentViewModel : ViewModel() { object MomentViewModel : ViewModel() {
private val momentService: MomentService = TestMomentServiceImpl() private val momentService: MomentService = MomentServiceImpl()
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty()) private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
val momentsFlow = _momentsFlow.asStateFlow() val momentsFlow = _momentsFlow.asStateFlow()
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()

View File

@@ -7,19 +7,19 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import com.aiosman.riderpro.AppStore import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.data.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.MomentPagingSource import com.aiosman.riderpro.entity.MomentPagingSource
import com.aiosman.riderpro.data.MomentRemoteDataSource import com.aiosman.riderpro.entity.MomentRemoteDataSource
import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.entity.MomentServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl import com.aiosman.riderpro.data.UserServiceImpl
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
object MyProfileViewModel { object MyProfileViewModel {
val service: AccountService = AccountServiceImpl() val service: AccountService = AccountServiceImpl()
val userService = TestUserServiceImpl() val userService = UserServiceImpl()
var profile by mutableStateOf<AccountProfileEntity?>(null) var profile by mutableStateOf<AccountProfileEntity?>(null)
var momentsFlow by mutableStateOf<Flow<PagingData<MomentEntity>>?>(null) var momentsFlow by mutableStateOf<Flow<PagingData<MomentEntity>>?>(null)
suspend fun loadProfile() { suspend fun loadProfile() {
@@ -28,7 +28,7 @@ object MyProfileViewModel {
config = PagingConfig(pageSize = 5, enablePlaceholders = false), config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = { pagingSourceFactory = {
MomentPagingSource( MomentPagingSource(
MomentRemoteDataSource(TestMomentServiceImpl()), MomentRemoteDataSource(MomentServiceImpl()),
author = profile?.id ?: 0, author = profile?.id ?: 0,
) )

View File

@@ -4,7 +4,6 @@ import androidx.annotation.DrawableRes
import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -47,14 +46,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import coil.compose.AsyncImage
import com.aiosman.riderpro.LocalAnimatedContentScope import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.exp.formatPostTime import com.aiosman.riderpro.exp.formatPostTime
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.CustomAsyncImage import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable

View File

@@ -44,7 +44,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.data.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.ui.composables.CustomAsyncImage import com.aiosman.riderpro.ui.composables.CustomAsyncImage
import com.aiosman.riderpro.ui.index.tabs.moment.MomentCard import com.aiosman.riderpro.ui.index.tabs.moment.MomentCard
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable

View File

@@ -9,16 +9,14 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.aiosman.riderpro.data.AccountPagingSource import com.aiosman.riderpro.entity.AccountPagingSource
import com.aiosman.riderpro.data.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.data.MomentPagingSource import com.aiosman.riderpro.entity.MomentPagingSource
import com.aiosman.riderpro.data.MomentRemoteDataSource import com.aiosman.riderpro.entity.MomentRemoteDataSource
import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.entity.MomentServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl import com.aiosman.riderpro.data.UserServiceImpl
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel
import com.aiosman.riderpro.ui.index.tabs.moment.MomentViewModel.accountService
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@@ -26,11 +24,11 @@ import kotlinx.coroutines.launch
object SearchViewModel : ViewModel() { object SearchViewModel : ViewModel() {
var searchText by mutableStateOf("") var searchText by mutableStateOf("")
private val momentService: MomentService = TestMomentServiceImpl() private val momentService: MomentService = MomentServiceImpl()
private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty()) private val _momentsFlow = MutableStateFlow<PagingData<MomentEntity>>(PagingData.empty())
val momentsFlow = _momentsFlow.asStateFlow() val momentsFlow = _momentsFlow.asStateFlow()
private val userService = TestUserServiceImpl() private val userService = UserServiceImpl()
private val _usersFlow = MutableStateFlow<PagingData<AccountProfileEntity>>(PagingData.empty()) private val _usersFlow = MutableStateFlow<PagingData<AccountProfileEntity>>(PagingData.empty())
val usersFlow = _usersFlow.asStateFlow() val usersFlow = _usersFlow.asStateFlow()
var showResult by mutableStateOf(false) var showResult by mutableStateOf(false)

View File

@@ -7,9 +7,9 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.aiosman.riderpro.data.AccountLikeEntity import com.aiosman.riderpro.entity.AccountLikeEntity
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.LikeItemPagingSource import com.aiosman.riderpro.entity.LikeItemPagingSource
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.api.ApiClient import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody

View File

@@ -8,9 +8,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.entity.MomentServiceImpl
import com.aiosman.riderpro.data.UploadImage import com.aiosman.riderpro.data.UploadImage
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.ui.modification.Modification import com.aiosman.riderpro.ui.modification.Modification
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -20,7 +20,7 @@ import java.io.InputStream
object NewPostViewModel : ViewModel() { object NewPostViewModel : ViewModel() {
var momentService: MomentService = TestMomentServiceImpl() var momentService: MomentService = MomentServiceImpl()
var textContent by mutableStateOf("") var textContent by mutableStateOf("")
var searchPlaceAddressResult by mutableStateOf<SearchPlaceAddressResult?>(null) var searchPlaceAddressResult by mutableStateOf<SearchPlaceAddressResult?>(null)
var modificationList by mutableStateOf<List<Modification>>(listOf()) var modificationList by mutableStateOf<List<Modification>>(listOf())

View File

@@ -61,9 +61,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.Pager import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
@@ -75,22 +73,22 @@ import com.aiosman.riderpro.LocalAnimatedContentScope
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.LocalSharedTransitionScope import com.aiosman.riderpro.LocalSharedTransitionScope
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.CommentEntity import com.aiosman.riderpro.entity.CommentEntity
import com.aiosman.riderpro.data.CommentPagingSource import com.aiosman.riderpro.entity.CommentPagingSource
import com.aiosman.riderpro.data.CommentRemoteDataSource import com.aiosman.riderpro.data.CommentRemoteDataSource
import com.aiosman.riderpro.data.CommentService import com.aiosman.riderpro.data.CommentService
import com.aiosman.riderpro.data.TestCommentServiceImpl import com.aiosman.riderpro.data.CommentServiceImpl
import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.data.MomentService
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.entity.MomentServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl import com.aiosman.riderpro.data.UserServiceImpl
import com.aiosman.riderpro.data.UserService import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.exp.formatPostTime import com.aiosman.riderpro.exp.formatPostTime
import com.aiosman.riderpro.exp.timeAgo import com.aiosman.riderpro.exp.timeAgo
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.model.MomentImageEntity import com.aiosman.riderpro.entity.MomentImageEntity
import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.NavigationRoute
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder
@@ -106,9 +104,9 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
object PostViewModel : ViewModel() { object PostViewModel : ViewModel() {
var service: MomentService = TestMomentServiceImpl() var service: MomentService = MomentServiceImpl()
var commentService: CommentService = TestCommentServiceImpl() var commentService: CommentService = CommentServiceImpl()
var userService: UserService = TestUserServiceImpl() var userService: UserService = UserServiceImpl()
private var _commentsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty()) private var _commentsFlow = MutableStateFlow<PagingData<CommentEntity>>(PagingData.empty())
val commentsFlow = _commentsFlow.asStateFlow() val commentsFlow = _commentsFlow.asStateFlow()
var postId: String = "" var postId: String = ""
@@ -146,7 +144,7 @@ object PostViewModel : ViewModel() {
suspend fun initData() { suspend fun initData() {
moment = service.getMomentById(postId.toInt()) moment = service.getMomentById(postId.toInt())
moment?.let { moment?.let {
accountProfileEntity = accountService.getAccountProfileById(it.authorId) accountProfileEntity = userService.getUserProfile(it.authorId.toString())
} }
} }

View File

@@ -18,13 +18,13 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig import androidx.paging.PagingConfig
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.riderpro.data.AccountProfileEntity import com.aiosman.riderpro.entity.AccountProfileEntity
import com.aiosman.riderpro.data.MomentPagingSource import com.aiosman.riderpro.entity.MomentPagingSource
import com.aiosman.riderpro.data.MomentRemoteDataSource import com.aiosman.riderpro.entity.MomentRemoteDataSource
import com.aiosman.riderpro.data.TestMomentServiceImpl import com.aiosman.riderpro.entity.MomentServiceImpl
import com.aiosman.riderpro.data.TestUserServiceImpl import com.aiosman.riderpro.data.UserServiceImpl
import com.aiosman.riderpro.data.UserService import com.aiosman.riderpro.data.UserService
import com.aiosman.riderpro.model.MomentEntity import com.aiosman.riderpro.entity.MomentEntity
import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout
import com.aiosman.riderpro.ui.index.tabs.profile.CarGroup import com.aiosman.riderpro.ui.index.tabs.profile.CarGroup
import com.aiosman.riderpro.ui.index.tabs.profile.MomentPostUnit import com.aiosman.riderpro.ui.index.tabs.profile.MomentPostUnit
@@ -36,9 +36,9 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun AccountProfile(id:String) { fun AccountProfile(id:String) {
val userService: UserService = TestUserServiceImpl() val userService: UserService = UserServiceImpl()
var userProfile by remember { mutableStateOf<AccountProfileEntity?>(null) } var userProfile by remember { mutableStateOf<AccountProfileEntity?>(null) }
val momentService = TestMomentServiceImpl() val momentService = MomentServiceImpl()
var momentsFlow by remember { mutableStateOf<Flow<PagingData<MomentEntity>>?>(null) } var momentsFlow by remember { mutableStateOf<Flow<PagingData<MomentEntity>>?>(null) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {