接入声优接口

This commit is contained in:
renhaoting 2025-11-06 16:45:09 +08:00
parent 2d967c52a0
commit 063a8bca04
17 changed files with 116 additions and 750 deletions

View File

@ -5,22 +5,17 @@ import com.remax.visualnovel.entity.response.basenew.ResponseNew
import com.remax.visualnovel.entity.request.ParamActorList import com.remax.visualnovel.entity.request.ParamActorList
import com.remax.visualnovel.entity.request.ParamActorTag import com.remax.visualnovel.entity.request.ParamActorTag
import com.remax.visualnovel.entity.response.ActorBean import com.remax.visualnovel.entity.response.ActorBean
import com.remax.visualnovel.entity.response.ChatSound
import com.remax.visualnovel.widget.custom.ActorTag import com.remax.visualnovel.widget.custom.ActorTag
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST import retrofit2.http.POST
interface ActorsService { interface ActorsService {
@POST(BuildConfig.API_BASE + "/tag/selectByCondition") @POST(BuildConfig.API_BASE + "/tag/list")
suspend fun requestActorTags(@Body param: ParamActorTag): ResponseNew<List<ActorTag>> suspend fun requestActorTags(@Body param: ParamActorTag): ResponseNew<List<ActorTag>>
@POST(BuildConfig.API_BASE + "/character/select/list") @POST(BuildConfig.API_BASE + "/character/list")
suspend fun requestActorList(@Body param: ParamActorList): ResponseNew<List<ActorBean>> suspend fun requestActorList(@Body param: ParamActorList): ResponseNew<List<ActorBean>>
@GET(BuildConfig.API_BASE + "/tts/config/select/list")
suspend fun requestSoundList(): ResponseNew<List<ChatSound>>
} }

View File

@ -8,6 +8,7 @@ import com.remax.visualnovel.entity.request.ChatAlbum
import com.remax.visualnovel.entity.request.ChatSetting import com.remax.visualnovel.entity.request.ChatSetting
import com.remax.visualnovel.entity.request.HeartbeatBuy import com.remax.visualnovel.entity.request.HeartbeatBuy
import com.remax.visualnovel.entity.request.ParamBgUpload import com.remax.visualnovel.entity.request.ParamBgUpload
import com.remax.visualnovel.entity.request.ParamSoundList
import com.remax.visualnovel.entity.request.RTCRequest import com.remax.visualnovel.entity.request.RTCRequest
import com.remax.visualnovel.entity.request.SearchPage import com.remax.visualnovel.entity.request.SearchPage
import com.remax.visualnovel.entity.request.SimpleDataDTO import com.remax.visualnovel.entity.request.SimpleDataDTO
@ -24,6 +25,7 @@ import com.remax.visualnovel.entity.response.Pageable
import com.remax.visualnovel.entity.response.Token import com.remax.visualnovel.entity.response.Token
import com.remax.visualnovel.entity.response.VoiceASR import com.remax.visualnovel.entity.response.VoiceASR
import com.remax.visualnovel.entity.response.base.Response import com.remax.visualnovel.entity.response.base.Response
import com.remax.visualnovel.entity.response.basenew.ResponseNew
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST import retrofit2.http.POST
@ -93,7 +95,7 @@ interface ChatService {
* 获取聊天设置 * 获取聊天设置
*/ */
@POST("/web/chat-set/get-my") @POST("/web/chat-set/get-my")
suspend fun getChatSetting(@Body request: ChatSetting): Response<ChatSet> suspend fun getChatSetting(@Body request: ChatSetting): ResponseNew<ChatSet>
/** /**
* 修改聊天设置 * 修改聊天设置
@ -111,7 +113,7 @@ interface ChatService {
* 修改聊天模型 * 修改聊天模型
*/ */
@POST("/web/chat-set/set-chat-model") @POST("/web/chat-set/set-chat-model")
suspend fun setChatModel(@Body request: ChatSetting): Response<Any> suspend fun setChatModel(@Body request: ChatSetting): ResponseNew<Any>
/** /**
* 修改是否自动播放语音 * 修改是否自动播放语音
@ -141,7 +143,7 @@ interface ChatService {
* 语音转文本 * 语音转文本
*/ */
@POST(BuildConfig.API_COW + "/web/voice/asr-v2") @POST(BuildConfig.API_COW + "/web/voice/asr-v2")
suspend fun voiceASR(@Body request: SimpleDataDTO): Response<VoiceASR> suspend fun voiceASR(@Body request: SimpleDataDTO): ResponseNew<VoiceASR>
/** /**
* 生成语音 * 生成语音
@ -165,13 +167,16 @@ interface ChatService {
//------------------------------------------------------ //------------------------------------------------------
@GET(BuildConfig.API_BASE + "/tts/config/select/list") @POST(BuildConfig.API_BASE + "/tts/config/list")
suspend fun loadSoundList(@Query("language") language: Int = 1): Response<List<ChatSound>> suspend fun requestSoundList(@Body param: ParamSoundList): ResponseNew<List<ChatSound>>
@GET(BuildConfig.API_BASE + "/model/config/select/list") @POST(BuildConfig.API_BASE + "/model/config/list")
suspend fun loadAiModelList(@Query("language") language: Int = 1): Response<List<ChatModel>> suspend fun requestAiModelList(@Body language: Int = 1): ResponseNew<List<ChatModel>>
@POST(BuildConfig.API_BASE + "/bg_image/config/upload") @POST(BuildConfig.API_BASE + "/bg_image/config/upload")
suspend fun uploadCustomBgPic(@Body param: ParamBgUpload): Response<Any> suspend fun uploadCustomBgPic(@Body param: ParamBgUpload): ResponseNew<Any>
@POST(BuildConfig.API_BASE + "/bg_image/config/list")
suspend fun requestChatBgList(@Body userId: Int): ResponseNew<List<ChatBackgroundBase.ChatBackground>>
} }

View File

@ -9,6 +9,7 @@ import com.remax.visualnovel.entity.response.ChatBubble
import com.remax.visualnovel.entity.response.ChatModel import com.remax.visualnovel.entity.response.ChatModel
import com.remax.visualnovel.entity.response.Pageable import com.remax.visualnovel.entity.response.Pageable
import com.remax.visualnovel.entity.response.base.Response import com.remax.visualnovel.entity.response.base.Response
import com.remax.visualnovel.entity.response.basenew.ResponseNew
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.POST import retrofit2.http.POST
@ -30,11 +31,11 @@ interface DictService {
* 礼物字典 * 礼物字典
*/ */
@POST("/web/gift/dict-list") @POST("/web/gift/dict-list")
suspend fun getGiftDict(@Body pageQuery: PageQuery = PageQuery(1).apply { page.ps = 100 }): Response<Pageable<Gift>> suspend fun getGiftDict(@Body pageQuery: PageQuery = PageQuery(1).apply { page.ps = 100 }): ResponseNew<Pageable<Gift>>
/** /**
* chat模型 * chat模型
*/ */
@POST("/web/chat-model/dict-list") @POST("/web/chat-model/dict-list")
suspend fun getAIChatModel(): Response<List<ChatModel>> suspend fun getAIChatModel(): ResponseNew<List<ChatModel>>
} }

