声优列表接口初步
This commit is contained in:
parent
14c9cfa4a7
commit
12b94fae91
|
|
@ -14,4 +14,5 @@
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
/.idea/dictionaries/project.xml
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,9 @@ android {
|
||||||
buildConfigString("RECHAEGE_SERVICES", "https://test.xxxxx.ai/policy/recharge")
|
buildConfigString("RECHAEGE_SERVICES", "https://test.xxxxx.ai/policy/recharge")
|
||||||
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
|
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
|
||||||
|
|
||||||
buildConfigString("API_BASE", "http://54.223.196.180:9090")
|
//buildConfigString("API_BASE", "http://54.223.196.180:9090")
|
||||||
|
buildConfigString("API_BASE", "http://192.168.110.113:9090")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -160,7 +162,8 @@ android {
|
||||||
buildConfigString("RECHAEGE_SERVICES", "https://test.xxxxx.ai/policy/recharge")
|
buildConfigString("RECHAEGE_SERVICES", "https://test.xxxxx.ai/policy/recharge")
|
||||||
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
|
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
|
||||||
|
|
||||||
buildConfigString("API_BASE", "http://54.223.196.180:9090")
|
//buildConfigString("API_BASE", "http://54.223.196.180:9090")
|
||||||
|
buildConfigString("API_BASE", "http://192.168.110.113:9090")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
|
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import com.remax.visualnovel.entity.response.Album
|
||||||
import com.remax.visualnovel.entity.response.Character
|
import com.remax.visualnovel.entity.response.Character
|
||||||
import com.remax.visualnovel.entity.response.ChatBackground
|
import com.remax.visualnovel.entity.response.ChatBackground
|
||||||
import com.remax.visualnovel.entity.response.ChatSet
|
import com.remax.visualnovel.entity.response.ChatSet
|
||||||
|
import com.remax.visualnovel.entity.response.ChatSound
|
||||||
import com.remax.visualnovel.entity.response.Friends
|
import com.remax.visualnovel.entity.response.Friends
|
||||||
import com.remax.visualnovel.entity.response.HeartbeatLevelOutput
|
import com.remax.visualnovel.entity.response.HeartbeatLevelOutput
|
||||||
import com.remax.visualnovel.entity.response.Pageable
|
import com.remax.visualnovel.entity.response.Pageable
|
||||||
|
|
@ -22,7 +23,9 @@ 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 retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
interface ChatService {
|
interface ChatService {
|
||||||
|
|
||||||
|
|
@ -156,4 +159,13 @@ interface ChatService {
|
||||||
@POST("/web/ai-user/buy-heartbeat-val")
|
@POST("/web/ai-user/buy-heartbeat-val")
|
||||||
suspend fun buyHeartbeatVal(@Body request: HeartbeatBuy): Response<Any>
|
suspend fun buyHeartbeatVal(@Body request: HeartbeatBuy): Response<Any>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------
|
||||||
|
@GET(BuildConfig.API_BASE + "/tts/config/select/list")
|
||||||
|
suspend fun loadSoundList(@Query("language") page: Int = 1): Response<List<ChatSound>>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,50 +6,14 @@ import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ChatSound(
|
data class ChatSound(
|
||||||
|
val ttsId: Long,
|
||||||
|
val language: Int,
|
||||||
|
val desLanguage: String,
|
||||||
|
val gender: Int,
|
||||||
|
val rules: Int,
|
||||||
|
val nameLanguage: String,
|
||||||
|
val headPortrait: String,
|
||||||
|
var isSelected: Boolean = false
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
|
||||||
* id
|
|
||||||
*/
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 图片url
|
|
||||||
*/
|
|
||||||
val imgUrl: String?,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 名称
|
|
||||||
*/
|
|
||||||
val name: String,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 名称
|
|
||||||
*/
|
|
||||||
val description: String,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* actor 性别
|
|
||||||
*/
|
|
||||||
val isMale: Boolean,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当前用户是否解锁 false:未解锁,true:解锁
|
|
||||||
*/
|
|
||||||
val isUnlock: Boolean? = null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解锁心动等级 类型为HEARTBEAT_LEVEL时才有用
|
|
||||||
*/
|
|
||||||
val unlockHeartbeatLevel: String? = null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解锁类型 MEMBER:会员 HEARTBEAT_LEVEL:心动等级
|
|
||||||
*/
|
|
||||||
val unlockType: String? = null,
|
|
||||||
var select: Boolean = false
|
|
||||||
) : Parcelable {
|
|
||||||
companion object {
|
|
||||||
const val MEMBER = "MEMBER"
|
|
||||||
const val HEARTBEAT_LEVEL = "HEARTBEAT_LEVEL"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,4 +130,8 @@ class ChatRepository @Inject constructor(private val chatService: ChatService) :
|
||||||
chatService.buyHeartbeatVal(request)
|
chatService.buyHeartbeatVal(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun loadSoundList(lang: Int) = executeHttp {
|
||||||
|
chatService.loadSoundList(lang)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ import com.remax.visualnovel.databinding.ActivityActorChatBinding
|
||||||
import com.remax.visualnovel.entity.request.ChatSetting
|
import com.remax.visualnovel.entity.request.ChatSetting
|
||||||
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.launchAndLoadingCollect
|
import com.remax.visualnovel.extension.launchAndLoadingCollect
|
||||||
import com.remax.visualnovel.extension.launchWithRequest
|
import com.remax.visualnovel.extension.launchWithRequest
|
||||||
import com.remax.visualnovel.extension.setMargin
|
import com.remax.visualnovel.extension.setMargin
|
||||||
|
|
@ -34,6 +35,7 @@ import com.remax.visualnovel.ui.chat.customui.ChatCallView
|
||||||
import com.remax.visualnovel.ui.chat.setting.model.ChatModelDialog
|
import com.remax.visualnovel.ui.chat.setting.model.ChatModelDialog
|
||||||
import com.remax.visualnovel.ui.chat.customui.HoldToTalkDialog
|
import com.remax.visualnovel.ui.chat.customui.HoldToTalkDialog
|
||||||
import com.remax.visualnovel.ui.chat.customui.InputPanel
|
import com.remax.visualnovel.ui.chat.customui.InputPanel
|
||||||
|
import com.remax.visualnovel.utils.LanguageUtil
|
||||||
import com.remax.visualnovel.utils.RecordHelper
|
import com.remax.visualnovel.utils.RecordHelper
|
||||||
import com.remax.visualnovel.utils.StatusBarUtil3
|
import com.remax.visualnovel.utils.StatusBarUtil3
|
||||||
import com.remax.visualnovel.utils.setOnKeyboardHeightChangeListener
|
import com.remax.visualnovel.utils.setOnKeyboardHeightChangeListener
|
||||||
|
|
@ -47,7 +49,7 @@ import kotlin.getValue
|
||||||
@Route(path = Routers.CHAT)
|
@Route(path = Routers.CHAT)
|
||||||
class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
|
class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
|
||||||
private var mMode = MODE_TEXT
|
private var mMode = MODE_TEXT
|
||||||
private val chatViewModel by viewModels<ChatViewModel>()
|
private val mViewModel by viewModels<ChatViewModel>()
|
||||||
private val mRecordAssist = RecordAssist()
|
private val mRecordAssist = RecordAssist()
|
||||||
private val mImeHelper = ImeHelper()
|
private val mImeHelper = ImeHelper()
|
||||||
|
|
||||||
|
|
@ -69,6 +71,22 @@ class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initData() {
|
override fun initData() {
|
||||||
|
loadSoundDatas()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadSoundDatas() {
|
||||||
|
launchAndCollect({
|
||||||
|
mViewModel.loadSoundList(LanguageUtil.instance().getCurrentLanguageCode())
|
||||||
|
}) {
|
||||||
|
onSuccess = {
|
||||||
|
val dataList = it?: emptyList()
|
||||||
|
binding.settingView.setSoundItems(dataList)
|
||||||
|
}
|
||||||
|
|
||||||
|
onComplete = {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +220,7 @@ class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
|
||||||
|
|
||||||
|
|
||||||
private fun showSelectModelDialog() {
|
private fun showSelectModelDialog() {
|
||||||
with(chatViewModel) {
|
with(mViewModel) {
|
||||||
fun createModelDialog() {
|
fun createModelDialog() {
|
||||||
val modelDialog = ChatModelDialog(this@ChatActivity)
|
val modelDialog = ChatModelDialog(this@ChatActivity)
|
||||||
|
|
||||||
|
|
@ -224,7 +242,7 @@ class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
|
||||||
|
|
||||||
if (chatModels.isNullOrEmpty()) {
|
if (chatModels.isNullOrEmpty()) {
|
||||||
launchAndLoadingCollect({
|
launchAndLoadingCollect({
|
||||||
chatViewModel.getChatModels()
|
mViewModel.getChatModels()
|
||||||
}) {
|
}) {
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
createModelDialog()
|
createModelDialog()
|
||||||
|
|
@ -314,11 +332,11 @@ class ChatActivity : BaseBindingActivity<ActivityActorChatBinding>() {
|
||||||
Timber.i("startRecording onStop: ${recordHelper.getFilename()}")
|
Timber.i("startRecording onStop: ${recordHelper.getFilename()}")
|
||||||
|
|
||||||
launchAndLoadingCollect({
|
launchAndLoadingCollect({
|
||||||
chatViewModel.voiceASR(recordHelper.getFilename())
|
mViewModel.voiceASR(recordHelper.getFilename())
|
||||||
}) {
|
}) {
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
if (!it?.content.isNullOrBlank()) {
|
if (!it?.content.isNullOrBlank()) {
|
||||||
chatViewModel.sendMsg(it.content, errorCallback = sendMsgErrorCallback)
|
mViewModel.sendMsg(it.content, errorCallback = sendMsgErrorCallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package com.remax.visualnovel.ui.chat
|
package com.remax.visualnovel.ui.chat
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by HJW on 2025/8/13
|
|
||||||
*/
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
|
@ -354,4 +352,14 @@ class ChatViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------ new ------------------------
|
||||||
|
suspend fun loadSoundList(lang: Int) = chatRepository.loadSoundList(lang)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
|
|
||||||
|
|
||||||
fun initSoundSelectorView() {
|
fun initSoundSelectorView() {
|
||||||
val items = listOf(
|
/*val items = listOf(
|
||||||
ChatSound(
|
ChatSound(
|
||||||
id = 1L,
|
id = 1L,
|
||||||
name = "Sound-1",
|
name = "Sound-1",
|
||||||
|
|
@ -212,9 +212,9 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
imgUrl = ""
|
imgUrl = ""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
*/
|
||||||
with(mBinding.soundActorSelector) {
|
with(mBinding.soundActorSelector) {
|
||||||
setItems(items)
|
//setItems(items)
|
||||||
setEventListener(
|
setEventListener(
|
||||||
object : ExpandSoundSelectView.IEventListener {
|
object : ExpandSoundSelectView.IEventListener {
|
||||||
override fun onItemSelected(
|
override fun onItemSelected(
|
||||||
|
|
@ -228,6 +228,10 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
if (isExpanded)
|
if (isExpanded)
|
||||||
scroll2Position(this@with)
|
scroll2Position(this@with)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onFiltersChanged(sexValue: Int) {
|
||||||
|
//TODO("Not yet implemented")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -357,5 +361,9 @@ class ChatSettingView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setSoundItems(newItems: List<ChatSound>) {
|
||||||
|
mBinding.soundActorSelector.setItems(newItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,11 @@ class ExpandSoundSelectView @JvmOverloads constructor(
|
||||||
private var isExpanded = false
|
private var isExpanded = false
|
||||||
private var animationDuration = 300
|
private var animationDuration = 300
|
||||||
|
|
||||||
private var mEventListener: IEventListener? = null
|
private lateinit var mEventListener: IEventListener
|
||||||
interface IEventListener {
|
interface IEventListener {
|
||||||
fun onItemSelected(position: Int, item: ChatSound)
|
fun onItemSelected(position: Int, item: ChatSound)
|
||||||
fun onExpanded(isExpanded: Boolean)
|
fun onExpanded(isExpanded: Boolean)
|
||||||
|
fun onFiltersChanged(sexValue: Int)
|
||||||
}
|
}
|
||||||
fun setEventListener(listener: IEventListener) {
|
fun setEventListener(listener: IEventListener) {
|
||||||
mEventListener = listener
|
mEventListener = listener
|
||||||
|
|
@ -49,11 +50,11 @@ class ExpandSoundSelectView @JvmOverloads constructor(
|
||||||
|
|
||||||
mExpandView.setEventListener(object: ExpandSoundSubView.IEventListener {
|
mExpandView.setEventListener(object: ExpandSoundSubView.IEventListener {
|
||||||
override fun onSoundSelected(sound: ChatSound) {
|
override fun onSoundSelected(sound: ChatSound) {
|
||||||
setTitleText(sound.name)
|
setTitleText(sound.nameLanguage)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFilterChanged(filterType: Int) {
|
override fun onFilterChanged(sexValue: Int) {
|
||||||
//
|
mEventListener.onFiltersChanged(sexValue)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class ExpandSoundSubView @JvmOverloads constructor(
|
||||||
|
|
||||||
interface IEventListener {
|
interface IEventListener {
|
||||||
fun onSoundSelected(sound: ChatSound)
|
fun onSoundSelected(sound: ChatSound)
|
||||||
fun onFilterChanged(filterType: Int)
|
fun onFilterChanged(setValue: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -92,9 +92,9 @@ class ExpandSoundSubView @JvmOverloads constructor(
|
||||||
|
|
||||||
onClick(R.id.item_root) {
|
onClick(R.id.item_root) {
|
||||||
val sound = getModel<ChatSound>()
|
val sound = getModel<ChatSound>()
|
||||||
if (!sound.select) {
|
if (!sound.isSelected) {
|
||||||
itemsRv.bindingAdapter.models?.filterIsInstance<ChatSound>()?.forEach { item ->
|
itemsRv.bindingAdapter.models?.filterIsInstance<ChatSound>()?.forEach { item ->
|
||||||
item.select = item == sound
|
item.isSelected = item == sound
|
||||||
}
|
}
|
||||||
itemsRv.bindingAdapter.notifyDataSetChanged()
|
itemsRv.bindingAdapter.notifyDataSetChanged()
|
||||||
mEventListener.onSoundSelected(sound)
|
mEventListener.onSoundSelected(sound)
|
||||||
|
|
@ -104,17 +104,17 @@ class ExpandSoundSubView @JvmOverloads constructor(
|
||||||
onBind {
|
onBind {
|
||||||
val item = getModel<ChatSound>()
|
val item = getModel<ChatSound>()
|
||||||
with(getBinding<LayoutItemSettingSoundBinding>()) {
|
with(getBinding<LayoutItemSettingSoundBinding>()) {
|
||||||
if (!item.imgUrl.isNullOrEmpty()) {
|
if (item.headPortrait.isNotEmpty()) {
|
||||||
userAvatar.load(item.imgUrl)
|
userAvatar.load(item.headPortrait)
|
||||||
} else {
|
} else {
|
||||||
userAvatar.setImageResource(if (item.isMale) R.mipmap.ic_gender_male else R.mipmap.ic_gender_female)
|
userAvatar.setImageResource(if (item.gender == 1) R.mipmap.ic_gender_male else R.mipmap.ic_gender_female)
|
||||||
}
|
}
|
||||||
|
|
||||||
tvSoundName.text = item.name
|
tvSoundName.text = item.nameLanguage
|
||||||
itemRoot.setBgColorDirectly(bgColor = ResUtil.getColor(if (item.isMale) R.color.male_bg else R.color.female_bg), radius = ResUtil.getPixelSize(R.dimen.dp_10).toFloat(), bgDrawable = null)
|
itemRoot.setBgColorDirectly(bgColor = ResUtil.getColor(if (item.gender == 1) R.color.male_bg else R.color.female_bg), radius = ResUtil.getPixelSize(R.dimen.dp_10).toFloat(), bgDrawable = null)
|
||||||
tvSoundDescrible.setTextColor(ResUtil.getColor(if (item.isMale) R.color.male_text_color else R.color.female_text_color))
|
tvSoundDescrible.setTextColor(ResUtil.getColor(if (item.gender == 1) R.color.male_text_color else R.color.female_text_color))
|
||||||
tvSoundDescrible.text = item.description
|
tvSoundDescrible.text = item.desLanguage
|
||||||
ivSelect.setImageResource(if (item.select) R.drawable.sound_item_selected else R.drawable.sound_item_unselected)
|
ivSelect.setImageResource(if (item.isSelected) R.drawable.sound_item_selected else R.drawable.sound_item_unselected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
package com.remax.visualnovel.utils
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class LanguageCodeUtil private constructor() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var instance: LanguageCodeUtil? = null
|
||||||
|
|
||||||
|
fun getInstance(): LanguageCodeUtil {
|
||||||
|
return instance ?: synchronized(this) {
|
||||||
|
instance ?: LanguageCodeUtil().also { instance = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言编号映射表(ISO 639-1 语言代码 -> 编号)
|
||||||
|
* 编号规则:按语言使用频率和地区重要性分配
|
||||||
|
*/
|
||||||
|
private val languageCodeMap: Map<String, Int> = mapOf(
|
||||||
|
// 主要语言(1-99)
|
||||||
|
"zh" to 1, // 中文
|
||||||
|
"en" to 2, // 英语
|
||||||
|
"es" to 3, // 西班牙语
|
||||||
|
"hi" to 4, // 印地语
|
||||||
|
"ar" to 5, // 阿拉伯语
|
||||||
|
"pt" to 6, // 葡萄牙语
|
||||||
|
"bn" to 7, // 孟加拉语
|
||||||
|
"ru" to 8, // 俄语
|
||||||
|
"ja" to 9, // 日语
|
||||||
|
"pa" to 10, // 旁遮普语
|
||||||
|
|
||||||
|
// 欧洲语言(100-199)
|
||||||
|
"fr" to 101, // 法语
|
||||||
|
"de" to 102, // 德语
|
||||||
|
"it" to 103, // 意大利语
|
||||||
|
"tr" to 104, // 土耳其语
|
||||||
|
"pl" to 105, // 波兰语
|
||||||
|
"uk" to 106, // 乌克兰语
|
||||||
|
"ro" to 107, // 罗马尼亚语
|
||||||
|
"nl" to 108, // 荷兰语
|
||||||
|
"hu" to 109, // 匈牙利语
|
||||||
|
"el" to 110, // 希腊语
|
||||||
|
"cs" to 111, // 捷克语
|
||||||
|
"sv" to 112, // 瑞典语
|
||||||
|
"da" to 113, // 丹麦语
|
||||||
|
"fi" to 114, // 芬兰语
|
||||||
|
"sk" to 115, // 斯洛伐克语
|
||||||
|
"bg" to 116, // 保加利亚语
|
||||||
|
"hr" to 117, // 克罗地亚语
|
||||||
|
"lt" to 118, // 立陶宛语
|
||||||
|
"sl" to 119, // 斯洛文尼亚语
|
||||||
|
"lv" to 120, // 拉脱维亚语
|
||||||
|
"et" to 121, // 爱沙尼亚语
|
||||||
|
|
||||||
|
// 亚洲语言(200-299)
|
||||||
|
"ko" to 201, // 韩语
|
||||||
|
"vi" to 202, // 越南语
|
||||||
|
"th" to 203, // 泰语
|
||||||
|
"fa" to 204, // 波斯语
|
||||||
|
"ur" to 205, // 乌尔都语
|
||||||
|
"ms" to 206, // 马来语
|
||||||
|
"id" to 207, // 印尼语
|
||||||
|
"tl" to 208, // 他加禄语
|
||||||
|
"my" to 209, // 缅甸语
|
||||||
|
"km" to 210, // 高棉语
|
||||||
|
"lo" to 211, // 老挝语
|
||||||
|
"ne" to 212, // 尼泊尔语
|
||||||
|
"si" to 213, // 僧伽罗语
|
||||||
|
"mn" to 214, // 蒙古语
|
||||||
|
"ka" to 215, // 格鲁吉亚语
|
||||||
|
"hy" to 216, // 亚美尼亚语
|
||||||
|
|
||||||
|
// 非洲语言(300-399)
|
||||||
|
"sw" to 301, // 斯瓦希里语
|
||||||
|
"am" to 302, // 阿姆哈拉语
|
||||||
|
"ha" to 303, // 豪萨语
|
||||||
|
"yo" to 304, // 约鲁巴语
|
||||||
|
"ig" to 305, // 伊博语
|
||||||
|
"zu" to 306, // 祖鲁语
|
||||||
|
"xh" to 307, // 科萨语
|
||||||
|
"rw" to 308, // 卢旺达语
|
||||||
|
|
||||||
|
// 其他语言(400-499)
|
||||||
|
"he" to 401, // 希伯来语
|
||||||
|
"yi" to 402, // 意第绪语
|
||||||
|
"cy" to 403, // 威尔士语
|
||||||
|
"ga" to 404, // 爱尔兰语
|
||||||
|
"is" to 405, // 冰岛语
|
||||||
|
"mt" to 406, // 马耳他语
|
||||||
|
|
||||||
|
// 中文变体(500-599)
|
||||||
|
"zh-CN" to 501, // 简体中文
|
||||||
|
"zh-TW" to 502, // 繁体中文(台湾)
|
||||||
|
"zh-HK" to 503, // 繁体中文(香港)
|
||||||
|
"zh-SG" to 504, // 简体中文(新加坡)
|
||||||
|
"zh-MO" to 505, // 繁体中文(澳门)
|
||||||
|
|
||||||
|
// 英语变体(600-699)
|
||||||
|
"en-US" to 601, // 美式英语
|
||||||
|
"en-GB" to 602, // 英式英语
|
||||||
|
"en-CA" to 603, // 加拿大英语
|
||||||
|
"en-AU" to 604, // 澳大利亚英语
|
||||||
|
"en-IN" to 605, // 印度英语
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取语言的Int编号
|
||||||
|
* @param locale 语言Locale对象
|
||||||
|
* @return 语言编号,如果未找到返回默认值(0表示未知语言)
|
||||||
|
*/
|
||||||
|
fun getLanguageCode(locale: Locale): Int {
|
||||||
|
// 优先尝试完整语言标签(包含地区)
|
||||||
|
val fullTag = locale.toLanguageTag()
|
||||||
|
if (languageCodeMap.containsKey(fullTag)) {
|
||||||
|
return languageCodeMap[fullTag]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试语言代码(不含地区)
|
||||||
|
val languageTag = locale.language
|
||||||
|
if (languageCodeMap.containsKey(languageTag)) {
|
||||||
|
return languageCodeMap[languageTag]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试构建语言-地区组合
|
||||||
|
val countrySpecificTag = "${locale.language}-${locale.country}"
|
||||||
|
if (languageCodeMap.containsKey(countrySpecificTag)) {
|
||||||
|
return languageCodeMap[countrySpecificTag]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未找到,返回未知语言代码
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据编号获取语言Locale对象
|
||||||
|
* @param code 语言编号
|
||||||
|
* @return 对应的Locale对象,如果未找到返回系统默认Locale
|
||||||
|
*/
|
||||||
|
fun getLocaleByCode(code: Int): Locale {
|
||||||
|
val languageEntry = languageCodeMap.entries.find { it.value == code }
|
||||||
|
return if (languageEntry != null) {
|
||||||
|
val languageTag = languageEntry.key
|
||||||
|
if (languageTag.contains("-")) {
|
||||||
|
// 包含地区的语言标签
|
||||||
|
val parts = languageTag.split("-")
|
||||||
|
if (parts.size >= 2) {
|
||||||
|
Locale(parts[0], parts[1])
|
||||||
|
} else {
|
||||||
|
Locale(languageTag)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 只有语言代码
|
||||||
|
Locale(languageTag)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未找到,返回系统默认
|
||||||
|
Locale.getDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取语言名称(根据编号)
|
||||||
|
*/
|
||||||
|
fun getLanguageNameByCode(code: Int, displayLocale: Locale = Locale.getDefault()): String {
|
||||||
|
val locale = getLocaleByCode(code)
|
||||||
|
return locale.getDisplayName(displayLocale)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查编号是否有效
|
||||||
|
*/
|
||||||
|
fun isValidLanguageCode(code: Int): Boolean {
|
||||||
|
return code != 0 && languageCodeMap.containsValue(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有支持的语言编号列表
|
||||||
|
*/
|
||||||
|
fun getSupportedLanguageCodes(): List<Int> {
|
||||||
|
return languageCodeMap.values.sorted()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有支持的语言信息
|
||||||
|
*/
|
||||||
|
fun getSupportedLanguages(displayLocale: Locale = Locale.getDefault()): List<LanguageInfo> {
|
||||||
|
return languageCodeMap.entries.map { (languageTag, code) ->
|
||||||
|
val locale = if (languageTag.contains("-")) {
|
||||||
|
val parts = languageTag.split("-")
|
||||||
|
Locale(parts[0], parts[1])
|
||||||
|
} else {
|
||||||
|
Locale(languageTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
LanguageInfo(
|
||||||
|
code = code,
|
||||||
|
languageTag = languageTag,
|
||||||
|
locale = locale,
|
||||||
|
displayName = locale.getDisplayName(displayLocale),
|
||||||
|
nativeName = locale.getDisplayName(locale),
|
||||||
|
isRTL = isRtlLanguage(locale)
|
||||||
|
)
|
||||||
|
}.sortedBy { it.code }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断语言是否RTL
|
||||||
|
*/
|
||||||
|
private fun isRtlLanguage(locale: Locale): Boolean {
|
||||||
|
val rtlLanguages = arrayOf("ar", "he", "fa", "ur", "ps", "sd", "ug", "yi")
|
||||||
|
return rtlLanguages.contains(locale.language)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言信息数据类
|
||||||
|
*/
|
||||||
|
data class LanguageInfo(
|
||||||
|
val code: Int,
|
||||||
|
val languageTag: String,
|
||||||
|
val locale: Locale,
|
||||||
|
val displayName: String,
|
||||||
|
val nativeName: String,
|
||||||
|
val isRTL: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
package com.remax.visualnovel.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.LocaleList
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class LanguageUtil private constructor() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var instance: LanguageUtil? = null
|
||||||
|
|
||||||
|
fun instance(): LanguageUtil {
|
||||||
|
return instance ?: synchronized(this) {
|
||||||
|
instance ?: LanguageUtil().also { instance = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val languageCodeUtil = LanguageCodeUtil.getInstance()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统Locale对象
|
||||||
|
*/
|
||||||
|
private fun getSystemLocale(): Locale {
|
||||||
|
return Locale.getDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取应用当前显示的语言(考虑应用内语言切换)
|
||||||
|
*/
|
||||||
|
fun getAppCurrentLanguage(context: Context): Locale {
|
||||||
|
val config = context.resources.configuration
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
config.locales.get(0)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
config.locale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前系统语言的Int编号
|
||||||
|
*/
|
||||||
|
fun getCurrentLanguageCode(): Int {
|
||||||
|
return languageCodeUtil.getLanguageCode(getSystemLocale())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定Locale的Int编号
|
||||||
|
*/
|
||||||
|
fun getLanguageCode(locale: Locale): Int {
|
||||||
|
return languageCodeUtil.getLanguageCode(locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据编号获取语言信息
|
||||||
|
*/
|
||||||
|
fun getLanguageInfoByCode(code: Int): LanguageCodeUtil.LanguageInfo? {
|
||||||
|
return languageCodeUtil.getSupportedLanguages().find { it.code == code }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统支持的所有语言列表(带编号)
|
||||||
|
*/
|
||||||
|
fun getSystemSupportedLanguagesWithCodes(): List<LanguageCodeUtil.LanguageInfo> {
|
||||||
|
val systemLocales = getSystemSupportedLocales()
|
||||||
|
return systemLocales.map { locale ->
|
||||||
|
val code = languageCodeUtil.getLanguageCode(locale)
|
||||||
|
LanguageCodeUtil.LanguageInfo(
|
||||||
|
code = code,
|
||||||
|
languageTag = locale.toLanguageTag(),
|
||||||
|
locale = locale,
|
||||||
|
displayName = locale.getDisplayName(getSystemLocale()),
|
||||||
|
nativeName = locale.getDisplayName(locale),
|
||||||
|
isRTL = isRtlLanguage(locale)
|
||||||
|
)
|
||||||
|
}.sortedBy { it.code }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查系统是否支持特定语言编号
|
||||||
|
*/
|
||||||
|
fun isLanguageCodeSupported(code: Int): Boolean {
|
||||||
|
return languageCodeUtil.isValidLanguageCode(code) &&
|
||||||
|
getSystemSupportedLocales().any {
|
||||||
|
languageCodeUtil.getLanguageCode(it) == code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取语言信息详情(增强版,包含编号)
|
||||||
|
*/
|
||||||
|
data class EnhancedLanguageInfo(
|
||||||
|
val languageCode: Int, // 语言编号
|
||||||
|
val languageTag: String, // 语言标签
|
||||||
|
val locale: Locale, // Locale对象
|
||||||
|
val displayName: String, // 本地化显示名称
|
||||||
|
val englishName: String, // 英文名称
|
||||||
|
val isRTL: Boolean, // 是否从右到左
|
||||||
|
val flagEmoji: String, // 国旗emoji
|
||||||
|
val direction: String // 语言方向
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getEnhancedLanguageInfo(locale: Locale = getSystemLocale()): EnhancedLanguageInfo {
|
||||||
|
val code = languageCodeUtil.getLanguageCode(locale)
|
||||||
|
return EnhancedLanguageInfo(
|
||||||
|
languageCode = code,
|
||||||
|
languageTag = locale.toLanguageTag(),
|
||||||
|
locale = locale,
|
||||||
|
displayName = locale.getDisplayName(locale),
|
||||||
|
englishName = locale.getDisplayName(Locale.ENGLISH),
|
||||||
|
isRTL = isRtlLanguage(locale),
|
||||||
|
flagEmoji = getLanguageFlagEmoji(locale),
|
||||||
|
direction = if (isRtlLanguage(locale)) "RTL" else "LTR"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统支持的所有语言列表
|
||||||
|
*/
|
||||||
|
fun getSystemSupportedLocales(): List<Locale> {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
val localeList = LocaleList.getDefault()
|
||||||
|
val locales = mutableListOf<Locale>()
|
||||||
|
for (i in 0 until localeList.size()) {
|
||||||
|
locales.add(localeList[i])
|
||||||
|
}
|
||||||
|
locales
|
||||||
|
} else {
|
||||||
|
listOf(Locale.getDefault())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查系统是否支持特定语言
|
||||||
|
*/
|
||||||
|
fun isLanguageSupported(languageCode: String): Boolean {
|
||||||
|
return getSystemSupportedLocales().any { it.language == languageCode }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断语言是否从右到左
|
||||||
|
*/
|
||||||
|
private fun isRtlLanguage(locale: Locale): Boolean {
|
||||||
|
val rtlLanguages = arrayOf("ar", "he", "fa", "ur", "ps", "sd", "ug", "yi")
|
||||||
|
return rtlLanguages.contains(locale.language)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取语言对应的国旗emoji
|
||||||
|
*/
|
||||||
|
fun getLanguageFlagEmoji(locale: Locale): String {
|
||||||
|
return when (locale.country.uppercase()) {
|
||||||
|
"CN" -> "🇨🇳"
|
||||||
|
"US", "GB" -> "🇺🇸"
|
||||||
|
"JP" -> "🇯🇵"
|
||||||
|
"KR" -> "🇰🇷"
|
||||||
|
"FR" -> "🇫🇷"
|
||||||
|
"DE" -> "🇩🇪"
|
||||||
|
"ES" -> "🇪🇸"
|
||||||
|
"IT" -> "🇮🇹"
|
||||||
|
"RU" -> "🇷🇺"
|
||||||
|
"BR" -> "🇧🇷"
|
||||||
|
"IN" -> "🇮🇳"
|
||||||
|
"CA" -> "🇨🇦"
|
||||||
|
"AU" -> "🇦🇺"
|
||||||
|
"MX" -> "🇲🇽"
|
||||||
|
"TW" -> "🇹🇼"
|
||||||
|
"HK" -> "🇭🇰"
|
||||||
|
"MO" -> "🇲🇴"
|
||||||
|
"SG" -> "🇸🇬"
|
||||||
|
else -> "🌐"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化语言显示
|
||||||
|
*/
|
||||||
|
fun formatLanguageDisplay(locale: Locale): String {
|
||||||
|
return when (locale.language) {
|
||||||
|
"zh" -> when (locale.country.uppercase()) {
|
||||||
|
"CN", "SG" -> "中文 (简体)"
|
||||||
|
"TW", "HK", "MO" -> "中文 (繁體)"
|
||||||
|
else -> "中文"
|
||||||
|
}
|
||||||
|
"en" -> "English"
|
||||||
|
"ja" -> "日本語"
|
||||||
|
"ko" -> "한국어"
|
||||||
|
"fr" -> "Français"
|
||||||
|
"de" -> "Deutsch"
|
||||||
|
"es" -> "Español"
|
||||||
|
"ru" -> "Русский"
|
||||||
|
"ar" -> "العربية"
|
||||||
|
"pt" -> "Português"
|
||||||
|
"hi" -> "हिन्दी"
|
||||||
|
"th" -> "ไทย"
|
||||||
|
"vi" -> "Tiếng Việt"
|
||||||
|
else -> locale.getDisplayName(locale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,5 +2,8 @@
|
||||||
<network-security-config>
|
<network-security-config>
|
||||||
<domain-config cleartextTrafficPermitted="true">
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
<domain includeSubdomains="true">54.223.196.180</domain>
|
<domain includeSubdomains="true">54.223.196.180</domain>
|
||||||
|
<domain includeSubdomains="true">192.168.110.113</domain>
|
||||||
</domain-config>
|
</domain-config>
|
||||||
|
|
||||||
|
|
||||||
</network-security-config>
|
</network-security-config>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue