改包名com.aiosman.ravenow
This commit is contained in:
556
app/src/main/java/com/aiosman/ravenow/data/AccountService.kt
Normal file
556
app/src/main/java/com/aiosman/ravenow/data/AccountService.kt
Normal file
@@ -0,0 +1,556 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.AppConfig
|
||||
import com.aiosman.ravenow.data.api.CaptchaInfo
|
||||
import com.aiosman.ravenow.data.api.ChangePasswordRequestBody
|
||||
import com.aiosman.ravenow.data.api.GoogleRegisterRequestBody
|
||||
import com.aiosman.ravenow.data.api.LoginUserRequestBody
|
||||
import com.aiosman.ravenow.data.api.RegisterMessageChannelRequestBody
|
||||
import com.aiosman.ravenow.data.api.RegisterRequestBody
|
||||
import com.aiosman.ravenow.data.api.ResetPasswordRequestBody
|
||||
import com.aiosman.ravenow.data.api.TrtcSignResponseBody
|
||||
import com.aiosman.ravenow.data.api.UnRegisterMessageChannelRequestBody
|
||||
import com.aiosman.ravenow.data.api.UpdateNoticeRequestBody
|
||||
import com.aiosman.ravenow.data.api.UpdateUserLangRequestBody
|
||||
import com.aiosman.ravenow.entity.AccountFavouriteEntity
|
||||
import com.aiosman.ravenow.entity.AccountLikeEntity
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.NoticeCommentEntity
|
||||
import com.aiosman.ravenow.entity.NoticePostEntity
|
||||
import com.aiosman.ravenow.entity.NoticeUserEntity
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 用户资料
|
||||
*/
|
||||
data class AccountProfile(
|
||||
// 用户ID
|
||||
val id: Int,
|
||||
// 用户名
|
||||
val username: String,
|
||||
// 昵称
|
||||
val nickname: String,
|
||||
// 头像
|
||||
val avatar: String,
|
||||
// 关注数
|
||||
val followingCount: Int,
|
||||
// 粉丝数
|
||||
val followerCount: Int,
|
||||
// 是否关注
|
||||
val isFollowing: Boolean,
|
||||
// 个人简介
|
||||
val bio: String,
|
||||
// 主页背景图
|
||||
val banner: String?,
|
||||
// trtcUserId
|
||||
val trtcUserId: String,
|
||||
) {
|
||||
/**
|
||||
* 转换为Entity
|
||||
*/
|
||||
fun toAccountProfileEntity(): AccountProfileEntity {
|
||||
return AccountProfileEntity(
|
||||
id = id,
|
||||
followerCount = followerCount,
|
||||
followingCount = followingCount,
|
||||
nickName = nickname,
|
||||
avatar = "${ApiClient.BASE_SERVER}$avatar",
|
||||
bio = bio,
|
||||
country = "Worldwide",
|
||||
isFollowing = isFollowing,
|
||||
banner = banner.let {
|
||||
if (!it.isNullOrEmpty()) {
|
||||
return@let "${ApiClient.BASE_SERVER}$it"
|
||||
}
|
||||
null
|
||||
},
|
||||
trtcUserId = trtcUserId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息关联资料
|
||||
*/
|
||||
data class NoticePost(
|
||||
// 动态ID
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
// 动态内容
|
||||
@SerializedName("textContent")
|
||||
// 动态图片
|
||||
val textContent: String,
|
||||
// 动态图片
|
||||
@SerializedName("images")
|
||||
val images: List<Image>,
|
||||
// 动态时间
|
||||
@SerializedName("time")
|
||||
val time: String,
|
||||
) {
|
||||
/**
|
||||
* 转换为Entity
|
||||
*/
|
||||
fun toNoticePostEntity(): NoticePostEntity {
|
||||
return NoticePostEntity(
|
||||
id = id,
|
||||
textContent = textContent,
|
||||
images = images.map {
|
||||
it.copy(
|
||||
url = "${ApiClient.BASE_SERVER}${it.url}",
|
||||
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}",
|
||||
blurHash = it.blurHash,
|
||||
width = it.width,
|
||||
height = it.height
|
||||
)
|
||||
},
|
||||
time = ApiClient.dateFromApiString(time)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//"comment": {
|
||||
// "id": 103,
|
||||
// "content": "ppp",
|
||||
// "time": "2024-09-08 15:31:37"
|
||||
//}
|
||||
data class NoticeComment(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("content")
|
||||
val content: String,
|
||||
@SerializedName("time")
|
||||
val time: String,
|
||||
@SerializedName("replyComment")
|
||||
val replyComment: NoticeComment?,
|
||||
@SerializedName("postId")
|
||||
val postId: Int,
|
||||
@SerializedName("post")
|
||||
val post: NoticePost?,
|
||||
) {
|
||||
fun toNoticeCommentEntity(): NoticeCommentEntity {
|
||||
return NoticeCommentEntity(
|
||||
id = id,
|
||||
content = content,
|
||||
postId = postId,
|
||||
time = ApiClient.dateFromApiString(time),
|
||||
replyComment = replyComment?.toNoticeCommentEntity(),
|
||||
post = post?.toNoticePostEntity()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息关联用户
|
||||
*/
|
||||
data class NoticeUser(
|
||||
// 用户ID
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
// 昵称
|
||||
@SerializedName("nickName")
|
||||
val nickName: String,
|
||||
// 头像
|
||||
@SerializedName("avatar")
|
||||
val avatar: String,
|
||||
) {
|
||||
/**
|
||||
* 转换为Entity
|
||||
*/
|
||||
fun toNoticeUserEntity(): NoticeUserEntity {
|
||||
return NoticeUserEntity(
|
||||
id = id,
|
||||
nickName = nickName,
|
||||
avatar = "${ApiClient.BASE_SERVER}$avatar",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点赞消息通知
|
||||
*/
|
||||
data class AccountLike(
|
||||
// 是否未读
|
||||
@SerializedName("isUnread")
|
||||
val isUnread: Boolean,
|
||||
// 动态
|
||||
@SerializedName("post")
|
||||
val post: NoticePost?,
|
||||
@SerializedName("comment")
|
||||
val comment: NoticeComment?,
|
||||
// 点赞用户
|
||||
@SerializedName("user")
|
||||
val user: NoticeUser,
|
||||
// 点赞时间
|
||||
@SerializedName("likeTime")
|
||||
val likeTime: String,
|
||||
// 动态ID
|
||||
@SerializedName("postId")
|
||||
val postId: Int,
|
||||
) {
|
||||
fun toAccountLikeEntity(): AccountLikeEntity {
|
||||
return AccountLikeEntity(
|
||||
post = post?.toNoticePostEntity(),
|
||||
comment = comment?.toNoticeCommentEntity(),
|
||||
user = user.toNoticeUserEntity(),
|
||||
likeTime = ApiClient.dateFromApiString(likeTime),
|
||||
postId = postId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
interface AccountService {
|
||||
/**
|
||||
* 获取登录当前用户的资料
|
||||
*/
|
||||
suspend fun getMyAccountProfile(): AccountProfileEntity
|
||||
|
||||
/**
|
||||
* 获取登录的用户认证信息
|
||||
*/
|
||||
suspend fun getMyAccount(): UserAuth
|
||||
|
||||
/**
|
||||
* 使用用户名密码登录
|
||||
* @param loginName 用户名
|
||||
* @param password 密码
|
||||
* @param captchaInfo 验证码信息
|
||||
*/
|
||||
suspend fun loginUserWithPassword(
|
||||
loginName: String,
|
||||
password: String,
|
||||
captchaInfo: CaptchaInfo? = null
|
||||
): UserAuth
|
||||
|
||||
/**
|
||||
* 使用google登录
|
||||
* @param googleId googleId
|
||||
*/
|
||||
suspend fun loginUserWithGoogle(googleId: String): UserAuth
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
suspend fun logout()
|
||||
|
||||
/**
|
||||
* 更新用户资料
|
||||
* @param avatar 头像
|
||||
* @param nickName 昵称
|
||||
* @param bio 简介
|
||||
* @param banner 主页背景图
|
||||
*/
|
||||
suspend fun updateProfile(
|
||||
avatar: UploadImage?,
|
||||
banner: UploadImage?,
|
||||
nickName: String?,
|
||||
bio: String?
|
||||
)
|
||||
|
||||
/**
|
||||
* 注册用户
|
||||
* @param loginName 用户名
|
||||
* @param password 密码
|
||||
*/
|
||||
suspend fun registerUserWithPassword(loginName: String, password: String)
|
||||
|
||||
/**
|
||||
* 使用google账号注册
|
||||
* @param idToken googleIdToken
|
||||
*/
|
||||
suspend fun regiterUserWithGoogleAccount(idToken: String)
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param oldPassword 旧密码
|
||||
* @param newPassword 新密码
|
||||
*/
|
||||
suspend fun changeAccountPassword(oldPassword: String, newPassword: String)
|
||||
|
||||
/**
|
||||
* 获取我的点赞通知
|
||||
* @param page 页码
|
||||
* @param pageSize 每页数量
|
||||
*/
|
||||
suspend fun getMyLikeNotice(page: Int, pageSize: Int): ListContainer<AccountLike>
|
||||
|
||||
/**
|
||||
* 获取我的关注通知
|
||||
* @param page 页码
|
||||
* @param pageSize 每页数量
|
||||
*/
|
||||
suspend fun getMyFollowNotice(page: Int, pageSize: Int): ListContainer<AccountFollow>
|
||||
|
||||
/**
|
||||
* 获取我的收藏通知
|
||||
* @param page 页码
|
||||
* @param pageSize 每页数量
|
||||
*/
|
||||
suspend fun getMyFavouriteNotice(page: Int, pageSize: Int): ListContainer<AccountFavourite>
|
||||
|
||||
/**
|
||||
* 获取我的通知信息
|
||||
*/
|
||||
suspend fun getMyNoticeInfo(): AccountNotice
|
||||
|
||||
/**
|
||||
* 更新通知信息,更新最后一次查看时间
|
||||
* @param payload 通知信息
|
||||
*/
|
||||
suspend fun updateNotice(payload: UpdateNoticeRequestBody)
|
||||
|
||||
/**
|
||||
* 注册消息通道
|
||||
*/
|
||||
suspend fun registerMessageChannel(client: String, identifier: String)
|
||||
|
||||
/**
|
||||
* 取消注册消息通道
|
||||
*/
|
||||
suspend fun unregisterMessageChannel(client: String, identifier: String)
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
*/
|
||||
suspend fun resetPassword(email: String)
|
||||
|
||||
/**
|
||||
* 更新用户额外信息
|
||||
*/
|
||||
suspend fun updateUserExtra(language: String, timeOffset: Int, timezone: String)
|
||||
|
||||
/**
|
||||
* 获取腾讯云TRTC签名
|
||||
*/
|
||||
suspend fun getMyTrtcSign(): TrtcSignResponseBody
|
||||
|
||||
suspend fun getAppConfig(): AppConfig
|
||||
}
|
||||
|
||||
class AccountServiceImpl : AccountService {
|
||||
override suspend fun getMyAccountProfile(): AccountProfileEntity {
|
||||
val resp = ApiClient.api.getMyAccount()
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get account")
|
||||
return body.data.toAccountProfileEntity()
|
||||
}
|
||||
|
||||
override suspend fun getMyAccount(): UserAuth {
|
||||
val resp = ApiClient.api.checkToken()
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get account")
|
||||
AppState.UserId = body.id
|
||||
return UserAuth(body.id)
|
||||
}
|
||||
|
||||
override suspend fun loginUserWithPassword(
|
||||
loginName: String,
|
||||
password: String,
|
||||
captchaInfo: CaptchaInfo?
|
||||
): UserAuth {
|
||||
val resp = ApiClient.api.login(LoginUserRequestBody(
|
||||
username = loginName,
|
||||
password = password,
|
||||
captcha = captchaInfo,
|
||||
))
|
||||
if (!resp.isSuccessful) {
|
||||
parseErrorResponse(resp.errorBody())?.let {
|
||||
throw it.toServiceException()
|
||||
}
|
||||
throw ServiceException("Failed to register")
|
||||
}
|
||||
return UserAuth(0, resp.body()?.token)
|
||||
}
|
||||
|
||||
override suspend fun loginUserWithGoogle(googleId: String): UserAuth {
|
||||
val resp = ApiClient.api.login(LoginUserRequestBody(googleId = googleId))
|
||||
val body = resp.body() ?: throw ServiceException("Failed to login")
|
||||
|
||||
return UserAuth(0, body.token)
|
||||
}
|
||||
|
||||
override suspend fun regiterUserWithGoogleAccount(idToken: String) {
|
||||
val resp = ApiClient.api.registerWithGoogle(GoogleRegisterRequestBody(idToken))
|
||||
if (!resp.isSuccessful) {
|
||||
parseErrorResponse(resp.errorBody())?.let {
|
||||
throw it.toServiceException()
|
||||
}
|
||||
throw ServiceException("Failed to register")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun logout() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
||||
fun createMultipartBody(file: File, filename: String, name: String): MultipartBody.Part {
|
||||
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
|
||||
return MultipartBody.Part.createFormData(name, filename, requestFile)
|
||||
}
|
||||
|
||||
override suspend fun updateProfile(
|
||||
avatar: UploadImage?,
|
||||
banner: UploadImage?,
|
||||
nickName: String?,
|
||||
bio: String?
|
||||
) {
|
||||
val nicknameField: RequestBody? = nickName?.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val bioField: RequestBody? = bio?.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||
val avatarField: MultipartBody.Part? = avatar?.let {
|
||||
createMultipartBody(it.file, it.filename, "avatar")
|
||||
}
|
||||
val bannerField: MultipartBody.Part? = banner?.let {
|
||||
createMultipartBody(it.file, it.filename, "banner")
|
||||
}
|
||||
ApiClient.api.updateProfile(avatarField, bannerField, nicknameField, bioField)
|
||||
}
|
||||
|
||||
override suspend fun registerUserWithPassword(loginName: String, password: String) {
|
||||
val resp = ApiClient.api.register(RegisterRequestBody(loginName, password))
|
||||
|
||||
if (!resp.isSuccessful) {
|
||||
parseErrorResponse(resp.errorBody())?.let {
|
||||
throw it.toServiceException()
|
||||
}
|
||||
throw ServiceException("Failed to register")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun changeAccountPassword(oldPassword: String, newPassword: String) {
|
||||
val resp = ApiClient.api.changePassword(ChangePasswordRequestBody(oldPassword, newPassword))
|
||||
if (!resp.isSuccessful) {
|
||||
parseErrorResponse(resp.errorBody())?.let {
|
||||
throw it.toServiceException()
|
||||
}
|
||||
throw ServiceException("Failed to change password")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
override suspend fun registerMessageChannel(client: String, identifier: String) {
|
||||
ApiClient.api.registerMessageChannel(RegisterMessageChannelRequestBody(client, identifier))
|
||||
}
|
||||
|
||||
override suspend fun unregisterMessageChannel(client: String, identifier: String) {
|
||||
ApiClient.api.unRegisterMessageChannel(UnRegisterMessageChannelRequestBody(client, identifier))
|
||||
}
|
||||
|
||||
override suspend fun resetPassword(email: String) {
|
||||
val resp = ApiClient.api.resetPassword(
|
||||
ResetPasswordRequestBody(
|
||||
username = email
|
||||
)
|
||||
)
|
||||
if (!resp.isSuccessful) {
|
||||
parseErrorResponse(resp.errorBody())?.let {
|
||||
throw it.toServiceException()
|
||||
}
|
||||
throw ServiceException("Failed to reset password")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateUserExtra(language: String, timeOffset: Int, timezone: String) {
|
||||
ApiClient.api.updateUserExtra(UpdateUserLangRequestBody(language, timeOffset, timezone))
|
||||
}
|
||||
|
||||
override suspend fun getMyTrtcSign(): TrtcSignResponseBody {
|
||||
val resp = ApiClient.api.getChatSign()
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get trtc sign")
|
||||
return body.data
|
||||
}
|
||||
|
||||
override suspend fun getAppConfig(): AppConfig {
|
||||
val resp = ApiClient.api.getAppConfig()
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get app config")
|
||||
return body.data
|
||||
}
|
||||
}
|
||||
45
app/src/main/java/com/aiosman/ravenow/data/CaptchaService.kt
Normal file
45
app/src/main/java/com/aiosman/ravenow/data/CaptchaService.kt
Normal file
@@ -0,0 +1,45 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.CaptchaRequestBody
|
||||
import com.aiosman.ravenow.data.api.CaptchaResponseBody
|
||||
import com.aiosman.ravenow.data.api.CheckLoginCaptchaRequestBody
|
||||
import com.aiosman.ravenow.data.api.GenerateLoginCaptchaRequestBody
|
||||
|
||||
|
||||
interface CaptchaService {
|
||||
suspend fun generateCaptcha(source: String): CaptchaResponseBody
|
||||
suspend fun checkLoginCaptcha(username: String): Boolean
|
||||
suspend fun generateLoginCaptcha(username: String): CaptchaResponseBody
|
||||
}
|
||||
|
||||
class CaptchaServiceImpl : CaptchaService {
|
||||
override suspend fun generateCaptcha(source: String): CaptchaResponseBody {
|
||||
val resp = ApiClient.api.generateCaptcha(
|
||||
CaptchaRequestBody(source)
|
||||
)
|
||||
val data = resp.body() ?: throw Exception("Failed to generate captcha")
|
||||
return data.data.copy(
|
||||
masterBase64 = data.data.masterBase64.replace("data:image/jpeg;base64,", ""),
|
||||
thumbBase64 = data.data.thumbBase64.replace("data:image/png;base64,", "")
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun checkLoginCaptcha(username: String): Boolean {
|
||||
val resp = ApiClient.api.checkLoginCaptcha(
|
||||
CheckLoginCaptchaRequestBody(username)
|
||||
)
|
||||
return resp.body()?.data ?: true
|
||||
}
|
||||
|
||||
override suspend fun generateLoginCaptcha(username: String): CaptchaResponseBody {
|
||||
val resp = ApiClient.api.generateLoginCaptcha(
|
||||
GenerateLoginCaptchaRequestBody(username)
|
||||
)
|
||||
val data = resp.body() ?: throw Exception("Failed to generate captcha")
|
||||
return data.data.copy(
|
||||
masterBase64 = data.data.masterBase64.replace("data:image/jpeg;base64,", ""),
|
||||
thumbBase64 = data.data.thumbBase64.replace("data:image/png;base64,", "")
|
||||
)
|
||||
}
|
||||
}
|
||||
42
app/src/main/java/com/aiosman/ravenow/data/ChatService.kt
Normal file
42
app/src/main/java/com/aiosman/ravenow/data/ChatService.kt
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.UpdateChatNotificationRequestBody
|
||||
import com.aiosman.ravenow.entity.ChatNotification
|
||||
|
||||
interface ChatService {
|
||||
suspend fun getChatNotifications(
|
||||
targetTrtcId: String
|
||||
): ChatNotification?
|
||||
|
||||
suspend fun updateChatNotification(
|
||||
targetUserId: Int,
|
||||
strategy: String
|
||||
): ChatNotification
|
||||
}
|
||||
|
||||
class ChatServiceImpl : ChatService {
|
||||
override suspend fun getChatNotifications(
|
||||
targetTrtcId: String
|
||||
): ChatNotification? {
|
||||
val resp = ApiClient.api.getChatNotification(targetTrtcId)
|
||||
if (resp.isSuccessful) {
|
||||
return resp.body()?.data
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun updateChatNotification(
|
||||
targetUserId: Int,
|
||||
strategy: String
|
||||
): ChatNotification {
|
||||
val resp = ApiClient.api.updateChatNotification(UpdateChatNotificationRequestBody(
|
||||
targetUserId = targetUserId,
|
||||
strategy = strategy
|
||||
))
|
||||
if (resp.isSuccessful) {
|
||||
return resp.body()?.data!!
|
||||
}
|
||||
throw Exception("update chat notification failed")
|
||||
}
|
||||
}
|
||||
255
app/src/main/java/com/aiosman/ravenow/data/CommentService.kt
Normal file
255
app/src/main/java/com/aiosman/ravenow/data/CommentService.kt
Normal file
@@ -0,0 +1,255 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.CommentRequestBody
|
||||
import com.aiosman.ravenow.entity.CommentEntity
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* 评论相关 Service
|
||||
*/
|
||||
interface CommentService {
|
||||
/**
|
||||
* 获取动态
|
||||
* @param pageNumber 页码
|
||||
* @param postId 动态ID,过滤条件
|
||||
* @param postUser 动态作者ID,获取某个用户所有动态下的评论
|
||||
* @param selfNotice 是否是自己的通知
|
||||
* @param order 排序
|
||||
* @param parentCommentId 父评论ID
|
||||
* @param pageSize 每页数量
|
||||
* @return 评论列表
|
||||
*/
|
||||
suspend fun getComments(
|
||||
pageNumber: Int,
|
||||
postId: Int? = null,
|
||||
postUser: Int? = null,
|
||||
selfNotice: Boolean? = null,
|
||||
order: String? = null,
|
||||
parentCommentId: Int? = null,
|
||||
pageSize: Int? = null
|
||||
): ListContainer<CommentEntity>
|
||||
|
||||
/**
|
||||
* 创建评论
|
||||
* @param postId 动态ID
|
||||
* @param content 评论内容
|
||||
* @param parentCommentId 父评论ID
|
||||
* @param replyUserId 回复用户ID
|
||||
*/
|
||||
suspend fun createComment(
|
||||
postId: Int,
|
||||
content: String,
|
||||
parentCommentId: Int? = null,
|
||||
replyUserId: Int? = null,
|
||||
replyCommentId: Int? = null
|
||||
): CommentEntity
|
||||
|
||||
/**
|
||||
* 点赞评论
|
||||
* @param commentId 评论ID
|
||||
*/
|
||||
suspend fun likeComment(commentId: Int)
|
||||
|
||||
/**
|
||||
* 取消点赞评论
|
||||
* @param commentId 评论ID
|
||||
*/
|
||||
suspend fun dislikeComment(commentId: Int)
|
||||
|
||||
/**
|
||||
* 更新评论已读状态
|
||||
* @param commentId 评论ID
|
||||
*/
|
||||
suspend fun updateReadStatus(commentId: Int)
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
* @param commentId 评论ID
|
||||
*/
|
||||
suspend fun DeleteComment(commentId: Int)
|
||||
|
||||
/**
|
||||
* 获取评论
|
||||
* @param commentId 评论ID
|
||||
*/
|
||||
suspend fun getCommentById(commentId: Int): CommentEntity
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 评论
|
||||
*/
|
||||
data class Comment(
|
||||
// 评论ID
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
// 评论内容
|
||||
@SerializedName("content")
|
||||
val content: String,
|
||||
// 评论用户
|
||||
@SerializedName("user")
|
||||
val user: User,
|
||||
// 点赞数
|
||||
@SerializedName("likeCount")
|
||||
val likeCount: Int,
|
||||
// 是否点赞
|
||||
@SerializedName("isLiked")
|
||||
val isLiked: Boolean,
|
||||
// 创建时间
|
||||
@SerializedName("createdAt")
|
||||
val createdAt: String,
|
||||
// 动态ID
|
||||
@SerializedName("postId")
|
||||
val postId: Int,
|
||||
// 动态
|
||||
@SerializedName("post")
|
||||
val post: NoticePost?,
|
||||
// 是否未读
|
||||
@SerializedName("isUnread")
|
||||
val isUnread: Boolean,
|
||||
@SerializedName("reply")
|
||||
val reply: List<Comment>,
|
||||
@SerializedName("replyUser")
|
||||
val replyUser: User?,
|
||||
@SerializedName("parentCommentId")
|
||||
val parentCommentId: Int?,
|
||||
@SerializedName("replyCount")
|
||||
val replyCount: Int
|
||||
) {
|
||||
/**
|
||||
* 转换为Entity
|
||||
*/
|
||||
fun toCommentEntity(): CommentEntity {
|
||||
return CommentEntity(
|
||||
id = id,
|
||||
name = user.nickName,
|
||||
comment = content,
|
||||
date = ApiClient.dateFromApiString(createdAt),
|
||||
likes = likeCount,
|
||||
postId = postId,
|
||||
avatar = "${ApiClient.BASE_SERVER}${user.avatar}",
|
||||
author = user.id,
|
||||
liked = isLiked,
|
||||
unread = isUnread,
|
||||
post = post?.let {
|
||||
it.copy(
|
||||
images = it.images.map {
|
||||
it.copy(
|
||||
url = "${ApiClient.BASE_SERVER}${it.url}",
|
||||
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}"
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
reply = reply.map { it.toCommentEntity() },
|
||||
replyUserNickname = replyUser?.nickName,
|
||||
replyUserId = replyUser?.id,
|
||||
replyUserAvatar = replyUser?.avatar?.let { "${ApiClient.BASE_SERVER}$it" },
|
||||
parentCommentId = parentCommentId,
|
||||
replyCount = replyCount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class CommentRemoteDataSource(
|
||||
private val commentService: CommentService,
|
||||
) {
|
||||
suspend fun getComments(
|
||||
pageNumber: Int,
|
||||
postId: Int?,
|
||||
postUser: Int?,
|
||||
selfNotice: Boolean?,
|
||||
order: String?,
|
||||
parentCommentId: Int?,
|
||||
pageSize: Int? = 20
|
||||
): ListContainer<CommentEntity> {
|
||||
return commentService.getComments(
|
||||
pageNumber,
|
||||
postId,
|
||||
postUser = postUser,
|
||||
selfNotice = selfNotice,
|
||||
order = order,
|
||||
parentCommentId = parentCommentId,
|
||||
pageSize = pageSize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CommentServiceImpl : CommentService {
|
||||
override suspend fun getComments(
|
||||
pageNumber: Int,
|
||||
postId: Int?,
|
||||
postUser: Int?,
|
||||
selfNotice: Boolean?,
|
||||
order: String?,
|
||||
parentCommentId: Int?,
|
||||
pageSize: Int?
|
||||
): ListContainer<CommentEntity> {
|
||||
val resp = ApiClient.api.getComments(
|
||||
page = pageNumber,
|
||||
postId = postId,
|
||||
postUser = postUser,
|
||||
order = order,
|
||||
selfNotice = selfNotice?.let {
|
||||
if (it) 1 else 0
|
||||
},
|
||||
parentCommentId = parentCommentId,
|
||||
pageSize = pageSize ?: 20
|
||||
)
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get comments")
|
||||
return ListContainer(
|
||||
list = body.list.map { it.toCommentEntity() },
|
||||
page = body.page,
|
||||
total = body.total,
|
||||
pageSize = body.pageSize
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun createComment(
|
||||
postId: Int,
|
||||
content: String,
|
||||
parentCommentId: Int?,
|
||||
replyUserId: Int?,
|
||||
replyCommentId: Int?
|
||||
): CommentEntity {
|
||||
val resp = ApiClient.api.createComment(
|
||||
postId,
|
||||
CommentRequestBody(
|
||||
content = content,
|
||||
parentCommentId = parentCommentId,
|
||||
replyUserId = replyUserId,
|
||||
replyCommentId = replyCommentId
|
||||
),
|
||||
)
|
||||
val body = resp.body() ?: throw ServiceException("Failed to create comment")
|
||||
return body.data.toCommentEntity()
|
||||
}
|
||||
|
||||
override suspend fun likeComment(commentId: Int) {
|
||||
val resp = ApiClient.api.likeComment(commentId)
|
||||
return
|
||||
}
|
||||
|
||||
override suspend fun dislikeComment(commentId: Int) {
|
||||
val resp = ApiClient.api.dislikeComment(commentId)
|
||||
return
|
||||
}
|
||||
|
||||
override suspend fun updateReadStatus(commentId: Int) {
|
||||
val resp = ApiClient.api.updateReadStatus(commentId)
|
||||
return
|
||||
}
|
||||
|
||||
override suspend fun DeleteComment(commentId: Int) {
|
||||
val resp = ApiClient.api.deleteComment(commentId)
|
||||
return
|
||||
}
|
||||
|
||||
override suspend fun getCommentById(commentId: Int): CommentEntity {
|
||||
val resp = ApiClient.api.getComment(commentId)
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get comment")
|
||||
return body.data.toCommentEntity()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
/**
|
||||
* 通用接口返回数据
|
||||
*/
|
||||
data class DataContainer<T>(
|
||||
val data: T
|
||||
)
|
||||
19
app/src/main/java/com/aiosman/ravenow/data/DictService.kt
Normal file
19
app/src/main/java/com/aiosman/ravenow/data/DictService.kt
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.DictItem
|
||||
|
||||
|
||||
interface DictService {
|
||||
/**
|
||||
* 获取字典项
|
||||
*/
|
||||
suspend fun getDictByKey(key: String): DictItem
|
||||
}
|
||||
|
||||
class DictServiceImpl : DictService {
|
||||
override suspend fun getDictByKey(key: String): DictItem {
|
||||
val resp = ApiClient.api.getDict(key)
|
||||
return resp.body()?.data ?: throw Exception("failed to get dict")
|
||||
}
|
||||
}
|
||||
47
app/src/main/java/com/aiosman/ravenow/data/Exception.kt
Normal file
47
app/src/main/java/com/aiosman/ravenow/data/Exception.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.aiosman.ravenow.data.api.ErrorCode
|
||||
import com.aiosman.ravenow.data.api.toErrorCode
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import okhttp3.ResponseBody
|
||||
|
||||
/**
|
||||
* 错误返回
|
||||
*/
|
||||
class ServiceException(
|
||||
override val message: String,
|
||||
val code: Int? = 0,
|
||||
val data: Any? = null,
|
||||
val error: String? = null,
|
||||
val name: String? = null,
|
||||
val errorType: ErrorCode = ErrorCode.UNKNOWN
|
||||
) : Exception(
|
||||
message
|
||||
)
|
||||
|
||||
data class ApiErrorResponse(
|
||||
@SerializedName("code")
|
||||
val code: Int?,
|
||||
@SerializedName("error")
|
||||
val error: String?,
|
||||
@SerializedName("message")
|
||||
val name: String?,
|
||||
) {
|
||||
fun toServiceException(): ServiceException {
|
||||
return ServiceException(
|
||||
message = error ?: name ?: "",
|
||||
code = code,
|
||||
error = error,
|
||||
name = name,
|
||||
errorType = (code ?: 0).toErrorCode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun parseErrorResponse(errorBody: ResponseBody?): ApiErrorResponse? {
|
||||
return errorBody?.let {
|
||||
val gson = Gson()
|
||||
gson.fromJson(it.charStream(), ApiErrorResponse::class.java)
|
||||
}
|
||||
}
|
||||
22
app/src/main/java/com/aiosman/ravenow/data/ListContainer.kt
Normal file
22
app/src/main/java/com/aiosman/ravenow/data/ListContainer.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
/**
|
||||
* 通用列表接口返回
|
||||
*/
|
||||
data class ListContainer<T>(
|
||||
// 总数
|
||||
@SerializedName("total")
|
||||
val total: Int,
|
||||
// 当前页
|
||||
@SerializedName("page")
|
||||
val page: Int,
|
||||
// 每页数量
|
||||
@SerializedName("pageSize")
|
||||
val pageSize: Int,
|
||||
// 列表
|
||||
@SerializedName("list")
|
||||
val list: List<T>
|
||||
)
|
||||
169
app/src/main/java/com/aiosman/ravenow/data/MomentService.kt
Normal file
169
app/src/main/java/com/aiosman/ravenow/data/MomentService.kt
Normal file
@@ -0,0 +1,169 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentImageEntity
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.io.File
|
||||
|
||||
data class Moment(
|
||||
@SerializedName("id")
|
||||
val id: Long,
|
||||
@SerializedName("textContent")
|
||||
val textContent: String,
|
||||
@SerializedName("images")
|
||||
val images: List<Image>,
|
||||
@SerializedName("user")
|
||||
val user: User,
|
||||
@SerializedName("likeCount")
|
||||
val likeCount: Long,
|
||||
@SerializedName("isLiked")
|
||||
val isLiked: Boolean,
|
||||
@SerializedName("favoriteCount")
|
||||
val favoriteCount: Long,
|
||||
@SerializedName("isFavorite")
|
||||
val isFavorite: Boolean,
|
||||
@SerializedName("shareCount")
|
||||
val isCommented: Boolean,
|
||||
@SerializedName("commentCount")
|
||||
val commentCount: Long,
|
||||
@SerializedName("time")
|
||||
val time: String,
|
||||
@SerializedName("isFollowed")
|
||||
val isFollowed: Boolean,
|
||||
) {
|
||||
fun toMomentItem(): MomentEntity {
|
||||
return MomentEntity(
|
||||
id = id.toInt(),
|
||||
avatar = "${ApiClient.BASE_SERVER}${user.avatar}",
|
||||
nickname = user.nickName,
|
||||
location = "Worldwide",
|
||||
time = ApiClient.dateFromApiString(time),
|
||||
followStatus = isFollowed,
|
||||
momentTextContent = textContent,
|
||||
momentPicture = R.drawable.default_moment_img,
|
||||
likeCount = likeCount.toInt(),
|
||||
commentCount = commentCount.toInt(),
|
||||
shareCount = 0,
|
||||
favoriteCount = favoriteCount.toInt(),
|
||||
images = images.map {
|
||||
MomentImageEntity(
|
||||
url = "${ApiClient.BASE_SERVER}${it.url}",
|
||||
thumbnail = "${ApiClient.BASE_SERVER}${it.thumbnail}",
|
||||
id = it.id,
|
||||
blurHash = it.blurHash,
|
||||
width = it.width,
|
||||
height = it.height
|
||||
)
|
||||
},
|
||||
authorId = user.id.toInt(),
|
||||
liked = isLiked,
|
||||
isFavorite = isFavorite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Image(
|
||||
@SerializedName("id")
|
||||
val id: Long,
|
||||
@SerializedName("url")
|
||||
val url: String,
|
||||
@SerializedName("thumbnail")
|
||||
val thumbnail: String,
|
||||
@SerializedName("blurHash")
|
||||
val blurHash: String?,
|
||||
@SerializedName("width")
|
||||
val width: Int?,
|
||||
@SerializedName("height")
|
||||
val height: Int?
|
||||
)
|
||||
|
||||
data class User(
|
||||
@SerializedName("id")
|
||||
val id: Long,
|
||||
@SerializedName("nickName")
|
||||
val nickName: String,
|
||||
@SerializedName("avatar")
|
||||
val avatar: String
|
||||
)
|
||||
|
||||
data class UploadImage(
|
||||
val file: File,
|
||||
val filename: String,
|
||||
val url: String,
|
||||
val ext: String
|
||||
)
|
||||
|
||||
interface MomentService {
|
||||
/**
|
||||
* 获取动态详情
|
||||
* @param id 动态ID
|
||||
*/
|
||||
suspend fun getMomentById(id: Int): MomentEntity
|
||||
|
||||
/**
|
||||
* 点赞动态
|
||||
* @param id 动态ID
|
||||
*/
|
||||
suspend fun likeMoment(id: Int)
|
||||
|
||||
/**
|
||||
* 取消点赞动态
|
||||
* @param id 动态ID
|
||||
*/
|
||||
suspend fun dislikeMoment(id: Int)
|
||||
|
||||
/**
|
||||
* 获取动态列表
|
||||
* @param pageNumber 页码
|
||||
* @param author 作者ID,过滤条件
|
||||
* @param timelineId 用户时间线ID,指定用户 ID 的时间线
|
||||
* @param contentSearch 内容搜索,过滤条件
|
||||
* @param trend 是否趋势动态
|
||||
* @param explore 是否探索动态
|
||||
* @return 动态列表
|
||||
*/
|
||||
suspend fun getMoments(
|
||||
pageNumber: Int,
|
||||
author: Int? = null,
|
||||
timelineId: Int? = null,
|
||||
contentSearch: String? = null,
|
||||
trend: Boolean? = false,
|
||||
explore: Boolean? = false,
|
||||
favoriteUserId: Int? = null
|
||||
): ListContainer<MomentEntity>
|
||||
|
||||
/**
|
||||
* 创建动态
|
||||
* @param content 动态内容
|
||||
* @param authorId 作者ID
|
||||
* @param images 图片列表
|
||||
* @param relPostId 关联动态ID
|
||||
*/
|
||||
suspend fun createMoment(
|
||||
content: String,
|
||||
authorId: Int,
|
||||
images: List<UploadImage>,
|
||||
relPostId: Int? = null
|
||||
): MomentEntity
|
||||
|
||||
/**
|
||||
* 收藏动态
|
||||
* @param id 动态ID
|
||||
*/
|
||||
suspend fun favoriteMoment(id: Int)
|
||||
|
||||
/**
|
||||
* 取消收藏动态
|
||||
* @param id 动态ID
|
||||
*/
|
||||
suspend fun unfavoriteMoment(id: Int)
|
||||
|
||||
/**
|
||||
* 删除动态
|
||||
*/
|
||||
suspend fun deleteMoment(id: Int)
|
||||
}
|
||||
|
||||
|
||||
100
app/src/main/java/com/aiosman/ravenow/data/UserService.kt
Normal file
100
app/src/main/java/com/aiosman/ravenow/data/UserService.kt
Normal file
@@ -0,0 +1,100 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
|
||||
data class UserAuth(
|
||||
val id: Int,
|
||||
val token: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* 用户相关 Service
|
||||
*/
|
||||
interface UserService {
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param id 用户ID
|
||||
* @return 用户信息
|
||||
*/
|
||||
suspend fun getUserProfile(id: String): AccountProfileEntity
|
||||
|
||||
/**
|
||||
* 关注用户
|
||||
* @param id 用户ID
|
||||
*/
|
||||
suspend fun followUser(id: String)
|
||||
|
||||
/**
|
||||
* 取消关注用户
|
||||
* @param id 用户ID
|
||||
*/
|
||||
suspend fun unFollowUser(id: String)
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param pageSize 分页大小
|
||||
* @param page 页码
|
||||
* @param nickname 昵称搜索
|
||||
* @param followerId 粉丝ID,账号粉丝
|
||||
* @param followingId 关注ID,账号关注
|
||||
* @return 用户列表
|
||||
*/
|
||||
suspend fun getUsers(
|
||||
pageSize: Int = 20,
|
||||
page: Int = 1,
|
||||
nickname: String? = null,
|
||||
followerId: Int? = null,
|
||||
followingId: Int? = null
|
||||
): ListContainer<AccountProfileEntity>
|
||||
|
||||
suspend fun getUserProfileByTrtcUserId(id: String):AccountProfileEntity
|
||||
|
||||
}
|
||||
|
||||
class UserServiceImpl : UserService {
|
||||
override suspend fun getUserProfile(id: String): AccountProfileEntity {
|
||||
val resp = ApiClient.api.getAccountProfileById(id.toInt())
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get account")
|
||||
return body.data.toAccountProfileEntity()
|
||||
}
|
||||
|
||||
override suspend fun followUser(id: String) {
|
||||
val resp = ApiClient.api.followUser(id.toInt())
|
||||
return
|
||||
}
|
||||
|
||||
override suspend fun unFollowUser(id: String) {
|
||||
val resp = ApiClient.api.unfollowUser(id.toInt())
|
||||
return
|
||||
}
|
||||
|
||||
override suspend fun getUsers(
|
||||
pageSize: Int,
|
||||
page: Int,
|
||||
nickname: String?,
|
||||
followerId: Int?,
|
||||
followingId: Int?
|
||||
): ListContainer<AccountProfileEntity> {
|
||||
val resp = ApiClient.api.getUsers(
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
search = nickname,
|
||||
followerId = followerId,
|
||||
followingId = followingId
|
||||
)
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get account")
|
||||
return ListContainer<AccountProfileEntity>(
|
||||
list = body.list.map { it.toAccountProfileEntity() },
|
||||
page = body.page,
|
||||
total = body.total,
|
||||
pageSize = body.pageSize,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getUserProfileByTrtcUserId(id: String): AccountProfileEntity {
|
||||
val resp = ApiClient.api.getAccountProfileByTrtcUserId(id)
|
||||
val body = resp.body() ?: throw ServiceException("Failed to get account")
|
||||
return body.data.toAccountProfileEntity()
|
||||
}
|
||||
}
|
||||
140
app/src/main/java/com/aiosman/ravenow/data/api/ApiClient.kt
Normal file
140
app/src/main/java/com/aiosman/ravenow/data/api/ApiClient.kt
Normal file
@@ -0,0 +1,140 @@
|
||||
package com.aiosman.ravenow.data.api
|
||||
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.TimeZone
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.ConstVars
|
||||
import com.auth0.android.jwt.JWT
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
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
|
||||
|
||||
fun getUnsafeOkHttpClient(
|
||||
authInterceptor: AuthInterceptor? = null
|
||||
): OkHttpClient {
|
||||
return try {
|
||||
// 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
|
||||
) {
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(
|
||||
chain: Array<java.security.cert.X509Certificate>,
|
||||
authType: String
|
||||
) {
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf()
|
||||
})
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
val sslContext = SSLContext.getInstance("SSL")
|
||||
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
|
||||
// Create an ssl socket factory with our all-trusting manager
|
||||
val sslSocketFactory = sslContext.socketFactory
|
||||
|
||||
OkHttpClient.Builder()
|
||||
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
|
||||
.hostnameVerifier { _, _ -> true }
|
||||
.apply {
|
||||
authInterceptor?.let {
|
||||
addInterceptor(it)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
class AuthInterceptor() : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val requestBuilder = chain.request().newBuilder()
|
||||
val token = AppStore.token
|
||||
token?.let {
|
||||
val jwt = JWT(token)
|
||||
val expiresAt = jwt.expiresAt?.time?.minus(3000)
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val isExpired = expiresAt != null && currentTime > expiresAt
|
||||
if (isExpired) {
|
||||
runBlocking {
|
||||
val newToken = refreshToken()
|
||||
if (newToken != null) {
|
||||
AppStore.token = newToken
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestBuilder.addHeader("Authorization", "Bearer ${AppStore.token}")
|
||||
|
||||
val response = chain.proceed(requestBuilder.build())
|
||||
return response
|
||||
}
|
||||
|
||||
private suspend fun refreshToken(): String? {
|
||||
val client = Retrofit.Builder()
|
||||
.baseUrl(ApiClient.RETROFIT_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(getUnsafeOkHttpClient())
|
||||
.build()
|
||||
.create(RiderProAPI::class.java)
|
||||
|
||||
val resp = client.refreshToken(AppStore.token ?: "")
|
||||
val newToken = resp.body()?.token
|
||||
if (newToken != null) {
|
||||
AppStore.token = newToken
|
||||
}
|
||||
return newToken
|
||||
}
|
||||
}
|
||||
|
||||
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(authInterceptor = AuthInterceptor())
|
||||
}
|
||||
private val retrofit: Retrofit by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl(RETROFIT_URL)
|
||||
.client(okHttpClient)
|
||||
.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 = TIME_FORMAT
|
||||
val simpleDateFormat = SimpleDateFormat(timeFormat, Locale.getDefault())
|
||||
simpleDateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||
val date = simpleDateFormat.parse(apiString)
|
||||
|
||||
simpleDateFormat.timeZone = TimeZone.getDefault()
|
||||
val localDateString = simpleDateFormat.format(date)
|
||||
return simpleDateFormat.parse(localDateString)
|
||||
}
|
||||
}
|
||||
42
app/src/main/java/com/aiosman/ravenow/data/api/Error.kt
Normal file
42
app/src/main/java/com/aiosman/ravenow/data/api/Error.kt
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.aiosman.ravenow.data.api
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import com.aiosman.ravenow.R
|
||||
|
||||
//
|
||||
enum class ErrorCode(val code: Int) {
|
||||
USER_EXIST(40001),
|
||||
USER_NOT_EXIST(40002),
|
||||
InvalidateCaptcha(40004),
|
||||
IncorrectOldPassword(40005),
|
||||
// 未知错误
|
||||
UNKNOWN(99999)
|
||||
}
|
||||
|
||||
fun ErrorCode.toErrorMessage(context: Context): String {
|
||||
return context.getErrorMessageCode(code)
|
||||
}
|
||||
|
||||
fun ErrorCode.showToast(context: Context) {
|
||||
Toast.makeText(context, toErrorMessage(context), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
// code to ErrorCode
|
||||
fun Int.toErrorCode(): ErrorCode {
|
||||
return when (this) {
|
||||
40001 -> ErrorCode.USER_EXIST
|
||||
40002 -> ErrorCode.USER_NOT_EXIST
|
||||
40004 -> ErrorCode.InvalidateCaptcha
|
||||
40005 -> ErrorCode.IncorrectOldPassword
|
||||
else -> ErrorCode.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getErrorMessageCode(code: Int?): String {
|
||||
return when (code) {
|
||||
40001 -> getString(R.string.error_10001_user_exist)
|
||||
ErrorCode.IncorrectOldPassword.code -> getString(R.string.error_incorrect_old_password)
|
||||
else -> getString(R.string.error_unknown)
|
||||
}
|
||||
}
|
||||
428
app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt
Normal file
428
app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt
Normal file
@@ -0,0 +1,428 @@
|
||||
package com.aiosman.ravenow.data.api
|
||||
|
||||
import com.aiosman.ravenow.data.AccountFavourite
|
||||
import com.aiosman.ravenow.data.AccountFollow
|
||||
import com.aiosman.ravenow.data.AccountLike
|
||||
import com.aiosman.ravenow.data.AccountNotice
|
||||
import com.aiosman.ravenow.data.AccountProfile
|
||||
import com.aiosman.ravenow.data.Comment
|
||||
import com.aiosman.ravenow.data.DataContainer
|
||||
import com.aiosman.ravenow.data.ListContainer
|
||||
import com.aiosman.ravenow.data.Moment
|
||||
import com.aiosman.ravenow.entity.ChatNotification
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.PATCH
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
data class RegisterRequestBody(
|
||||
@SerializedName("username")
|
||||
val username: String,
|
||||
@SerializedName("password")
|
||||
val password: String
|
||||
)
|
||||
|
||||
data class LoginUserRequestBody(
|
||||
@SerializedName("username")
|
||||
val username: String? = null,
|
||||
@SerializedName("password")
|
||||
val password: String? = null,
|
||||
@SerializedName("googleId")
|
||||
val googleId: String? = null,
|
||||
@SerializedName("captcha")
|
||||
val captcha: CaptchaInfo? = null,
|
||||
)
|
||||
|
||||
data class GoogleRegisterRequestBody(
|
||||
@SerializedName("idToken")
|
||||
val idToken: String
|
||||
)
|
||||
|
||||
data class AuthResult(
|
||||
@SerializedName("code")
|
||||
val code: Int,
|
||||
@SerializedName("expire")
|
||||
val expire: String,
|
||||
@SerializedName("token")
|
||||
val token: String
|
||||
)
|
||||
|
||||
data class ValidateTokenResult(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
data class CommentRequestBody(
|
||||
@SerializedName("content")
|
||||
val content: String,
|
||||
@SerializedName("parentCommentId")
|
||||
val parentCommentId: Int? = null,
|
||||
@SerializedName("replyUserId")
|
||||
val replyUserId: Int? = null,
|
||||
@SerializedName("replyCommentId")
|
||||
val replyCommentId: Int? = null,
|
||||
)
|
||||
|
||||
data class ChangePasswordRequestBody(
|
||||
@SerializedName("currentPassword")
|
||||
val oldPassword: String = "",
|
||||
@SerializedName("newPassword")
|
||||
val newPassword: String = ""
|
||||
)
|
||||
|
||||
data class UpdateNoticeRequestBody(
|
||||
@SerializedName("lastLookLikeTime")
|
||||
val lastLookLikeTime: String? = null,
|
||||
@SerializedName("lastLookFollowTime")
|
||||
val lastLookFollowTime: String? = null,
|
||||
@SerializedName("lastLookFavoriteTime")
|
||||
val lastLookFavouriteTime: String? = null
|
||||
)
|
||||
|
||||
data class RegisterMessageChannelRequestBody(
|
||||
@SerializedName("client")
|
||||
val client: String,
|
||||
@SerializedName("identifier")
|
||||
val identifier: String,
|
||||
)
|
||||
|
||||
data class UnRegisterMessageChannelRequestBody(
|
||||
@SerializedName("client")
|
||||
val client: String,
|
||||
@SerializedName("identifier")
|
||||
val identifier: String,
|
||||
)
|
||||
data class ResetPasswordRequestBody(
|
||||
@SerializedName("username")
|
||||
val username: String,
|
||||
)
|
||||
|
||||
data class UpdateUserLangRequestBody(
|
||||
@SerializedName("language")
|
||||
val lang: String,
|
||||
@SerializedName("timeOffset")
|
||||
val timeOffset: Int,
|
||||
@SerializedName("timezone")
|
||||
val timezone: String,
|
||||
)
|
||||
|
||||
data class TrtcSignResponseBody(
|
||||
@SerializedName("sig")
|
||||
val sig: String,
|
||||
@SerializedName("userId")
|
||||
val userId: String,
|
||||
)
|
||||
|
||||
data class AppConfig(
|
||||
@SerializedName("trtcAppId")
|
||||
val trtcAppId: Int,
|
||||
)
|
||||
|
||||
data class DictItem(
|
||||
@SerializedName("key")
|
||||
val key: String,
|
||||
@SerializedName("value")
|
||||
val value: String,
|
||||
@SerializedName("desc")
|
||||
val desc: String,
|
||||
)
|
||||
|
||||
data class CaptchaRequestBody(
|
||||
@SerializedName("source")
|
||||
val source: String,
|
||||
)
|
||||
|
||||
data class CaptchaResponseBody(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("thumb_base64")
|
||||
val thumbBase64: String,
|
||||
@SerializedName("master_base64")
|
||||
val masterBase64: String,
|
||||
@SerializedName("count")
|
||||
val count: Int,
|
||||
)
|
||||
|
||||
data class CheckLoginCaptchaRequestBody(
|
||||
@SerializedName("username")
|
||||
val username: String,
|
||||
)
|
||||
|
||||
data class GenerateLoginCaptchaRequestBody(
|
||||
@SerializedName("username")
|
||||
val username: String,
|
||||
)
|
||||
|
||||
data class DotPosition(
|
||||
@SerializedName("index")
|
||||
val index: Int,
|
||||
@SerializedName("x")
|
||||
val x: Int,
|
||||
@SerializedName("y")
|
||||
val y: Int,
|
||||
)
|
||||
|
||||
data class CaptchaInfo(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("dot")
|
||||
val dot: List<DotPosition>
|
||||
)
|
||||
|
||||
|
||||
data class UpdateChatNotificationRequestBody(
|
||||
@SerializedName("targetUserId")
|
||||
val targetUserId: Int,
|
||||
@SerializedName("strategy")
|
||||
val strategy: String,
|
||||
)
|
||||
|
||||
interface RiderProAPI {
|
||||
@POST("register")
|
||||
suspend fun register(@Body body: RegisterRequestBody): Response<Unit>
|
||||
|
||||
@POST("login")
|
||||
suspend fun login(@Body body: LoginUserRequestBody): Response<AuthResult>
|
||||
|
||||
@GET("auth/token")
|
||||
suspend fun checkToken(): Response<ValidateTokenResult>
|
||||
|
||||
@GET("auth/refresh_token")
|
||||
suspend fun refreshToken(
|
||||
@Query("token") token: String
|
||||
): Response<AuthResult>
|
||||
|
||||
@GET("posts")
|
||||
suspend fun getPosts(
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("timelineId") timelineId: Int? = null,
|
||||
@Query("authorId") authorId: Int? = null,
|
||||
@Query("contentSearch") contentSearch: String? = null,
|
||||
@Query("postUser") postUser: Int? = null,
|
||||
@Query("trend") trend: String? = null,
|
||||
@Query("favouriteUserId") favouriteUserId: Int? = null,
|
||||
@Query("explore") explore: String? = null,
|
||||
): Response<ListContainer<Moment>>
|
||||
|
||||
@Multipart
|
||||
@POST("posts")
|
||||
suspend fun createPost(
|
||||
@Part image: List<MultipartBody.Part>,
|
||||
@Part("textContent") textContent: RequestBody,
|
||||
): Response<DataContainer<Moment>>
|
||||
|
||||
@GET("post/{id}")
|
||||
suspend fun getPost(
|
||||
@Path("id") id: Int
|
||||
): Response<DataContainer<Moment>>
|
||||
|
||||
@POST("post/{id}/like")
|
||||
suspend fun likePost(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@POST("post/{id}/dislike")
|
||||
suspend fun dislikePost(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@POST("post/{id}/favorite")
|
||||
suspend fun favoritePost(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@POST("post/{id}/unfavorite")
|
||||
suspend fun unfavoritePost(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@POST("post/{id}/comment")
|
||||
suspend fun createComment(
|
||||
@Path("id") id: Int,
|
||||
@Body body: CommentRequestBody
|
||||
): Response<DataContainer<Comment>>
|
||||
|
||||
@POST("comment/{id}/like")
|
||||
suspend fun likeComment(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@POST("comment/{id}/dislike")
|
||||
suspend fun dislikeComment(
|
||||
@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,
|
||||
@Query("order") order: String? = null,
|
||||
@Query("parentCommentId") parentCommentId: Int? = null,
|
||||
): Response<ListContainer<Comment>>
|
||||
|
||||
@GET("account/my")
|
||||
suspend fun getMyAccount(): Response<DataContainer<AccountProfile>>
|
||||
|
||||
@Multipart
|
||||
@PATCH("account/my/profile")
|
||||
suspend fun updateProfile(
|
||||
@Part avatar: MultipartBody.Part?,
|
||||
@Part banner: MultipartBody.Part?,
|
||||
@Part("nickname") nickname: RequestBody?,
|
||||
@Part("bio") bio: RequestBody?,
|
||||
): Response<Unit>
|
||||
|
||||
@POST("account/my/password")
|
||||
suspend fun changePassword(
|
||||
@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>
|
||||
|
||||
@POST("account/my/messaging")
|
||||
suspend fun registerMessageChannel(
|
||||
@Body body: RegisterMessageChannelRequestBody
|
||||
): Response<Unit>
|
||||
|
||||
@POST("account/my/messaging/unregister")
|
||||
suspend fun unRegisterMessageChannel(
|
||||
@Body body: UnRegisterMessageChannelRequestBody
|
||||
): Response<Unit>
|
||||
|
||||
@GET("profile/{id}")
|
||||
suspend fun getAccountProfileById(
|
||||
@Path("id") id: Int
|
||||
): Response<DataContainer<AccountProfile>>
|
||||
|
||||
@GET("profile/trtc/{id}")
|
||||
suspend fun getAccountProfileByTrtcUserId(
|
||||
@Path("id") id: String
|
||||
): Response<DataContainer<AccountProfile>>
|
||||
|
||||
@POST("user/{id}/follow")
|
||||
suspend fun followUser(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@POST("user/{id}/unfollow")
|
||||
suspend fun unfollowUser(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@GET("users")
|
||||
suspend fun getUsers(
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("nickname") search: String? = null,
|
||||
@Query("followerId") followerId: Int? = null,
|
||||
@Query("followingId") followingId: Int? = null,
|
||||
): Response<ListContainer<AccountProfile>>
|
||||
|
||||
@POST("register/google")
|
||||
suspend fun registerWithGoogle(@Body body: GoogleRegisterRequestBody): Response<AuthResult>
|
||||
|
||||
@DELETE("post/{id}")
|
||||
suspend fun deletePost(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@DELETE("comment/{id}")
|
||||
suspend fun deleteComment(
|
||||
@Path("id") id: Int
|
||||
): Response<Unit>
|
||||
|
||||
@POST("account/my/password/reset")
|
||||
suspend fun resetPassword(
|
||||
@Body body: ResetPasswordRequestBody
|
||||
): Response<Unit>
|
||||
|
||||
@GET("comment/{id}")
|
||||
suspend fun getComment(
|
||||
@Path("id") id: Int
|
||||
): Response<DataContainer<Comment>>
|
||||
|
||||
@PATCH("account/my/extra")
|
||||
suspend fun updateUserExtra(
|
||||
@Body body: UpdateUserLangRequestBody
|
||||
): Response<Unit>
|
||||
|
||||
@GET("account/my/chat/sign")
|
||||
suspend fun getChatSign(): Response<DataContainer<TrtcSignResponseBody>>
|
||||
|
||||
@GET("app/info")
|
||||
suspend fun getAppConfig(): Response<DataContainer<AppConfig>>
|
||||
|
||||
@GET("dict")
|
||||
suspend fun getDict(
|
||||
@Query("key") key: String
|
||||
): Response<DataContainer<DictItem>>
|
||||
|
||||
@POST("captcha/generate")
|
||||
suspend fun generateCaptcha(
|
||||
@Body body: CaptchaRequestBody
|
||||
): Response<DataContainer<CaptchaResponseBody>>
|
||||
|
||||
@POST("login/needCaptcha")
|
||||
suspend fun checkLoginCaptcha(
|
||||
@Body body: CheckLoginCaptchaRequestBody
|
||||
): Response<DataContainer<Boolean>>
|
||||
|
||||
@POST("captcha/login/generate")
|
||||
suspend fun generateLoginCaptcha(
|
||||
@Body body: GenerateLoginCaptchaRequestBody
|
||||
): Response<DataContainer<CaptchaResponseBody>>
|
||||
|
||||
@GET("chat/notification")
|
||||
suspend fun getChatNotification(
|
||||
@Query("targetTrtcId") targetTrtcId: String
|
||||
): Response<DataContainer<ChatNotification>>
|
||||
|
||||
@POST("chat/notification")
|
||||
suspend fun updateChatNotification(
|
||||
@Body body: UpdateChatNotificationRequestBody
|
||||
): Response<DataContainer<ChatNotification>>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user