View File

@ -0,0 +1,7 @@
package com.remax.visualnovel.entity.request
data class ParamSoundList(
var language: Int = 1,
var gender: Int? = null
)

View File

@ -13,11 +13,12 @@ data class ChatSound(
val rules: Int = 0, val rules: Int = 0,
val nameLanguage: String = "", val nameLanguage: String = "",
val headPortrait: String = "", val headPortrait: String = "",
// Other needed
var sampleUrl: String = "",
var isSelected: Boolean = false, var isSelected: Boolean = false,
var isPlaying: Boolean = false, var isPlaying: Boolean = false,
// Other needed
var sampleUrl: String = ""
) : Parcelable { ) : Parcelable {
} }

View File

@ -16,4 +16,5 @@ class ActorsRepository @Inject constructor(private val mActorsService: ActorsSer
mActorsService.requestActorList(param) mActorsService.requestActorList(param)
} }
} }

View File

@ -1,137 +1,50 @@
package com.remax.visualnovel.repository.api package com.remax.visualnovel.repository.api
import com.pengxr.modular.eventbus.generated.events.EventDefineOfChatSettingEvents
import com.pengxr.modular.eventbus.generated.events.EventDefineOfUserAIEvents
import com.pengxr.modular.eventbus.generated.events.EventDefineOfUserEvents
import com.remax.visualnovel.api.service.ChatService import com.remax.visualnovel.api.service.ChatService
import com.remax.visualnovel.entity.request.AIFeedback
import com.remax.visualnovel.entity.request.AIIDRequest
import com.remax.visualnovel.entity.request.AIIsShowDTO
import com.remax.visualnovel.entity.request.ChatAlbum
import com.remax.visualnovel.entity.request.ChatSetting import com.remax.visualnovel.entity.request.ChatSetting
import com.remax.visualnovel.entity.request.HeartbeatBuy import com.remax.visualnovel.entity.request.ParamBgUpload
import com.remax.visualnovel.entity.request.RTCRequest import com.remax.visualnovel.entity.request.ParamSoundList
import com.remax.visualnovel.entity.request.SearchPage
import com.remax.visualnovel.entity.request.SimpleDataDTO import com.remax.visualnovel.entity.request.SimpleDataDTO
import com.remax.visualnovel.entity.request.VoiceTTS import com.remax.visualnovel.repository.api.base.BaseRepositoryNew
import com.remax.visualnovel.entity.response.Character
import com.remax.visualnovel.entity.response.ChatSet
import com.remax.visualnovel.repository.api.base.BaseRepository
import com.remax.visualnovel.ui.chat.message.events.model.ChatSetAutoPlayEvent
import com.remax.visualnovel.ui.chat.message.events.model.ChatSetBackgroundEvent
import com.remax.visualnovel.ui.wallet.manager.WalletManager import com.remax.visualnovel.ui.wallet.manager.WalletManager
import javax.inject.Inject import javax.inject.Inject
/**
* Created by HJW on 2022/11/15
*/
class ChatRepository @Inject constructor(private val chatService: ChatService) : BaseRepository() {
suspend fun sendDialogueMsg(aiId: String) = executeHttp { class ChatRepository @Inject constructor(private val chatService: ChatService) : BaseRepositoryNew() {
chatService.sendDialogueMsg(AIIDRequest(aiId))
}
suspend fun viewAlbumImg(request: ChatAlbum) = executeHttp {
chatService.viewAlbumImg(request)
}
suspend fun getIMAICharacterProfile(aiId: String) = executeHttp {
chatService.getIMAICharacterProfile(Character(aiId = aiId))
}
suspend fun getMyFriends(request: SearchPage) = executeHttp {
chatService.getMyFriends(request)
}
suspend fun getMyFriendRank() = executeHttp {
chatService.getMyFriendRank()
}
suspend fun getPrompts(aiId: String) = executeHttp {
chatService.getPrompts(AIIDRequest(aiId))
}.transformResult({
WalletManager.refreshWallet()
})
suspend fun aiFeedback(request: AIFeedback) = executeHttp {
chatService.aiFeedback(request)
}
suspend fun getRTCToken(roomId: String) = executeHttp {
chatService.getRTCToken(RTCRequest(roomId))
}
suspend fun voiceChatOpt(request: RTCRequest) = executeHttp {
chatService.voiceChatOpt(request)
}
suspend fun getChatBackgroundList(aiId: String) = executeHttp {
chatService.getChatBackgroundList(AIIDRequest(aiId))
}
suspend fun getChatSetting(aiId: String?) = executeHttp {
chatService.getChatSetting(ChatSetting(aiId))
}
suspend fun setChatSetting(request: ChatSet) = executeHttp {
chatService.setChatSetting(request)
}.transformResult({
EventDefineOfUserEvents.onUserInfoChanged().post(null)
})
suspend fun setChatBubble(request: ChatSetting) = executeHttp {
chatService.setChatBubble(request)
}
suspend fun setChatModel(request: ChatSetting) = executeHttp { suspend fun setChatModel(request: ChatSetting) = executeHttp {
chatService.setChatModel(request) chatService.setChatModel(request)
} }
suspend fun setChatAutoPlay(request: ChatSetting) = executeHttp { suspend fun getChatSetting(aiId: String?) = executeHttp {
chatService.setChatAutoPlay(request) chatService.getChatSetting(ChatSetting(aiId))
}.transformResult({
EventDefineOfChatSettingEvents.settingChanged()
.post(ChatSetAutoPlayEvent(request.aiId ?: "", if (request.isAutoPlayVoice == true) 1 else 0))
})
suspend fun setChatBackground(request: ChatSetting) = executeHttp {
chatService.setChatBackground(request)
}.transformResult({
EventDefineOfChatSettingEvents.settingChanged()
.post(ChatSetBackgroundEvent(request.aiId ?: "", request.backgroundImg))
})
suspend fun deleteChatBackground(request: ChatSetting) = executeHttp {
chatService.deleteChatBackground(request)
} }
suspend fun relationSwitch(request: AIIsShowDTO) = executeHttp {
chatService.relationSwitch(request)
}.transformResult({
EventDefineOfUserAIEvents.onAIHeartIsOpenChanged().post(request)
})
suspend fun voiceASR(aiId: String, url: String?) = executeHttp { suspend fun voiceASR(aiId: String, url: String?) = executeHttp {
chatService.voiceASR(SimpleDataDTO(aiId = aiId, url = url)) chatService.voiceASR(SimpleDataDTO(aiId = aiId, url = url))
}.transformResult({ }.transformResult({
WalletManager.refreshWallet() WalletManager.refreshWallet()
}) })
suspend fun voiceTTS(request: VoiceTTS) = executeHttp {
chatService.voiceTTS(request)
// ------------------------ new ---------------------------------
suspend fun getSoundList(param: ParamSoundList) = executeHttp {
chatService.requestSoundList(param)
} }
suspend fun getHeartbeatLevel(aiId: String) = executeHttp { suspend fun getAiModelList(language: Int = 1) = executeHttp {
chatService.getHeartbeatLevel(Character(aiId = aiId)) chatService.requestAiModelList(language)
} }
suspend fun buyHeartbeatVal(request: HeartbeatBuy) = executeHttp { suspend fun uploadCustomBgPic(param: ParamBgUpload) = executeHttp {
chatService.buyHeartbeatVal(request) chatService.uploadCustomBgPic(param)
} }
suspend fun loadSoundList(lang: Int) = executeHttp { suspend fun getChatBgList(userId: Int) = executeHttp {
chatService.loadSoundList(lang) chatService.requestChatBgList(userId)
} }
} }

