改包名com.aiosman.ravenow

This commit is contained in:
2024-11-17 20:07:42 +08:00
parent 914cfca6be
commit 074244c0f8
168 changed files with 897 additions and 970 deletions

View File

@@ -0,0 +1,199 @@
package com.aiosman.ravenow.entity
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.ravenow.data.AccountFollow
import com.aiosman.ravenow.data.AccountService
import com.aiosman.ravenow.data.Image
import com.aiosman.ravenow.data.api.ApiClient
import java.io.IOException
import java.util.Date
/**
* 用户点赞
*/
data class AccountLikeEntity(
// 动态
val post: NoticePostEntity?,
// 回复评论
val comment: NoticeCommentEntity?,
// 点赞用户
val user: NoticeUserEntity,
// 点赞时间
val likeTime: Date,
// 动态ID
val postId: Int
)
/**
* 用户收藏
*/
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,
// 主页背景图
val banner: String?,
// trtcUserId
val trtcUserId: String,
)
/**
* 消息关联的动态
*/
data class NoticePostEntity(
// 动态ID
val id: Int,
// 动态内容
val textContent: String,
// 动态图片
val images: List<Image>,
// 时间
val time: Date,
)
data class NoticeCommentEntity(
// 评论ID
val id: Int,
// 评论内容
val content: String,
// 评论时间
val time: Date,
// 引用评论
val replyComment: NoticeCommentEntity?,
// 动态
val postId: Int,
// 动态
val post : NoticePostEntity?,
)
/**
* 消息关联的用户
*/
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,101 @@
package com.aiosman.ravenow.entity
import android.content.Context
import android.icu.util.Calendar
import com.aiosman.ravenow.exp.formatChatTime
import com.google.gson.annotations.SerializedName
import com.tencent.imsdk.v2.V2TIMImageElem
import com.tencent.imsdk.v2.V2TIMMessage
data class ChatItem(
val message: String,
val avatar: String,
val time: String,
val userId: String,
val nickname: String,
val timeCategory: String = "",
val timestamp: Long = 0,
val imageList: MutableList<V2TIMImageElem.V2TIMImage> = emptyList<V2TIMImageElem.V2TIMImage>().toMutableList(),
val messageType: Int = 0,
val textDisplay: String = "",
val msgId: String, // Add this property
var showTimestamp: Boolean = false,
var showTimeDivider: Boolean = false
) {
companion object {
fun convertToChatItem(message: V2TIMMessage, context: Context): ChatItem? {
// val avatar = if (message.sender == userProfile?.trtcUserId) {
// userProfile?.avatar ?: ""
// } else {
// myProfile?.avatar ?: ""
// }
// val nickname = if (message.sender == userProfile?.trtcUserId) {
// userProfile?.nickName ?: ""
// } else {
// myProfile?.nickName ?: ""
// }
val timestamp = message.timestamp
val calendar = Calendar.getInstance()
calendar.timeInMillis = timestamp * 1000
val imageElm = message.imageElem?.imageList
when (message.elemType) {
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
val imageElm = message.imageElem?.imageList?.all {
it.size == 0
}
if (imageElm != true) {
return ChatItem(
message = "Image",
avatar = message.faceUrl,
time = calendar.time.formatChatTime(context),
userId = message.sender,
nickname = message.nickName,
timestamp = timestamp * 1000,
imageList = message.imageElem?.imageList
?: emptyList<V2TIMImageElem.V2TIMImage>().toMutableList(),
messageType = V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE,
textDisplay = "Image",
msgId = message.msgID // Add this line to include msgId
)
}
return null
}
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
return ChatItem(
message = message.textElem?.text ?: "Unsupported message type",
avatar = message.faceUrl,
time = calendar.time.formatChatTime(context),
userId = message.sender,
nickname = message.nickName,
timestamp = timestamp * 1000,
imageList = imageElm?.toMutableList()
?: emptyList<V2TIMImageElem.V2TIMImage>().toMutableList(),
messageType = V2TIMMessage.V2TIM_ELEM_TYPE_TEXT,
textDisplay = message.textElem?.text ?: "Unsupported message type",
msgId = message.msgID // Add this line to include msgId
)
}
else -> {
return null
}
}
}
}
}
data class ChatNotification(
@SerializedName("userId")
val userId: Int,
@SerializedName("userTrtcId")
val userTrtcId: String,
@SerializedName("targetUserId")
val targetUserId: Int,
@SerializedName("targetTrtcId")
val targetTrtcId: String,
@SerializedName("strategy")
val strategy: String
)

