更新消息功能

This commit is contained in:
2024-08-20 19:48:12 +08:00
parent 6137e1c3b5
commit 5228fde035
18 changed files with 1077 additions and 259 deletions

View File

@@ -7,8 +7,9 @@ import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.ChangePasswordRequestBody
import com.aiosman.riderpro.data.api.LoginUserRequestBody
import com.aiosman.riderpro.data.api.RegisterRequestBody
import com.aiosman.riderpro.model.MomentEntity
import com.aiosman.riderpro.data.api.UpdateNoticeRequestBody
import com.aiosman.riderpro.test.TestDatabase
import com.google.gson.annotations.SerializedName
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
@@ -16,7 +17,18 @@ import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
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,
@@ -28,14 +40,6 @@ data class AccountProfileEntity(
val isFollowing: Boolean
)
//{
// "id": 1,
// "username": "root",
// "nickname": "rider_4351",
// "avatar": "/api/v1/public/default_avatar.jpeg",
// "followingCount": 1,
// "followerCount": 0
//}
data class AccountProfile(
val id: Int,
val username: String,
@@ -59,6 +63,219 @@ data class AccountProfile(
}
}
data class NoticePostEntity(
val id: Int,
val textContent: String,
val images: List<Image>,
val time: Date,
)
data class NoticePost(
@SerializedName("id")
val id: Int,
@SerializedName("textContent")
val textContent: String,
@SerializedName("images")
val images: List<Image>,
@SerializedName("time")
val time: String,
) {
fun toNoticePostEntity(): NoticePostEntity {
return NoticePostEntity(
id = id,
textContent = textContent,
images = images.map {
it.copy(
url = ApiClient.BASE_SERVER + it.url + "?token=${AppStore.token}",
thumbnail = ApiClient.BASE_SERVER + it.thumbnail + "?token=${AppStore.token}",
)
},
time = ApiClient.dateFromApiString(time)
)
}
}
data class NoticeUserEntity(
val id: Int,
val nickName: String,
val avatar: String,
)
data class NoticeUser(
@SerializedName("id")
val id: Int,
@SerializedName("nickName")
val nickName: String,
@SerializedName("avatar")
val avatar: String,
) {
fun toNoticeUserEntity(): NoticeUserEntity {
return NoticeUserEntity(
id = id,
nickName = nickName,
avatar = ApiClient.BASE_SERVER + avatar + "?token=${AppStore.token}",
)
}
}
data class AccountLike(
@SerializedName("isUnread")
val isUnread: Boolean,
@SerializedName("post")
val post: NoticePost,
@SerializedName("user")
val user: NoticeUser,
@SerializedName("likeTime")
val likeTime: String,
) {
fun toAccountLikeEntity(): AccountLikeEntity {
return AccountLikeEntity(
post = post.toNoticePostEntity(),
user = user.toNoticeUserEntity(),
likeTime = ApiClient.dateFromApiString(likeTime)
)
}
}
data class AccountFavourite(
@SerializedName("isUnread")
val isUnread: Boolean,
@SerializedName("post")
val post: NoticePost,
@SerializedName("user")
val user: NoticeUser,
@SerializedName("favoriteTime")
val favouriteTime: String,
) {
fun toAccountFavouriteEntity(): AccountFavouriteEntity {
return AccountFavouriteEntity(
post = post.toNoticePostEntity(),
user = user.toNoticeUserEntity(),
favoriteTime = ApiClient.dateFromApiString(favouriteTime)
)
}
}
data class AccountFollow(
@SerializedName("id")
val id: Int,
@SerializedName("username")
val username: String,
@SerializedName("nickname")
val nickname: String,
@SerializedName("avatar")
val avatar: String,
@SerializedName("isUnread")
val isUnread: Boolean,
@SerializedName("userId")
val userId: Int,
@SerializedName("isFollowing")
val isFollowing: Boolean,
)
//{
// "likeCount": 0,
// "followCount": 0,
// "favoriteCount": 0
//}
data class AccountNotice(
@SerializedName("likeCount")
val likeCount: Int,
@SerializedName("followCount")
val followCount: Int,
@SerializedName("favoriteCount")
val favoriteCount: Int,
@SerializedName("commentCount")
val commentCount: Int,
)
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 + "?token=${AppStore.token}",
)
},
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 {
suspend fun getMyAccountProfile(): AccountProfileEntity
suspend fun getAccountProfileById(id: Int): AccountProfileEntity
@@ -69,6 +286,11 @@ interface AccountService {
suspend fun updateProfile(avatar: UploadImage?, nickName: String?, bio: String?)
suspend fun registerUserWithPassword(loginName: String, password: String)
suspend fun changeAccountPassword(oldPassword: String, newPassword: String)
suspend fun getMyLikeNotice(page: Int, pageSize: Int): ListContainer<AccountLike>
suspend fun getMyFollowNotice(page: Int, pageSize: Int): ListContainer<AccountFollow>
suspend fun getMyFavouriteNotice(page: Int, pageSize: Int): ListContainer<AccountFavourite>
suspend fun getMyNoticeInfo(): AccountNotice
suspend fun updateNotice(payload: UpdateNoticeRequestBody)
}
class TestAccountServiceImpl : AccountService {
@@ -130,4 +352,36 @@ class TestAccountServiceImpl : AccountService {
override suspend fun changeAccountPassword(oldPassword: String, newPassword: String) {
ApiClient.api.changePassword(ChangePasswordRequestBody(oldPassword, newPassword))
}
override suspend fun getMyLikeNotice(page: Int, pageSize: Int): ListContainer<AccountLike> {
val resp = ApiClient.api.getMyLikeNotices(page, pageSize)
val body = resp.body() ?: throw ServiceException("Failed to get account")
return body
}
override suspend fun getMyFollowNotice(page: Int, pageSize: Int): ListContainer<AccountFollow> {
val resp = ApiClient.api.getMyFollowNotices(page, pageSize)
val body = resp.body() ?: throw ServiceException("Failed to get account")
return body
}
override suspend fun getMyFavouriteNotice(
page: Int,
pageSize: Int
): ListContainer<AccountFavourite> {
val resp = ApiClient.api.getMyFavouriteNotices(page, pageSize)
val body = resp.body() ?: throw ServiceException("Failed to get account")
return body
}
override suspend fun getMyNoticeInfo(): AccountNotice {
val resp = ApiClient.api.getMyNoticeInfo()
val body = resp.body() ?: throw ServiceException("Failed to get account")
return body.data
}
override suspend fun updateNotice(payload: UpdateNoticeRequestBody) {
ApiClient.api.updateNoticeInfo(payload)
}
}

View File

@@ -1,2 +0,0 @@
package com.aiosman.riderpro.data

View File

@@ -9,27 +9,23 @@ import com.aiosman.riderpro.test.TestDatabase
import com.google.gson.annotations.SerializedName
import java.io.IOException
import java.util.Calendar
import java.util.Date
import kotlin.math.min
interface CommentService {
suspend fun getComments(pageNumber: Int, postId: Int? = null): ListContainer<CommentEntity>
suspend fun getComments(
pageNumber: Int,
postId: Int? = null,
postUser: Int? = null,
selfNotice: Boolean? = null
): ListContainer<CommentEntity>
suspend fun createComment(postId: Int, content: String)
suspend fun likeComment(commentId: Int)
suspend fun dislikeComment(commentId: Int)
suspend fun updateReadStatus(commentId: Int)
}
//{
// "id": 2,
// "content": "123",
// "User": {
// "id": 1,
// "nickName": "",
// "avatar": "/api/v1/public/default_avatar.jpeg"
//},
// "likeCount": 1,
// "isLiked": true,
// "createdAt": "2024-08-05 02:53:48"
//}
data class Comment(
@SerializedName("id")
val id: Int,
@@ -42,20 +38,37 @@ data class Comment(
@SerializedName("isLiked")
val isLiked: Boolean,
@SerializedName("createdAt")
val createdAt: String
val createdAt: String,
@SerializedName("postId")
val postId: Int,
@SerializedName("post")
val post: NoticePost?,
@SerializedName("isUnread")
val isUnread: Boolean
) {
fun toCommentEntity(): CommentEntity {
return CommentEntity(
id = id,
name = user.nickName,
comment = content,
date = createdAt,
date = ApiClient.dateFromApiString(createdAt),
likes = likeCount,
replies = emptyList(),
postId = 0,
postId = postId,
avatar = ApiClient.BASE_SERVER + user.avatar + "?token=${AppStore.token}",
author = user.id,
liked = isLiked
liked = isLiked,
unread = isUnread,
post = post?.let {
it.copy(
images = it.images.map {
it.copy(
url = ApiClient.BASE_SERVER + it.url + "?token=${AppStore.token}",
thumbnail = ApiClient.BASE_SERVER + it.thumbnail + "?token=${AppStore.token}"
)
}
)
}
)
}
}
@@ -64,25 +77,31 @@ data class CommentEntity(
val id: Int,
val name: String,
val comment: String,
val date: 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 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
postId = postId,
postUser = postUser,
selfNotice = selfNotice
)
LoadResult.Page(
data = comments.list,
@@ -103,15 +122,37 @@ class CommentPagingSource(
class CommentRemoteDataSource(
private val commentService: CommentService,
) {
suspend fun getComments(pageNumber: Int, postId: Int?): ListContainer<CommentEntity> {
return commentService.getComments(pageNumber, postId)
suspend fun getComments(
pageNumber: Int,
postId: Int?,
postUser: Int?,
selfNotice: Boolean?
): ListContainer<CommentEntity> {
return commentService.getComments(
pageNumber,
postId,
postUser = postUser,
selfNotice = selfNotice
)
}
}
class TestCommentServiceImpl : CommentService {
override suspend fun getComments(pageNumber: Int, postId: Int?): ListContainer<CommentEntity> {
val resp = ApiClient.api.getComments(pageNumber, postId)
override suspend fun getComments(
pageNumber: Int,
postId: Int?,
postUser: Int?,
selfNotice: Boolean?
): ListContainer<CommentEntity> {
val resp = ApiClient.api.getComments(
pageNumber,
postId,
postUser = postUser,
selfNotice = selfNotice?.let {
if (it) 1 else 0
}
)
val body = resp.body() ?: throw ServiceException("Failed to get comments")
return ListContainer(
list = body.list.map { it.toCommentEntity() },
@@ -136,6 +177,11 @@ class TestCommentServiceImpl : CommentService {
return
}
override suspend fun updateReadStatus(commentId: Int) {
val resp = ApiClient.api.updateReadStatus(commentId)
return
}
companion object {
const val DataBatchSize = 5
}

View File

@@ -1,5 +1,6 @@
package com.aiosman.riderpro.data.api
import android.icu.text.SimpleDateFormat
import com.aiosman.riderpro.AppStore
import com.aiosman.riderpro.ConstVars
import okhttp3.Interceptor
@@ -8,6 +9,8 @@ import okhttp3.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.security.cert.CertificateException
import java.util.Date
import java.util.Locale
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
@@ -17,10 +20,18 @@ fun getUnsafeOkHttpClient(): OkHttpClient {
// Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {}
override fun checkClientTrusted(
chain: Array<java.security.cert.X509Certificate>,
authType: String
) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {}
override fun checkServerTrusted(
chain: Array<java.security.cert.X509Certificate>,
authType: String
) {
}
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf()
})
@@ -40,6 +51,7 @@ fun getUnsafeOkHttpClient(): OkHttpClient {
throw RuntimeException(e)
}
}
class AuthInterceptor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
@@ -52,6 +64,7 @@ object ApiClient {
const val BASE_SERVER = ConstVars.BASE_SERVER
const val BASE_API_URL = "${BASE_SERVER}/api/v1"
const val RETROFIT_URL = "${BASE_API_URL}/"
const val TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"
private val okHttpClient: OkHttpClient by lazy {
getUnsafeOkHttpClient()
}
@@ -62,9 +75,19 @@ object ApiClient {
.addConverterFactory(GsonConverterFactory.create())
.build()
}
val api: RiderProAPI by lazy {
retrofit.create(RiderProAPI::class.java)
}
fun formatTime(date: Date): String {
val dateFormat = SimpleDateFormat(TIME_FORMAT, Locale.getDefault())
return dateFormat.format(date)
}
fun dateFromApiString(apiString: String): Date {
val timeFormat = ApiClient.TIME_FORMAT
val simpleDateFormat = SimpleDateFormat(timeFormat, Locale.getDefault())
val date = simpleDateFormat.parse(apiString)
return date
}
}

View File

@@ -1,5 +1,9 @@
package com.aiosman.riderpro.data.api
import com.aiosman.riderpro.data.AccountFavourite
import com.aiosman.riderpro.data.AccountFollow
import com.aiosman.riderpro.data.AccountLike
import com.aiosman.riderpro.data.AccountNotice
import com.aiosman.riderpro.data.AccountProfile
import com.aiosman.riderpro.data.AccountProfileEntity
import com.aiosman.riderpro.data.Comment
@@ -59,6 +63,15 @@ data class ChangePasswordRequestBody(
val newPassword: String = ""
)
data class UpdateNoticeRequestBody(
@SerializedName("lastLookLikeTime")
val lastLookLikeTime: String? = null,
@SerializedName("lastLookFollowTime")
val lastLookFollowTime: String? = null,
@SerializedName("lastLookFavoriteTime")
val lastLookFavouriteTime: String? = null
)
interface RiderProAPI {
@POST("register")
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
@@ -76,6 +89,7 @@ interface RiderProAPI {
@Query("timelineId") timelineId: Int? = null,
@Query("authorId") authorId: Int? = null,
@Query("contentSearch") contentSearch: String? = null,
@Query("postUser") postUser: Int? = null,
): Response<ListContainer<Moment>>
@Multipart
@@ -126,12 +140,19 @@ interface RiderProAPI {
@Path("id") id: Int
): Response<Unit>
@POST("comment/{id}/read")
suspend fun updateReadStatus(
@Path("id") id: Int
): Response<Unit>
@GET("comments")
suspend fun getComments(
@Query("page") page: Int = 1,
@Query("postId") postId: Int? = null,
@Query("pageSize") pageSize: Int = 20,
@Query("postUser") postUser: Int? = null,
@Query("selfNotice") selfNotice: Int? = 0,
): Response<ListContainer<Comment>>
@GET("account/my")
@@ -149,6 +170,33 @@ interface RiderProAPI {
@Body body: ChangePasswordRequestBody
): Response<Unit>
@GET("account/my/notice/like")
suspend fun getMyLikeNotices(
@Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 20,
): Response<ListContainer<AccountLike>>
@GET("account/my/notice/follow")
suspend fun getMyFollowNotices(
@Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 20,
): Response<ListContainer<AccountFollow>>
@GET("account/my/notice/favourite")
suspend fun getMyFavouriteNotices(
@Query("page") page: Int = 1,
@Query("pageSize") pageSize: Int = 20,
): Response<ListContainer<AccountFavourite>>
@GET("account/my/notice")
suspend fun getMyNoticeInfo(): Response<DataContainer<AccountNotice>>
@POST("account/my/notice")
suspend fun updateNoticeInfo(
@Body body: UpdateNoticeRequestBody
): Response<Unit>
@GET("profile/{id}")
suspend fun getAccountProfileById(
@Path("id") id: Int