View File

@ -2,22 +2,13 @@ package com.remax.visualnovel.repository.api
import com.remax.visualnovel.api.service.DictService import com.remax.visualnovel.api.service.DictService
import com.remax.visualnovel.entity.request.AIIDRequest import com.remax.visualnovel.repository.api.base.BaseRepositoryNew
import com.remax.visualnovel.repository.api.base.BaseRepository
import javax.inject.Inject import javax.inject.Inject
/**
* Created by HJW on 2022/11/15
*/
class DictRepository @Inject constructor(private val dictService: DictService) : BaseRepository() {
suspend fun getChatBubbleList(aiId: String) = executeHttp { class DictRepository @Inject constructor(private val dictService: DictService) : BaseRepositoryNew() {
dictService.getChatBubbleList(AIIDRequest(aiId))
}
suspend fun getAIDict() = executeHttp {
dictService.getAIDict()
}
suspend fun getGiftDict() = executeHttp(false) { dictService.getGiftDict() } suspend fun getGiftDict() = executeHttp(false) { dictService.getGiftDict() }

View File

@ -23,10 +23,12 @@ import com.pengxr.modular.eventbus.generated.events.EventDefineOfUserEvents
import com.remax.visualnovel.R import com.remax.visualnovel.R
import com.remax.visualnovel.databinding.ActivityActorChatBinding import com.remax.visualnovel.databinding.ActivityActorChatBinding
import com.remax.visualnovel.entity.request.ChatSetting import com.remax.visualnovel.entity.request.ChatSetting
import com.remax.visualnovel.entity.request.ParamSoundList
import com.remax.visualnovel.event.model.OnLoginEvent import com.remax.visualnovel.event.model.OnLoginEvent
import com.remax.visualnovel.extension.countDownCoroutines import com.remax.visualnovel.extension.countDownCoroutines
import com.remax.visualnovel.extension.launchAndCollect import com.remax.visualnovel.extension.launchAndCollect2
import com.remax.visualnovel.extension.launchAndLoadingCollect import com.remax.visualnovel.extension.launchAndLoadingCollect
import com.remax.visualnovel.extension.launchAndLoadingCollect2
import com.remax.visualnovel.extension.launchWithRequest import com.remax.visualnovel.extension.launchWithRequest
import com.remax.visualnovel.extension.setMargin import com.remax.visualnovel.extension.setMargin
import com.remax.visualnovel.extension.toast import com.remax.visualnovel.extension.toast
@ -71,12 +73,16 @@ class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
} }
override fun initData() { override fun initData() {
loadSoundDatas() loadSoundDatas(null)
} }
private fun loadSoundDatas() { private fun loadSoundDatas(gender: Int?) {
launchAndCollect({ launchAndCollect2({
mViewModel.loadSoundList(LanguageUtil.instance().getCurrentLanguageCode()) val requestParam = ParamSoundList().apply {
this.gender = gender
this.language = LanguageUtil.instance().getCurrentLanguageCode()
}
mViewModel.loadSoundList(requestParam)
}) { }) {
onSuccess = { onSuccess = {
val dataList = it?: emptyList() val dataList = it?: emptyList()
@ -87,7 +93,6 @@ class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
} }
} }
} }
private val loginObserver = Observer<OnLoginEvent?> { private val loginObserver = Observer<OnLoginEvent?> {
@ -227,7 +232,7 @@ class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
modelDialog.build(chatModels ?: emptyList(), chatSet?.modelCode, { modelDialog.build(chatModels ?: emptyList(), chatSet?.modelCode, {
}) { model -> }) { model ->
launchAndLoadingCollect({ launchAndLoadingCollect2({
setChatModel( setChatModel(
ChatSetting(aiId, model.code) ChatSetting(aiId, model.code)
) )
@ -241,7 +246,7 @@ class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
} }
if (chatModels.isNullOrEmpty()) { if (chatModels.isNullOrEmpty()) {
launchAndLoadingCollect({ launchAndLoadingCollect2({
mViewModel.getChatModels() mViewModel.getChatModels()
}) { }) {
onSuccess = { onSuccess = {

View File

@ -1,58 +1,25 @@
package com.remax.visualnovel.ui.chat package com.remax.visualnovel.ui.chat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.remax.visualnovel.app.viewmodel.base.OssViewModel import com.remax.visualnovel.app.viewmodel.base.OssViewModel
import com.remax.visualnovel.entity.imbean.IMAIInMessage
import com.remax.visualnovel.entity.imbean.IMMessageWrapper
import com.remax.visualnovel.entity.imbean.raw.CustomRawData import com.remax.visualnovel.entity.imbean.raw.CustomRawData
import com.remax.visualnovel.entity.imbean.raw.CustomScoreData
import com.remax.visualnovel.entity.request.AIFeedback
import com.remax.visualnovel.entity.request.AlbumDTO
import com.remax.visualnovel.entity.request.ChatAlbum
import com.remax.visualnovel.entity.request.ChatSetting import com.remax.visualnovel.entity.request.ChatSetting
import com.remax.visualnovel.entity.request.Gift
import com.remax.visualnovel.entity.request.S3TypeDTO import com.remax.visualnovel.entity.request.S3TypeDTO
import com.remax.visualnovel.entity.request.SendGift
import com.remax.visualnovel.entity.response.Album
import com.remax.visualnovel.entity.response.BucketBean
import com.remax.visualnovel.entity.response.Character
import com.remax.visualnovel.entity.response.ChatModel import com.remax.visualnovel.entity.response.ChatModel
import com.remax.visualnovel.entity.response.ChatSet import com.remax.visualnovel.entity.response.ChatSet
import com.remax.visualnovel.entity.response.HeartbeatLevelEnum
import com.remax.visualnovel.entity.response.VoiceASR import com.remax.visualnovel.entity.response.VoiceASR
import com.remax.visualnovel.entity.response.base.ApiEmptyResponse import com.remax.visualnovel.entity.response.base.ApiEmptyResponse
import com.remax.visualnovel.entity.response.base.ApiFailedResponse
import com.remax.visualnovel.entity.response.base.Response import com.remax.visualnovel.entity.response.base.Response
import com.remax.visualnovel.extension.convertFromJson
import com.remax.visualnovel.manager.nim.FetchResult
import com.remax.visualnovel.manager.nim.LoadStatus
import com.remax.visualnovel.manager.nim.NimManager
import com.remax.visualnovel.repository.api.AIRepository import com.remax.visualnovel.repository.api.AIRepository
import com.remax.visualnovel.repository.api.ChatRepository import com.remax.visualnovel.repository.api.ChatRepository
import com.remax.visualnovel.repository.api.DictRepository import com.remax.visualnovel.repository.api.DictRepository
import com.remax.visualnovel.repository.api.MessageRepository import com.remax.visualnovel.repository.api.MessageRepository
import com.remax.visualnovel.repository.ext.convertMessage
import com.remax.visualnovel.ui.chat.message.call.manager.RTCManager
import com.google.gson.Gson import com.google.gson.Gson
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum
import com.netease.nimlib.sdk.v2.message.V2NIMClearHistoryNotification
import com.netease.nimlib.sdk.v2.message.V2NIMMessage
import com.netease.nimlib.sdk.v2.message.V2NIMMessageCreator import com.netease.nimlib.sdk.v2.message.V2NIMMessageCreator
import com.netease.nimlib.sdk.v2.message.V2NIMMessageDeletedNotification import com.remax.visualnovel.entity.request.ParamSoundList
import com.netease.nimlib.sdk.v2.message.V2NIMMessageListener import com.remax.visualnovel.entity.response.BucketBean
import com.netease.nimlib.sdk.v2.message.V2NIMMessagePinNotification import com.remax.visualnovel.entity.response.Character
import com.netease.nimlib.sdk.v2.message.V2NIMMessageQuickCommentNotification
import com.netease.nimlib.sdk.v2.message.V2NIMMessageRevokeNotification
import com.netease.nimlib.sdk.v2.message.V2NIMP2PMessageReadReceipt
import com.netease.nimlib.sdk.v2.message.V2NIMTeamMessageReadReceipt
import com.netease.nimlib.sdk.v2.utils.V2NIMConversationIdUtil
import com.remax.visualnovel.manager.gift.GiftManager
import com.remax.visualnovel.ui.chat.message.detail.flirting.FlirtingLevelActivity
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -64,34 +31,11 @@ class ChatViewModel @Inject constructor(
private val aiRepository: AIRepository, private val aiRepository: AIRepository,
) : OssViewModel() { ) : OssViewModel() {
var aiId: String = "" private var bucketBean: BucketBean? = null
var character: Character? = null var character: Character? = null
private set private set
var aiId: String = ""
val conversationId: String
get() = V2NIMConversationIdUtil.conversationId(character?.nimAccountId, SessionTypeEnum.P2P) ?: ""
private val _aiBaseInfoFlow = MutableLiveData<Pair<Boolean, Character?>>()
val aiBaseInfoFlow = _aiBaseInfoFlow
fun checkHeartbeatLevel(targetLevel: HeartbeatLevelEnum, checkSuccess: () -> Unit) {
val currLevel = character?.aiUserHeartbeatRelation?.currHeartbeatEnum
if ((currLevel?.level ?: 0) >= targetLevel.level) {
checkSuccess()
} else {
FlirtingLevelActivity.start(aiId)
}
}
suspend fun refreshAIBaseInfo(enterPage: Boolean) = chatRepository.getIMAICharacterProfile(aiId).transformResult({
character = it
NimManager.getUserList(listOf(character?.nimAccountId))
_aiBaseInfoFlow.value = Pair(enterPage, it)
getChatSetting()
getChatModels()
})
var chatModels: List<ChatModel>? = null var chatModels: List<ChatModel>? = null
private set private set
@ -111,9 +55,20 @@ class ChatViewModel @Inject constructor(
}) })
/**
* 云信发送消息raw代表是自定义消息
*/
suspend fun voiceASR(filePath: String): Response<VoiceASR> {
/*ossUploadFile(filePath, S3TypeDTO.SOUND, isImg = false, token = bucketBean).transformResult({
return chatRepository.voiceASR(aiId, it?.urlPath)
}) {
return Response.createZipFailResponse(it)
}*/
return ApiEmptyResponse()
}
fun sendMsg(msgContent: String, raw: CustomRawData? = null, errorCallback: (Int) -> Unit) { fun sendMsg(msgContent: String, raw: CustomRawData? = null, errorCallback: (Int) -> Unit) {
val v2Message = if (raw != null) val v2Message = if (raw != null)
@ -126,238 +81,11 @@ class ChatViewModel @Inject constructor(
} }
} }
// 消息发送LiveData本地发送消息通过该LiveData通知UI
private val _sendScoreLiveData = MutableLiveData<Double?>()
val sendScoreLiveData: LiveData<Double?> = _sendScoreLiveData
/**
* 接受方添加消息接收回调
*/
private val messageListener = object : V2NIMMessageListener {
/**
* 消息接收
*/
override fun onReceiveMessages(messages: List<V2NIMMessage>) {
NimManager.log("消息接收回调 onReceiveMessages")
NimManager.clearUnreadCountByIds(conversationId)
messages.firstOrNull()?.let { message ->
if (message.conversationId == conversationId) {
/**
* message中的分数
* 本地实时加减一下
* AI发送的文本消息和语音通话时都会有serverExtension:{"score":0.1}的计算
*/
message.serverExtension?.convertFromJson<CustomScoreData>()?.let {
_sendScoreLiveData.value = it.score
RTCManager.sendIMScoreMessage(it.score)
}
val messageRecFetchResult = FetchResult<IMMessageWrapper>(LoadStatus.Success)
message.convertMessage()?.let { messageWrapper ->
if (messageWrapper.type == IMMessageWrapper.IN_TEXT_TYPE) {
/**
* 处理自动播放
*/
if (character?.isAutoPlayVoice == 1) {
(messageWrapper as? IMAIInMessage)?.imVoice?.autoPlay = true
}
}
messageRecFetchResult.data = messageWrapper
messageRecFetchResult.type = messageWrapper.fetchType
messageRecFetchResult.typeIndex = -1
_sendMessageLiveData.value = messageRecFetchResult
}
}
}
}
override fun onReceiveP2PMessageReadReceipts(readReceipts: List<V2NIMP2PMessageReadReceipt?>?) {
}
override fun onReceiveTeamMessageReadReceipts(readReceipts: List<V2NIMTeamMessageReadReceipt?>?) {
}
override fun onMessageRevokeNotifications(revokeNotifications: List<V2NIMMessageRevokeNotification?>?) {
}
override fun onMessagePinNotification(pinNotification: V2NIMMessagePinNotification?) {
}
override fun onMessageQuickCommentNotification(quickCommentNotification: V2NIMMessageQuickCommentNotification?) {
}
override fun onMessageDeletedNotifications(messageDeletedNotifications: List<V2NIMMessageDeletedNotification?>?) {
}
override fun onClearHistoryNotifications(clearHistoryNotifications: List<V2NIMClearHistoryNotification?>?) {
}
/**
* 本端发送消息状态回调
*/
override fun onSendMessage(message: V2NIMMessage) {
val sendingState = message.sendingState
NimManager.log("本端发送消息 $message")
NimManager.log("本端发送消息状态 $sendingState")
/**
* 发消息时需要刷新一下调用推荐回复接口
*/
refreshPrompts = true
if (message.conversationId == conversationId) {
postMessageSend(message)
}
}
override fun onReceiveMessagesModified(messages: List<V2NIMMessage>) {
NimManager.log("收到更新的消息 $messages")
messages.firstOrNull()?.let { message ->
NimManager.log("收到更新的消息 conversationId:${message.conversationId}")
if (message.conversationId == conversationId) {
val messageRecFetchResult = FetchResult<IMMessageWrapper>(LoadStatus.Success)
messageRecFetchResult.data = message.convertMessage()
messageRecFetchResult.type = FetchResult.FetchType.Update
messageRecFetchResult.typeIndex = -1
_sendMessageLiveData.value = messageRecFetchResult
}
}
}
}
fun updateMsg(message: V2NIMMessage) {
val messageRecFetchResult = FetchResult<IMMessageWrapper>(LoadStatus.Success)
messageRecFetchResult.data = message.convertMessage()
messageRecFetchResult.type = FetchResult.FetchType.Update
messageRecFetchResult.typeIndex = -1
_sendMessageLiveData.value = messageRecFetchResult
}
fun aiFeedback(request: AIFeedback) {
viewModelScope.launch {
chatRepository.aiFeedback(request)
}
}
// 消息发送LiveData本地发送消息通过该LiveData通知UI
private val _sendMessageLiveData = MutableLiveData<FetchResult<IMMessageWrapper>?>()
val sendMessageLiveData: LiveData<FetchResult<IMMessageWrapper>?> = _sendMessageLiveData
private val sendMessageFetchResult by lazy {
FetchResult<IMMessageWrapper>(LoadStatus.Finish)
}
// 同步发送消息
private fun postMessageSend(message: V2NIMMessage) {
message.convertMessage()?.let { messageWrapper ->
sendMessageFetchResult.loadStatus = LoadStatus.Success
sendMessageFetchResult.type = messageWrapper.fetchType
sendMessageFetchResult.data = messageWrapper
_sendMessageLiveData.value = sendMessageFetchResult
}
}
fun addMessageListener() {
messageRepository.setMessageListener(true, messageListener)
}
private var bucketBean: BucketBean? = null
override fun onStart() {
viewModelScope.launch {
getBucketToken("mp3", S3TypeDTO.SOUND_PATH).transformResult({
bucketBean = it
})
}
}
override fun onDestroy() {
messageRepository.setMessageListener(false, messageListener)
NimManager.clearUnreadCountByIds(conversationId)
GiftManager.initSelect()
}
private var anchorMessage: V2NIMMessage? = null
/**
* 获取历史消息
*/
suspend fun getMessageList(isRefresh: Boolean): FetchResult<List<IMMessageWrapper>> {
if (isRefresh) anchorMessage = null
val res = messageRepository.getMessageList(conversationId, anchorMessage, character)
// if (isRefresh && res.data?.size == 1) {
// sendDialogueMsg()
// }
anchorMessage = res.extraInfo as? V2NIMMessage
return res
}
suspend fun voiceASR(filePath: String): Response<VoiceASR> {
ossUploadFile(filePath, S3TypeDTO.SOUND, isImg = false, token = bucketBean).transformResult({
return chatRepository.voiceASR(aiId, it?.urlPath)
}) {
return Response.createZipFailResponse(it)
}
return ApiEmptyResponse()
}
suspend fun sendGift(request: SendGift, gift: Gift): Response<Any> {
return messageRepository.sendGift(request, gift)
}
var refreshPrompts = true
private set
suspend fun getPrompts(): Response<List<String>> {
return if (refreshPrompts) {
refreshPrompts = false
chatRepository.getPrompts(aiId).transformResult {
// 失败的话需要重新拉
refreshPrompts = true
}
} else {
ApiFailedResponse()
}
}
suspend fun viewAlbumImg(request: ChatAlbum) = chatRepository.viewAlbumImg(request)
suspend fun unlockAlbum(request: ChatAlbum) = aiRepository.unlockAlbum(request)
fun sendDialogueMsg() {
viewModelScope.launch {
chatRepository.sendDialogueMsg(aiId)
}
}
suspend fun changeLiked(): Response<Any> {
val isLiked = character?.liked == true
val request = AlbumDTO(
aiId = aiId,
likedStatus = if (isLiked) Album.LIKED else Album.CANCELED,
liked = isLiked
)
return aiRepository.setAILikeOrCancel(request).transformResult({
changeCharacterLiked(isLiked)
})
}
fun changeCharacterLiked(isLiked: Boolean) {
character?.apply {
liked = isLiked
likedNum = if (isLiked) {
likedNum?.plus(1)
} else {
likedNum?.minus(1)
}
}
}
//------------------------ new ------------------------ //------------------------ new ------------------------
suspend fun loadSoundList(lang: Int) = chatRepository.loadSoundList(lang) suspend fun loadSoundList(param: ParamSoundList) = chatRepository.getSoundList(param)

View File

@ -1,248 +0,0 @@
package com.remax.visualnovel.ui.chat.message.detail.flirting
/**
* Created by HJW on 2025/8/16
*/
import android.annotation.SuppressLint
import android.view.View
import androidx.activity.viewModels
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.airbnb.lottie.LottieAnimationView
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.remax.visualnovel.R
import com.remax.visualnovel.app.base.BaseBindingActivity
import com.remax.visualnovel.app.delegate.isFull
import com.remax.visualnovel.app.delegate.titleTextAlpha
import com.remax.visualnovel.app.widget.tips.TipsSwitchWindow
import com.remax.visualnovel.entity.request.AIIsShowDTO
import com.remax.visualnovel.entity.response.HeartbeatLevel
import com.remax.visualnovel.extension.addScrollerAlpha
import com.remax.visualnovel.extension.getTemperatureTxt
import com.remax.visualnovel.extension.glide.loadAndRoundCorner
import com.remax.visualnovel.extension.launchAndLoadingCollect
import com.remax.visualnovel.extension.setMargin
import com.remax.visualnovel.extension.setOnClick
import com.remax.visualnovel.extension.setSize
import com.remax.visualnovel.extension.setSpanTypeFace
import com.remax.visualnovel.extension.showMoreTxtDialog
import com.remax.visualnovel.extension.translationYObjectAnimator
import com.remax.visualnovel.utils.Routers
import com.remax.visualnovel.utils.spannablex.spannable
import com.remax.visualnovel.utils.spannablex.utils.dp
import com.remax.visualnovel.widget.ui.UserAvatarView
import com.remax.visualnovel.widget.uitoken.changeTextFont
import com.remax.visualnovel.widget.uitoken.handleUIToken
import com.drake.brv.annotaion.DividerOrientation
import com.drake.brv.utils.divider
import com.drake.brv.utils.grid
import com.drake.brv.utils.models
import com.drake.brv.utils.setup
import com.remax.visualnovel.databinding.ActivityFlirtingLevelBinding
import com.remax.visualnovel.databinding.ItemFlirtingLevelBinding
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
@AndroidEntryPoint
@Route(path = Routers.CHAT_FLIRTING_LEVEL)
class FlirtingLevelActivity : BaseBindingActivity<ActivityFlirtingLevelBinding>() {
private val flirtingLevelViewModel by viewModels<FlirtingLevelViewModel>()
@JvmField
@Autowired
var aiId = ""
@SuppressLint("SetTextI18n")
override fun initView() {
ARouter.getInstance().inject(this)
setToolbar(R.string.app_name) {
isFull = true
titleTextAlpha = 1f
setRightIconBtn1(R.string.icon_more) {
val switchWindow = TipsSwitchWindow()
switchWindow.build(
this@FlirtingLevelActivity,
R.string.hide_relations,
flirtingLevelViewModel.levelOutput?.aiUserHeartbeatRelation?.isShow != true
) { switchView, isChecked ->
launchAndLoadingCollect({
flirtingLevelViewModel.relationSwitch(AIIsShowDTO(aiId, if (isChecked) 0 else 1))
}) {
onSuccess = {
with(binding) {
listOf(tvMeet, dividerMeet1, dividerMeet2).forEach { view ->
view.isGone =
flirtingLevelViewModel.levelOutput?.aiUserHeartbeatRelation?.isShow != true
}
}
}
onFailed = { _, _ ->
switchView.isChecked = !isChecked
}
}
}
switchWindow.showAsDropDown(this)
}
setRightIconBtn2(R.string.icon_faq) {
showMoreTxtDialog(
texts = listOf(
R.string.flirting_tips_txt_1,
R.string.flirting_tips_txt_2,
R.string.flirting_tips_txt_3
).map { getString(it) })
}
}
with(binding) {
scrollView.addScrollerAlpha(aiAvatarView, includeBgAlpha = true, textAlpha = false)
tvMeet.text = "· ${getString(R.string.meet)} ·"
setOnClick(retrieveGroup) {
flirtingLevelViewModel.levelOutput?.aiUserHeartbeatRelation?.let {
val retrieveDialog = FlirtingRetrieveDialog(this@FlirtingLevelActivity)
retrieveDialog.build(it) {
launchAndLoadingCollect({
flirtingLevelViewModel.buyHeartbeatVal(aiId)
}) {
onSuccess = {
initData()
retrieveDialog.dismiss()
}
}
}
retrieveDialog.binding.run {
showHeartAnim(levelBg, levelBgTop, lottieView, 84f)
}
retrieveDialog.show()
}
}
rv.grid(2)
.divider {
setDivider(16, true)
orientation = DividerOrientation.VERTICAL
}.setup {
addType<HeartbeatLevel>(R.layout.item_flirting_level)
onBind {
val item = getModel<HeartbeatLevel>()
with(getBinding<ItemFlirtingLevelBinding>()) {
tvName.text = item.name
tvName.isEnabled = item.isUnlock
lockView.isVisible = !item.isUnlock
imageView.loadAndRoundCorner(item.imgUrl, 16)
tvTemperature.text = item.startVal.getTemperatureTxt()
tvTemperature.isEnabled = item.isUnlock
imageStroke.isVisible = item.isUnlock
}
}
}
}
}
@SuppressLint("SetTextI18n", "DefaultLocale")
override fun initData() {
launchAndLoadingCollect({
flirtingLevelViewModel.getHeartbeatLevel(aiId)
}) {
onSuccess = {
val relation = it?.aiUserHeartbeatRelation
with(binding) {
listOf(tvMeet, dividerMeet1, dividerMeet2).forEach { view ->
view.isGone = relation?.isShow != true
}
showHeartAnim(levelBg, levelBgTop, lottieView, avatarView = aiAvatarView)
aiAvatarView.loadAvatar(relation?.aiHeadImg)
myAvatarView.loadAvatar(relation?.userHeadImg)
val currHeartbeat = relation?.currHeartbeatEnum
if (currHeartbeat == null) {
tvMeet.changeTextFont {
textUITextToken = getString(R.string.txt_title_s)
}
tvMeet.setText(R.string.no_intention_yet)
} else {
tvMeet.changeTextFont {
textUITextToken = getString(R.string.txt_display_s)
}
tvMeet.setText(currHeartbeat.tagName)
}
val heartbeatScore = String.format("%.2f", (relation?.heartbeatScore ?: 0.0f) * 100)
tvMeetDesc.text =
getString(R.string.flirting_desc, relation?.dayCount ?: 0, heartbeatScore)
tvLevel.text = spannable {
if (currHeartbeat != null) {
currHeartbeat.levelContent.text()
"".color(handleUIToken(R.string.color_outline_normal)?.color ?: 0)
}
(relation?.heartbeatVal ?: 0.0).toString().text()
getString(R.string.temperature).span {
setSpanTypeFace(this@FlirtingLevelActivity, R.string.txt_numMonotype_s)
}
}
/**
* 已经扣减的心动分
*/
retrieveGroup.isVisible = (relation?.subtractHeartbeatVal ?: 0.0) != 0.0
retrieveTitle.text =
getString(R.string.flirting_deducted_desc, (relation?.subtractHeartbeatVal ?: 0.0).toString())
rv.models = it?.heartbeatLeveLDictList
}
}
}
}
private fun showHeartAnim(
levelBg: View,
levelBgTop: View,
heartLottie: LottieAnimationView,
bgBottomMargin: Float = 0f,
avatarView: UserAvatarView? = null,
) {
levelBg.post {
val currProgress =
flirtingLevelViewModel.levelOutput?.aiUserHeartbeatRelation?.currHeartbeatEnum?.level ?: 0
val progress = currProgress / 10f
// 弹窗中的切图需要往上移动
levelBg.setMargin(bottomMargin = -((bgBottomMargin / 400f) * levelBgTop.measuredHeight).toInt())
// 根据切图比例调整宽高
val heartSize = ((98f / 214f) * levelBgTop.measuredHeight).toInt()
heartLottie.setSize(heartSize, heartSize)
// 头像框距离顶部的边距
val marginTop = ((120f / 214f) * levelBgTop.measuredHeight) + (heartSize - 64.dp) / 2f
avatarView?.setMargin(topMargin = marginTop.toInt())
avatarView?.isVisible = true
binding.myAvatarView.isVisible = true
// 动画持续时间
val totalTime = 1000L
// 总高度进度算
val totalHeight = progress * heartSize
Timber.i("showHeartAnim heartSize:$heartSize totalHeight:$totalHeight")
heartLottie.translationYObjectAnimator(-totalHeight, totalTime)
}
}
companion object {
fun start(aiId: String?) {
if (aiId != null) {
ARouter.getInstance()
.build(Routers.CHAT_FLIRTING_LEVEL)
.withString("aiId", aiId)
.navigation()
}
}
}
}

View File

@ -17,7 +17,7 @@ class FlirtingLevelViewModel @Inject constructor(private val chatRepository: Cha
var levelOutput: HeartbeatLevelOutput? = null var levelOutput: HeartbeatLevelOutput? = null
private set private set
suspend fun getHeartbeatLevel(aiId: String) = chatRepository.getHeartbeatLevel(aiId).transformResult({ /*suspend fun getHeartbeatLevel(aiId: String) = chatRepository.getHeartbeatLevel(aiId).transformResult({
levelOutput = it levelOutput = it
}) })
@ -28,5 +28,17 @@ class FlirtingLevelViewModel @Inject constructor(private val chatRepository: Cha
suspend fun buyHeartbeatVal(aiId: String) = chatRepository.buyHeartbeatVal(HeartbeatBuy( suspend fun buyHeartbeatVal(aiId: String) = chatRepository.buyHeartbeatVal(HeartbeatBuy(
aiId, aiId,
levelOutput?.aiUserHeartbeatRelation?.subtractHeartbeatVal levelOutput?.aiUserHeartbeatRelation?.subtractHeartbeatVal
)) ))*/
suspend fun getHeartbeatLevel(aiId: String) {
}
suspend fun relationSwitch(request: AIIsShowDTO) {
}
suspend fun buyHeartbeatVal(aiId: String) {
}
} }

View File

@ -199,47 +199,7 @@ class ChatSettingView @JvmOverloads constructor(
nameLanguage = "名称-5" nameLanguage = "名称-5"
), ),
) )
/*val items = listOf(
ChatSound(
id = 1L,
name = "Sound-1",
description = "This is description for sound-1",
isMale = true,
imgUrl = ""
),
ChatSound(
id = 2L,
name = "Sound-2",
description = "This is description for sound-2",
isMale = false,
imgUrl = ""
),
ChatSound(
id = 3L,
name = "Sound-3",
description = "This is description for sound-3",
isMale = true,
imgUrl = ""
),
ChatSound(
id = 4L,
name = "Sound-4",
description = "This is description for sound-4",
isMale = false,
imgUrl = ""
),
ChatSound(
id = 5L,
name = "Sound-5",
description = "This is description for sound-5",
isMale = true,
imgUrl = ""
)
)
*/
with(mBinding.soundActorSelector) { with(mBinding.soundActorSelector) {
setItems(items) setItems(items)
setEventListener( setEventListener(

View File

@ -8,7 +8,6 @@ import android.widget.LinearLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.drake.brv.annotaion.DividerOrientation import com.drake.brv.annotaion.DividerOrientation
import com.drake.brv.utils.addModels
import com.drake.brv.utils.bindingAdapter import com.drake.brv.utils.bindingAdapter
import com.drake.brv.utils.divider import com.drake.brv.utils.divider
import com.drake.brv.utils.grid import com.drake.brv.utils.grid
@ -59,21 +58,6 @@ class ExpandBackgroundSubView @JvmOverloads constructor(
addType<ChatBackgroundBase.ChatBackgroundUpload>(R.layout.layout_item_setting_background_upload) addType<ChatBackgroundBase.ChatBackgroundUpload>(R.layout.layout_item_setting_background_upload)
onClick(R.id.root) { onClick(R.id.root) {
/*when (val item = getModel<Any>(it)) {
is ChatBackgroundBase.ChatBackground -> {
val chatBackground = getModel<ChatBackgroundBase.ChatBackground>()
if (!chatBackground.isSelected) {
itemsRv.bindingAdapter.models?.filterIsInstance<ChatBackgroundBase.ChatBackground>()?.forEach { item ->
item.isSelected = item == chatBackground
}
itemsRv.bindingAdapter.notifyDataSetChanged()
}
}
is ChatBackgroundBase.ChatBackgroundUpload -> {
selectCustomBg()
}
}*/
when (itemViewType) { when (itemViewType) {
R.layout.layout_item_setting_background -> { R.layout.layout_item_setting_background -> {
val chatBackground = getModel<ChatBackgroundBase.ChatBackground>() val chatBackground = getModel<ChatBackgroundBase.ChatBackground>()
@ -91,6 +75,12 @@ class ExpandBackgroundSubView @JvmOverloads constructor(
} }
} }
onClick(R.id.iv_del) {
// TODO - request delete method
val chatBackground = getModel<ChatBackgroundBase.ChatBackground>()
//itemsRv.bindingAdapter.
}
onBind { onBind {
when (itemViewType) { when (itemViewType) {

View File

@ -15,8 +15,10 @@ import com.remax.visualnovel.configs.NovelApplication
import com.remax.visualnovel.databinding.FragmentMainActorBinding import com.remax.visualnovel.databinding.FragmentMainActorBinding
import com.remax.visualnovel.entity.request.ParamActorList import com.remax.visualnovel.entity.request.ParamActorList
import com.remax.visualnovel.entity.request.ParamActorTag import com.remax.visualnovel.entity.request.ParamActorTag
import com.remax.visualnovel.entity.request.ParamSoundList
import com.remax.visualnovel.entity.response.ActorBean import com.remax.visualnovel.entity.response.ActorBean
import com.remax.visualnovel.extension.launchAndCollect2 import com.remax.visualnovel.extension.launchAndCollect2
import com.remax.visualnovel.utils.LanguageUtil
import com.remax.visualnovel.utils.Routers import com.remax.visualnovel.utils.Routers
import com.remax.visualnovel.utils.StatusBarUtil3 import com.remax.visualnovel.utils.StatusBarUtil3
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -32,7 +34,7 @@ class ActorListFragment : BaseBindingFragment<FragmentMainActorBinding>() {
private val mActorsModel by viewModels<ActorsViewModel>() private val mActorsModel by viewModels<ActorsViewModel>()
private var mLoadedPageIndex = 0 private var mLoadedPageIndex = 0
private val mRequestParam by lazy { ParamActorList() } private val mRequestParam by lazy { ParamActorList() }
private val mRequestTagsParam by lazy { ParamActorTag() }
override fun onCreated(bundle: Bundle?) { override fun onCreated(bundle: Bundle?) {
@ -151,20 +153,22 @@ class ActorListFragment : BaseBindingFragment<FragmentMainActorBinding>() {
private fun getActorTags() { private fun getActorTags() {
mRequestTagsParam.limit = Int.MAX_VALUE val tagsParam = ParamActorTag().apply {
limit = Int.MAX_VALUE
}
launchAndCollect2({ launchAndCollect2({
mActorsModel.getActorTags(mRequestTagsParam) mActorsModel.getActorTags(tagsParam)
}, showLoading = false) { }, showLoading = false) {
onSuccess = { onSuccess = {
val data = it ?: emptyList() val data = it ?: emptyList()
binding.tagFlowLayout.setTagDataList(data) binding.tagFlowLayout.setTagDataList(data)
} }
} }
} }
private fun createSampleData(): List<ActorBean> { private fun createSampleData(): List<ActorBean> {
return listOf( return listOf(
ActorBean(characterName = "testName-1", description = "Des-xxxxxxxxxxxxx"), ActorBean(characterName = "testName-1", description = "Des-xxxxxxxxxxxxx"),

View File

@ -24,4 +24,5 @@ class ActorsViewModel @Inject constructor(private val mActorsRepository: ActorsR
suspend fun getActorTags(param: ParamActorTag) = mActorsRepository.getActorTags(param) suspend fun getActorTags(param: ParamActorTag) = mActorsRepository.getActorTags(param)
} }

View File

@ -42,12 +42,12 @@
<com.remax.visualnovel.widget.uitoken.view.UITokenImageView <com.remax.visualnovel.widget.uitoken.view.UITokenImageView
android:id="@+id/iv_del" android:id="@+id/iv_del"
android:layout_width="@dimen/dp_15" android:layout_width="match_parent"
android:layout_height="@dimen/dp_15" android:layout_height="wrap_content"
android:src="@mipmap/setting_delete" android:src="@mipmap/setting_delete"
app:radiusToken="@string/radius_l" app:radiusToken="@string/radius_l"
android:tint="@color/white" android:tint="@color/white"
android:layout_marginBottom="@dimen/dp_9" android:paddingVertical="@dimen/dp_12"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"