View File

@@ -0,0 +1,64 @@
package com.aiosman.ravenow.entity
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.ravenow.data.CommentRemoteDataSource
import com.aiosman.ravenow.data.NoticePost
import java.util.Date
data class CommentEntity(
val id: Int,
val name: String,
val comment: String,
val date: Date,
val likes: Int,
val postId: Int = 0,
val avatar: String,
val author: Long,
var liked: Boolean,
var unread: Boolean = false,
var post: NoticePost?,
var reply: List<CommentEntity>,
var replyUserId: Long?,
var replyUserNickname: String?,
var replyUserAvatar: String?,
var parentCommentId: Int?,
var replyCount: Int,
var replyPage: Int = 1
)
class CommentPagingSource(
private val remoteDataSource: CommentRemoteDataSource,
private val postId: Int? = null,
private val postUser: Int? = null,
private val selfNotice: Boolean? = null,
private val order: String? = null,
private val parentCommentId: Int? = 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,
order = order,
parentCommentId = parentCommentId,
pageSize = params.loadSize
)
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: Exception) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, CommentEntity>): Int? {
return state.anchorPosition
}
}

View File

@@ -0,0 +1,287 @@
package com.aiosman.ravenow.entity
import androidx.annotation.DrawableRes
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.ravenow.data.ListContainer
import com.aiosman.ravenow.data.MomentService
import com.aiosman.ravenow.data.ServiceException
import com.aiosman.ravenow.data.UploadImage
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.parseErrorResponse
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,
private val trend: Boolean? = false,
private val explore: Boolean? = false,
private val favoriteUserId: Int? = 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,
trend = trend,
explore = explore,
favoriteUserId = favoriteUserId
)
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?,
trend: Boolean?,
explore: Boolean?,
favoriteUserId: Int?
): ListContainer<MomentEntity> {
return momentService.getMoments(
pageNumber = pageNumber,
author = author,
timelineId = timelineId,
contentSearch = contentSearch,
trend = trend,
explore = explore,
favoriteUserId = favoriteUserId
)
}
}
class MomentServiceImpl() : MomentService {
val momentBackend = MomentBackend()
override suspend fun getMoments(
pageNumber: Int,
author: Int?,
timelineId: Int?,
contentSearch: String?,
trend: Boolean?,
explore: Boolean?,
favoriteUserId: Int?,
): ListContainer<MomentEntity> {
return momentBackend.fetchMomentItems(
pageNumber = pageNumber,
author = author,
timelineId = timelineId,
contentSearch = contentSearch,
trend = trend,
favoriteUserId = favoriteUserId,
explore = explore
)
}
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)
}
override suspend fun deleteMoment(id: Int) {
momentBackend.deleteMoment(id)
}
}
class MomentBackend {
val DataBatchSize = 20
suspend fun fetchMomentItems(
pageNumber: Int,
author: Int? = null,
timelineId: Int?,
contentSearch: String?,
trend: Boolean?,
explore: Boolean?,
favoriteUserId: Int? = null
): ListContainer<MomentEntity> {
val resp = ApiClient.api.getPosts(
pageSize = DataBatchSize,
page = pageNumber,
timelineId = timelineId,
authorId = author,
contentSearch = contentSearch,
trend = if (trend == true) "true" else "",
favouriteUserId = favoriteUserId,
explore = if (explore == true) "true" else ""
)
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)
if (!resp.isSuccessful) {
parseErrorResponse(resp.errorBody())?.let {
throw it.toServiceException()
}
throw ServiceException("Failed to get moment")
}
return resp.body()?.data?.toMomentItem() ?: throw ServiceException("Failed to get moment")
}
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)
}
suspend fun deleteMoment(id: Int) {
ApiClient.api.deletePost(id)
}
}
/**
* 动态图片
*/
data class MomentImageEntity(
// 图片ID
val id: Long,
// 图片URL
val url: String,
// 缩略图URL
val thumbnail: String,
// 图片BlurHash
val blurHash: String? = null,
// 宽度
var width: Int? = null,
// 高度
var height: Int? = 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,40 @@
package com.aiosman.ravenow.entity
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.ravenow.data.UserService
import java.io.IOException
/**
* 用户信息分页加载器
*/
class AccountPagingSource(
private val userService: UserService,
private val nickname: String? = null,
private val followerId: Int? = null,
private val followingId: Int? = 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,
followerId = followerId,
followingId = followingId
)
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
}
}