Compare commits

..

No commits in common. "48b23cbdd30c9f22ab054a7858143e9dd26e3817" and "b426fda4d3e8eb4f5391e0a517c83deae1b447ca" have entirely different histories.

32 changed files with 79 additions and 847 deletions

View File

@ -129,9 +129,8 @@ android {
buildConfigString("API_PIGEON", "https://test-pigeon.xxxx.ai")
buildConfigString("API_LION", "https://test-lion.xxxx.ai")
buildConfigString("RECHAEGE_SERVICES", "https://test.xxxxx.ai/policy/recharge")
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
buildConfigString("API_BASE", "http://54.223.196.180:9090")
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
}
@ -148,9 +147,8 @@ android {
buildConfigString("API_PIGEON", "https://test-pigeon.xxxx.ai")
buildConfigString("API_LION", "https://test-lion.xxxx.ai")
buildConfigString("RECHAEGE_SERVICES", "https://test.xxxxx.ai/policy/recharge")
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
buildConfigString("API_BASE", "http://54.223.196.180:9090")
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
}
}
}

View File

@ -31,7 +31,6 @@
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!--TODO - remove usesCleartextTraffic below-->
<application
android:name=".configs.NovelApplication"
android:allowBackup="true"
@ -41,11 +40,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
>
android:theme="@style/AppTheme">
<activity
android:name=".ui.splash.SplashActivity"
android:theme="@style/AppTheme.Launcher"

View File

@ -1,24 +0,0 @@
package com.remax.visualnovel.api.service
import com.remax.visualnovel.BuildConfig
import com.remax.visualnovel.entity.response.basenew.ResponseNew
import com.remax.visualnovel.entity.request.ParamActorList
import com.remax.visualnovel.entity.response.ActorBean
import com.remax.visualnovel.entity.response.ActorTag
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
interface ActorsService {
@GET(BuildConfig.API_BASE + "/tag/getTags")
suspend fun requestActorTags(): ResponseNew<List<ActorTag>>
@POST(BuildConfig.API_BASE + "/character/select/list")
suspend fun requestActorList(@Body param: ParamActorList): ResponseNew<List<ActorBean>>
/*@GET(BuildConfig.API_BASE + "/character/select/roleInfo/{roleId}")
suspend fun requestActorInfo(): ResponseNew<ActorTag>*/
}

View File

@ -3,7 +3,6 @@ package com.remax.visualnovel.app.di
import com.remax.visualnovel.api.factory.ServiceFactory
import com.remax.visualnovel.api.service.AIService
import com.remax.visualnovel.api.service.ActorsService
import com.remax.visualnovel.api.service.BookService
import com.remax.visualnovel.api.service.ChatService
import com.remax.visualnovel.api.service.DictService
@ -65,10 +64,4 @@ object ApiServiceModule {
private inline fun <reified T> create(): T {
return ServiceFactory.createService()
}
@Singleton
@Provides
fun actorsService() = create<ActorsService>()
}

View File

@ -1,8 +0,0 @@
package com.remax.visualnovel.entity.request
data class ParamActorList(
var index: Int = 0,
var limit: Int = 2,
var tagIds: List<Long> = listOf<Long>(),
)

View File

@ -1,14 +0,0 @@
package com.remax.visualnovel.entity.response
data class ActorBean(
val id: Long,
val roleName: String,
val description: String,
val coverImageId: String,
val sourceId: Int, //来源ID;所属书籍/漫剧
val sourceType: Int, //来源分类
val commonCount: Int, //评论数
val createTime: String,
val status: String
)

View File

@ -1,7 +0,0 @@
package com.remax.visualnovel.entity.response
data class ActorTag(
val tagName: String,
val tagId: Long
)

View File

@ -1,10 +0,0 @@
package com.remax.visualnovel.entity.response.basenew
class ResponseData<T> {
val total = 0
val index = 0
val limit = 0
val rows: T? = null
}

View File

@ -1,88 +0,0 @@
package com.remax.visualnovel.entity.response.basenew
import com.google.gson.annotations.SerializedName
import com.remax.visualnovel.app.base.app.CommonApplicationProxy
import com.remax.visualnovel.extension.toast
open class ResponseNew<T>(
@SerializedName(value = "data")
val data: ResponseData<T>? = null,
open val code: Int = -1,
open val message: String = "",
) {
companion object {
const val SuccessCode = 200
/**
* zip打包的错误error封装
* new
*/
inline fun <reified T> createZipFailResponse(vararg data: ResponseNew<*>): ApiFailedResponse<T> {
val failedResponse = ApiFailedResponse<T>()
for (t in data) {
if (!t.isApiSuccess) {
failedResponse.code = t.code
failedResponse.message = t.message
break
}
}
return failedResponse
}
}
val isOk: Boolean
get() = code == SuccessCode
val isApiSuccess: Boolean
get() =
this is ApiSuccessResponse || this is ApiEmptyResponse
/**
* 将返回结果分为成功和失败2个高阶函数
*
* 使用inline修饰使2个参数可以调用外部函数return
*/
inline fun transformResult(apiSuccessCallback: ((T?) -> Unit) = {}, apiFailedCallback: ((ResponseNew<T>) -> Unit) = {}): ResponseNew<T> {
if (isApiSuccess) {
apiSuccessCallback.invoke(data?.rows)
} else {
apiFailedCallback.invoke(this)
}
return this
}
}
inline fun <reified T> ResponseNew<T>.parseData(listenerBuilder: (ResultBuilder<T>.() -> Unit), showToast: Boolean = false) {
val listener = ResultBuilder<T>().also(listenerBuilder)
when (this) {
is ApiSuccessResponse -> listener.onSuccess(response?.rows)
is ApiEmptyResponse -> listener.onSuccess(null)
is ApiFailedResponse -> {
listener.onFailed(this.code, this.message)
listener.onFailedWithData(data?.rows)
if (showToast) {
CommonApplicationProxy.application.toast(message)
}
}
}
listener.onComplete()
}
class ResultBuilder<T> {
var onSuccess: (data: T?) -> Unit = {}
var onFailed: (errorCode: Int, errorMsg: String) -> Unit = { _, _ ->
}
var onFailedWithData: (errorData: T?) -> Unit = {}
var onComplete: () -> Unit = {}
}
data class ApiSuccessResponse<T>(val response: ResponseData<T>? = null) : ResponseNew<T>(data = response)
class ApiEmptyResponse<T> : ResponseNew<T>()
data class ApiFailedResponse<T>(override var code: Int = -1, override var message: String = "") :
ResponseNew<T>(code = code, message = message)

View File

@ -6,10 +6,8 @@ import android.text.method.ScrollingMovementMethod
import android.view.Gravity
import android.view.ViewGroup
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.view.isVisible
import com.remax.visualnovel.R
import com.remax.visualnovel.databinding.DialogConfirmBinding
import com.remax.visualnovel.databinding.DialogDoubleBtnBinding
import com.remax.visualnovel.databinding.DialogSingleBtnLayout2Binding
import com.remax.visualnovel.databinding.DialogSingleBtnLayoutBinding
@ -24,53 +22,9 @@ import com.remax.visualnovel.widget.uitoken.changeTextStyle
import com.remax.visualnovel.widget.uitoken.view.UITokenTextView
import kotlin.math.min
/**
* 底部水平布局 两个按钮的dialog
* Created by HJW on 2025/7/18
*/
fun Activity.showConfirmDialog(
@DrawableRes dialogBg: Int? = null,
@StringRes title: Int? = null,
@StringRes description: Int? = null,
@DrawableRes topBtnIconRes: Int? = null,
topBtnClick: (() -> Unit)? = null,
@StringRes bottomLeftStrRes: Int? = null,
@StringRes bottomRightStrRes: Int? = null,
bottomRightClick: (() -> Unit)? = null,
): LBindingDialog<DialogConfirmBinding> {
val dialog = LBindingDialog(this, DialogConfirmBinding::inflate)
.with()
.setCenter()
dialog.setCanceledOnTouchOutside(false)
dialog.setCancelable(false)
dialog.binding.run {
setOnClick(ivTop, tvRight, tvLeft) {
when (this) {
tvLeft -> {
dialog.dismiss()
}
tvRight -> {
bottomRightClick?.invoke()
dialog.dismiss()
}
ivTop -> topBtnClick?.invoke()
}
}
if (dialogBg != null) root.background = getDrawable(dialogBg)
if (topBtnIconRes != null) ivTop.setImageResource(topBtnIconRes) else tvTitle.isVisible = false
if (title != null) tvTitle.text = getString(title) else tvTitle.isVisible = false
if (description != null) tvDescription.text = getString(description) else tvDescription.isVisible = false
if (bottomLeftStrRes != null) tvLeft.text = getString(bottomLeftStrRes) else tvLeft.isVisible = false
if (bottomRightStrRes != null) tvRight.text = getString(bottomRightStrRes) else tvRight.isVisible = false
}
dialog.show()
return dialog
}
/**
* 双按钮的全局统一的dialog样式

View File

@ -1,144 +0,0 @@
package com.remax.visualnovel.extension
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.remax.visualnovel.app.AbsView
import com.remax.visualnovel.entity.response.basenew.ResponseNew
import com.remax.visualnovel.entity.response.basenew.ResultBuilder
import com.remax.visualnovel.entity.response.basenew.parseData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
inline fun <T> launchFlow2(
crossinline requestBlock: suspend () -> ResponseNew<T>,
noinline startCallback: (() -> Unit)? = null,
noinline completeCallback: (() -> Unit)? = null,
): Flow<ResponseNew<T>> {
return flow {
emit(requestBlock())
}.onStart {
startCallback?.invoke()
}.onCompletion {
completeCallback?.invoke()
}.flowOn(Dispatchers.Main)
}
/**
* 简单的请求不返回任何实体类, 无loading和toast,数据可通过livedata/flow监听
*/
inline fun AbsView.launchWithRequest2(crossinline requestBlock: suspend () -> Unit, showLoading: Boolean = false) {
lifecycleScope.launch {
flow {
emit(requestBlock())
}.onStart {
if (showLoading) showLoading()
}.onCompletion {
if (showLoading) hideLoading()
}.collect()
}
}
/**
* 调用上面的默认loading
*/
inline fun AbsView.launchWithRequestLoading2(crossinline requestBlock: suspend () -> Unit) {
launchWithRequest(requestBlock, true)
}
/**
* 链式调用返回结果的处理都在一起viewmodel中不需要创建一个livedata对象
* 适用于不需要监听数据变化/一次性使用的场景,比如提交表单/登录
* 屏幕旋转Activity销毁重建数据会消失
*
* 默认无toast无loading
*/
inline fun <reified T> AbsView.launchAndCollect2(
crossinline requestBlock: suspend () -> ResponseNew<T>,
showLoading: Boolean = false,
showToast: Boolean = true,
crossinline listenerBuilder: (ResultBuilder<T>.() -> Unit) = {}
) {
lifecycleScope.launch {
launchFlow2(requestBlock, { if (showLoading) showLoading() }) { if (showLoading) hideLoading() }.collect { response ->
response.parseData(listenerBuilder, showToast)
}
}
}
inline fun <reified T> AbsView.launchAndLoadingCollect2(
crossinline requestBlock: suspend () -> ResponseNew<T>, showToast: Boolean = true, crossinline listenerBuilder: (ResultBuilder<T>.() -> Unit) = {}
) {
launchAndCollect2(requestBlock, showLoading = true, showToast = showToast, listenerBuilder = listenerBuilder)
}
/**
* 简单flow流订阅 生命周期安全
*/
inline fun <T> Flow<T?>.flowWithLaunch2(
lifecycleOwner: LifecycleOwner, minActiveState: Lifecycle.State = Lifecycle.State.STARTED, crossinline resCallback: ((t: T?) -> Unit)
) {
lifecycleOwner.lifecycleScope.launch {
flowWithLifecycle(lifecycleOwner.lifecycle, minActiveState).collect {
resCallback.invoke(it)
}
}
}
fun <T> Flow<T>.flowWithLifecycle2(lifecycle: Lifecycle, minActiveState: Lifecycle.State = Lifecycle.State.STARTED): Flow<T> = callbackFlow {
lifecycle.repeatOnLifecycle(minActiveState) {
this@flowWithLifecycle2.collect {
send(it)
}
}
close()
}
/**
* response liveData监听
*/
inline fun <reified T> LiveData<ResponseNew<T>>.observeIn2(
lifecycleOwner: LifecycleOwner, showToast: Boolean = true, crossinline listenerBuilder: ResultBuilder<T>.() -> Unit
) {
this.observe(lifecycleOwner, Observer {
it.parseData(listenerBuilder, showToast)
})
}
/**
* 订阅UI上展示Flow数据流
*
* 状态State StateFlow,粘性的 事件Event SharedFlow 在其 replayCache 中保留特定数量的最新值
* MutableSharedFlow :一次性事件不需要重放的状态变更例如 Toast
* MutableStateFlow : 页面需要的状态比如UI的刷新多次执行没有任何问题
* collectLastValue = true时stateFlow也不会发送未改变的value,就和sharedFlow一样的用法
*/
inline fun <reified T> Flow<ResponseNew<T>>.collectIn2(
lifecycleOwner: LifecycleOwner,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
showToast: Boolean = true,
crossinline listenerBuilder: ResultBuilder<T>.() -> Unit
): Job {
return lifecycleOwner.lifecycleScope.launch {
flowWithLifecycle2(lifecycleOwner.lifecycle, minActiveState).collect {
it.parseData(listenerBuilder, showToast)
}
}
}

View File

@ -1,18 +1,14 @@
package com.remax.visualnovel.repository.api
import com.remax.visualnovel.api.service.ActorsService
import com.remax.visualnovel.entity.request.ParamActorList
import com.remax.visualnovel.repository.api.base.BaseRepositoryNew
import com.remax.visualnovel.entity.response.Book
import com.remax.visualnovel.repository.api.base.BaseRepository
import com.remax.visualnovel.api.service.BookService
import com.remax.visualnovel.entity.response.base.Response
import javax.inject.Inject
class ActorsRepository @Inject constructor(private val mActorsService: ActorsService) : BaseRepositoryNew() {
suspend fun getActorTags() = executeHttp {
mActorsService.requestActorTags()
}
suspend fun getActorList(param: ParamActorList) = executeHttp {
mActorsService.requestActorList(param)
}
class ActorsRepository @Inject constructor(private val bookService: BookService) : BaseRepository() {
suspend fun getBooks(): Response<Book> {
return bookService.getBooks()
}
}

View File

@ -1,93 +0,0 @@
package com.remax.visualnovel.repository.api.base
import com.remax.visualnovel.R
import com.remax.visualnovel.app.base.app.CommonApplicationProxy
import com.remax.visualnovel.constant.StatusCode
import com.remax.visualnovel.entity.response.basenew.ApiEmptyResponse
import com.remax.visualnovel.entity.response.basenew.ApiFailedResponse
import com.remax.visualnovel.entity.response.basenew.ApiSuccessResponse
import com.remax.visualnovel.entity.response.basenew.ResponseNew
import com.remax.visualnovel.extension.toast
import com.remax.visualnovel.manager.login.LoginManager
import timber.log.Timber
open class BaseRepositoryNew {
/**
* 如果不需要检查登录那么在未登录的情况下 直接返回Success
* @param checkLogin 检查登录默认都需要检查
*/
suspend fun <T> executeHttp(checkLogin: Boolean = true, block: suspend () -> ResponseNew<T>): ResponseNew<T> {
return if (!checkLogin) {
if (LoginManager.isLogin) {
execute(block)
} else {
ApiSuccessResponse()
}
} else {
execute(block)
}
}
private suspend fun <T> execute(block: suspend () -> ResponseNew<T>): ResponseNew<T> {
return try {
val data = block.invoke()
handleHttpOk(data)
} catch (e: Exception) {
handleHttpError(e)
}
}
/**
* 非后台返回错误捕获到的异常
*/
private fun <T> handleHttpError(e: Throwable): ApiFailedResponse<T> {
Timber.e("responseAsync error -> ${e.localizedMessage}")
val errorMsg = CommonApplicationProxy.application.getString(R.string.your_network_error)
return ApiFailedResponse(message = errorMsg)
}
/**
* http返回200还要判断后端业务层isSuccess
*/
private fun <T> handleHttpOk(response: ResponseNew<T>): ResponseNew<T> {
return when {
//后端业务正常
response.isOk -> {
getHttpSuccessResponse(response)
}
//登录超时
response.code == /*StatusCode.TOKEN_EXPIRED.code*/403 -> {
CommonApplicationProxy.application.toast(response.message)
LoginManager.logout()
ApiFailedResponse(response.code, response.message)
}
//余额不足
response.code == /*StatusCode.INSUFFICIENT_BALANCE.code*/ 610 -> {
/*WalletManager.refreshWallet()
WalletManager.showChargeDialog()*/
ApiFailedResponse(response.code, response.message)
}
else -> {
ApiFailedResponse(response.code, response.message)
}
}
}
/**
* 成功和数据为空的处理
*/
private fun <T> getHttpSuccessResponse(response: ResponseNew<T>): ResponseNew<T> {
val data = response.data
return if (data == null) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(data)
}
}
}

View File

@ -15,13 +15,9 @@ import com.remax.visualnovel.entity.response.ChatBubble
import com.remax.visualnovel.entity.response.ChatHistory
import com.remax.visualnovel.entity.response.ChatMode
import com.remax.visualnovel.entity.response.ChatSound
import com.remax.visualnovel.extension.showConfirmDialog
import com.remax.visualnovel.ui.chat.ui.expandableSelector.ExpandAiModelSelectView
import com.remax.visualnovel.ui.chat.ui.expandableSelector.ExpandBubbleSelectView
import com.remax.visualnovel.ui.chat.ui.expandableSelector.ExpandChatModeSelectView
import com.remax.visualnovel.ui.chat.ui.expandableSelector.ExpandSoundSelectView
import com.remax.visualnovel.ui.chat.ui.expandableSelector.SelectorItem
import com.remax.visualnovel.widget.imageviewer.utils.activity
import java.util.Date
class ChatSettingView @JvmOverloads constructor(
@ -48,30 +44,10 @@ class ChatSettingView @JvmOverloads constructor(
initBubbleSelectView()
initBackgroundSelectView()
initHistoryListView()
initOtherClickEvent()
}
private fun initOtherClickEvent() {
with (mBinding) {
llDelete.setOnClickListener {
activity?.showConfirmDialog (
dialogBg = R.drawable.chat_delete_bg,
R.string.delete_chat,
R.string.delete_chat_hint,
R.mipmap.setting_delete,
null,
R.string.no,
R.string.sure,
{
// TODO - do delete the chat
}
)
}
}
}
private fun scroll2Position(targetSubView: View) {
mBinding.scrollView.scroll2ChildView(targetSubView)
mBinding.scrollView.smoothScrollTo(0, targetSubView.top)
}
fun initAiModelSelectorView() {
@ -147,26 +123,11 @@ class ChatSettingView @JvmOverloads constructor(
)
)
with(mBinding.chatModelSelector) {
setTitleIcon(R.mipmap.setting_chat_mode_icon)
setTitleText(R.string.chat_mode)
setItems(items)
selectItem(0)
setOnEventListener(object : ExpandChatModeSelectView.IEventListener {
override fun onItemSelected(
position: Int,
item: ChatMode
) {
}
override fun onExpanded(isExpanded: Boolean) {
if (isExpanded)
scroll2Position(this@with)
}
})
}
//aiModelSelector.setOnItemSelectedListener()
mBinding.chatModelSelector.setTitleIcon(R.mipmap.setting_chat_mode_icon)
mBinding.chatModelSelector.setTitleText(R.string.chat_mode)
mBinding.chatModelSelector.setItems(items)
mBinding.chatModelSelector.selectItem(0)
}
@ -262,23 +223,7 @@ class ChatSettingView @JvmOverloads constructor(
)
)
with(mBinding.bubbleSelectView) {
setTitleText(R.string.chat_bubble)
setItems(items)
setOnEventListener(object : ExpandBubbleSelectView.IEventListener {
override fun onItemSelected(
position: Int,
item: ChatBubble
) {
}
override fun onExpanded(isExpanded: Boolean) {
if (isExpanded)
scroll2Position(this@with)
}
})
}
mBinding.bubbleSelectView.setItems(items)
}
fun initBackgroundSelectView() {

View File

@ -1,26 +0,0 @@
package com.remax.visualnovel.ui.chat.ui
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.graphics.Rect
import androidx.core.widget.NestedScrollView
class MyScrollView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : NestedScrollView(context, attrs, defStyleAttr) {
fun scroll2ChildView(child: View) {
val tempRect = Rect()
child.getDrawingRect(tempRect)
offsetDescendantRectToMyCoords(child, tempRect)
val scrollDelta: Int = computeScrollDeltaToGetChildRectOnScreen(tempRect)
/*if (scrollDelta != 0) {
scrollBy(0, scrollDelta)
}*/
smoothScrollTo(0, tempRect.top)
}
}

View File

@ -34,10 +34,6 @@ class ExpandAiModelSelectView @JvmOverloads constructor(
fun onExpanded(isExpanded: Boolean)
}
fun setOnEventListener(listener: IEventListener) {
this.mEventListener = listener
}
init {
initView(context, attrs)
}
@ -209,7 +205,9 @@ class ExpandAiModelSelectView @JvmOverloads constructor(
return height
}
fun setOnEventListener(listener: IEventListener) {
this.mEventListener = listener
}
}

View File

@ -25,16 +25,7 @@ class ExpandBubbleSelectView @JvmOverloads constructor(
private var isExpanded = false
private var animationDuration = 300
private var mEventListener: IEventListener? = null
interface IEventListener {
fun onItemSelected(position: Int, item: ChatBubble)
fun onExpanded(isExpanded: Boolean)
}
fun setOnEventListener(listener: IEventListener) {
this.mEventListener = listener
}
private var itemSelectedListener: OnItemSelectedListener? = null
init {
initView(context, attrs)
@ -72,7 +63,6 @@ class ExpandBubbleSelectView @JvmOverloads constructor(
fun toggle() {
if (isExpanded) collapse() else expand()
mEventListener?.onExpanded(isExpanded)
}
fun expand() {
@ -138,5 +128,11 @@ class ExpandBubbleSelectView @JvmOverloads constructor(
return height
}
fun setOnItemSelectedListener(listener: OnItemSelectedListener) {
this.itemSelectedListener = listener
}
interface OnItemSelectedListener {
fun onItemSelected(position: Int, item: SelectorItem)
}
}

View File

@ -29,16 +29,7 @@ class ExpandChatModeSelectView @JvmOverloads constructor(
private var isExpanded = false
private var animationDuration = 300
private var items: List<ChatMode> = emptyList()
private var mEventListener: IEventListener? = null
interface IEventListener {
fun onItemSelected(position: Int, item: ChatMode)
fun onExpanded(isExpanded: Boolean)
}
fun setOnEventListener(listener: IEventListener) {
this.mEventListener = listener
}
private var itemSelectedListener: OnItemSelectedListener? = null
init {
initView(context, attrs)
@ -105,7 +96,7 @@ class ExpandChatModeSelectView @JvmOverloads constructor(
binding.root.setOnClickListener {
selectItem(position)
mEventListener?.onItemSelected(position, item)
itemSelectedListener?.onItemSelected(position, item)
}
return binding.root
}
@ -138,7 +129,6 @@ class ExpandChatModeSelectView @JvmOverloads constructor(
fun toggle() {
if (isExpanded) collapse() else expand()
mEventListener?.onExpanded(isExpanded)
}
fun expand() {
@ -204,5 +194,11 @@ class ExpandChatModeSelectView @JvmOverloads constructor(
return height
}
fun setOnItemSelectedListener(listener: OnItemSelectedListener) {
this.itemSelectedListener = listener
}
interface OnItemSelectedListener {
fun onItemSelected(position: Int, item: ChatMode)
}
}

View File

@ -112,7 +112,7 @@ class ExpandSoundSubView @JvmOverloads constructor(
}
tvSoundName.text = item.name
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.isMale) R.color.male_bg else R.color.female_bg), radius = ResUtil.getPixelSize(R.dimen.dp_10).toFloat())
tvSoundDescrible.setTextColor(ResUtil.getColor(if (item.isMale) R.color.male_text_color else R.color.female_text_color))
tvSoundDescrible.text = item.description
ivSelect.setImageResource(if (item.select) R.drawable.sound_item_selected else R.drawable.sound_item_unselected)

View File

@ -9,14 +9,11 @@ import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.chad.library.adapter.base.loadmore.LoadMoreStatus
import com.dylanc.loadingstateview.BgColorType
import com.remax.visualnovel.R
import com.remax.visualnovel.app.base.BaseBindingFragment
import com.remax.visualnovel.configs.NovelApplication
import com.remax.visualnovel.databinding.FragmentMainActorBinding
import com.remax.visualnovel.entity.request.ParamActorList
import com.remax.visualnovel.extension.launchAndCollect2
import com.remax.visualnovel.utils.Routers
import com.remax.visualnovel.utils.StatusBarUtil3
import dagger.hilt.android.AndroidEntryPoint
@ -29,10 +26,7 @@ import kotlin.math.max
class ActorListFragment : BaseBindingFragment<FragmentMainActorBinding>() {
private lateinit var mActorAdapter: ActorsAdapter
private val mActorsModel by viewModels<ActorsViewModel>()
private var mLoadedPageIndex = 0
private val mRequestParam by lazy { ParamActorList() }
private val actorsViewModel by viewModels<ActorListViewModel>()
override fun onCreated(bundle: Bundle?) {
setUI()
@ -42,10 +36,6 @@ class ActorListFragment : BaseBindingFragment<FragmentMainActorBinding>() {
return BgColorType.TRANSPARENT
}
override fun lazyInit() {
getActorList(true, showLoading = false)
}
private fun setUI() {
with(binding.root) {
setPadding(
@ -69,22 +59,11 @@ class ActorListFragment : BaseBindingFragment<FragmentMainActorBinding>() {
mActorsRv.addItemDecoration(GridSpacingItemDecoration(16))
mActorsRv.setHasFixedSize(true)
mActorsRv.itemAnimator = DefaultItemAnimator()
mActorAdapter = ActorsAdapter()
mActorsRv.adapter = mActorAdapter
val characterList = createSampleData()
with(mActorAdapter) {
setList(characterList)
loadMoreModule.setOnLoadMoreListener {
getActorList(false, showLoading = false)
}
}
with(refreshLayout) {
onRefresh {
getActorList(true, showLoading = true)
}
}
val characterList = createSampleData()
mActorAdapter.setList(characterList)
}
}
@ -115,55 +94,6 @@ class ActorListFragment : BaseBindingFragment<FragmentMainActorBinding>() {
}
}
private fun getActorList(isRefresh: Boolean, showLoading: Boolean) {
if (isRefresh) {
mLoadedPageIndex = 0
}
mRequestParam.index = mLoadedPageIndex + 1
launchAndCollect2({
mActorsModel.getActorList(mRequestParam)
}, showLoading = showLoading) {
onSuccess = {
val data = it ?: emptyList()
with(mActorAdapter) {
/*if (isRefresh) {
setList(data)
setMyEmptyView(R.string.no_character_yet, topMargin = 60)
} else {
addData(data)
loadMoreModule.loadMoreComplete()
}
if (data.size < PageQuery.DEFAULT_PAGE_SIZE) {
loadMoreModule.loadMoreEnd()
}*/
}
mLoadedPageIndex++
}
onComplete = {
binding.refreshLayout.finishRefresh()
mActorAdapter.loadMoreModule.loadMoreComplete()
}
onFailed = { errorCode, errorMsg ->
var temp = 100
}
onFailedWithData = {
var temp = 100
}
}
}
private fun createSampleData(): List<ActorItem> {
return listOf(
ActorItem(

View File

@ -0,0 +1,25 @@
package com.remax.visualnovel.ui.main.actor
import com.remax.visualnovel.entity.response.Book
import com.remax.visualnovel.app.viewmodel.base.BaseViewModel
import com.remax.visualnovel.entity.response.base.Response
import com.remax.visualnovel.repository.api.ActorsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import javax.inject.Inject
@HiltViewModel
class ActorListViewModel @Inject constructor(private val chatRepository: ActorsRepository) : BaseViewModel() {
private val _msgStatFlow = MutableSharedFlow<Response<Book>>()
val msgStatFlow = _msgStatFlow.asSharedFlow()
suspend fun getMessageStat() {
_msgStatFlow.emit(chatRepository.getBooks())
}
}

View File

@ -1,24 +0,0 @@
package com.remax.visualnovel.ui.main.actor
import com.remax.visualnovel.app.viewmodel.base.BaseViewModel
import com.remax.visualnovel.entity.request.ParamActorList
import com.remax.visualnovel.entity.response.basenew.ResponseNew
import com.remax.visualnovel.entity.response.ActorBean
import com.remax.visualnovel.repository.api.ActorsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import javax.inject.Inject
@HiltViewModel
class ActorsViewModel @Inject constructor(private val mActorsRepository: ActorsRepository) : BaseViewModel() {
private val _actorsStatFlow = MutableSharedFlow<ResponseNew<List<ActorBean>>>()
val actorsStatFlow = _actorsStatFlow.asSharedFlow()
suspend fun getActorList(param: ParamActorList) = mActorsRepository.getActorList(param)
}

View File

@ -7,7 +7,6 @@ import android.graphics.Outline
import android.graphics.Shader
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.StateListDrawable
import android.os.Build
import android.util.TypedValue
@ -185,12 +184,10 @@ fun View.changeBackground(customViewToken: CustomViewToken) {
customViewToken.run {
if (advRadius > 0 || advStrokeWidth > 0 ||
advTopRightRadius > 0 || advTopLeftRadius > 0 ||
advBottomLeftRadius > 0 || advBottomRightRadius > 0
|| advBgDrawable != null) {
advBottomLeftRadius > 0 || advBottomRightRadius > 0) {
setBgColorDirectly(
bgColor = advBgColor,
radius = advRadius,
bgDrawable = advBgDrawable,
topLeftRadius = if (advTopLeftRadius > 0F) advTopLeftRadius else advRadius,
topRightRadius = if (advTopRightRadius > 0F) advTopRightRadius else advRadius,
bottomRightRadius = if (advBottomRightRadius > 0F) advBottomRightRadius else advRadius,
@ -525,7 +522,6 @@ fun View.getGradientDrawable(
fun View.setBgColorDirectly(
bgColor: Int = 0,
radius: Float = 0F,
bgDrawable: Drawable?,
topLeftRadius: Float = radius,
topRightRadius: Float = radius,
bottomRightRadius: Float = radius,
@ -536,7 +532,6 @@ fun View.setBgColorDirectly(
val resDrawable = StateListDrawable()
val normalDrawable = getGradientDrawableDirectly(
bgColor,
bgDrawable,
topLeftRadius,
topRightRadius,
bottomRightRadius,
@ -551,7 +546,6 @@ fun View.setBgColorDirectly(
fun View.getGradientDrawableDirectly(
bgColor: Int = 0,
bgDrawable: Drawable?,
topLeftRadius: Float = 0F,
topRightRadius: Float = 0F,
bottomRightRadius: Float = 0F,
@ -577,6 +571,6 @@ fun View.getGradientDrawableDirectly(
bottomLeftRadius,
bottomLeftRadius
)
return if (bgDrawable != null) LayerDrawable(arrayOf(bgDrawable, gradientDrawable)) else gradientDrawable
return gradientDrawable
}

View File

@ -1,7 +1,6 @@
package com.remax.visualnovel.widget.uitoken.bean
import android.graphics.Color
import android.graphics.drawable.Drawable
/**
* Created by HJW on 2022/9/1
@ -48,7 +47,6 @@ data class CustomViewToken(
//---------------------- New added -----------------------
var advBgColor:Int = Color.TRANSPARENT,
var advBgDrawable: Drawable? = null,
var advRadius:Float = 0F,
var advTopLeftRadius:Float = 0F,
var advTopRightRadius:Float = 0F,

View File

@ -81,7 +81,6 @@ open class UITokenTextView @JvmOverloads constructor(context: Context, private v
//<!-- new added 2025.10.29 -->
advBgColor = getColor(R.styleable.UITokenTextView_advBgColor, advBgColor)
advBgDrawable = getDrawable(R.styleable.UITokenTextView_advBgDrawable)
advRadius = getDimension(R.styleable.UITokenTextView_advRadius, advRadius)
advTopLeftRadius = getDimension(R.styleable.UITokenTextView_advTopLeftRadius, advTopLeftRadius)
advTopRightRadius = getDimension(R.styleable.UITokenTextView_advTopRightRadius, advTopRightRadius)

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<size
android:width="270dp"
android:height="300dp"
/>
<corners android:radius="25dp" />
<gradient android:type="linear"
android:angle="270"
android:startColor="#ffffd4d4"
android:endColor="#ffffffff"
/>
</shape>

View File

@ -1,107 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.remax.visualnovel.widget.uitoken.view.UITokenConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dp_20"
app:advRadius="@dimen/dp_25"
app:advBgDrawable="@drawable/chat_delete_bg">
<com.remax.visualnovel.widget.uitoken.view.UITokenImageView
android:id="@+id/iv_top"
android:layout_width="@dimen/dp_98"
android:layout_height="@dimen/dp_98"
android:padding="@dimen/dp_20"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@mipmap/setting_delete"
android:tint="@color/white"
app:advBgColor="@color/red_ff3b30"
app:advRadius="@dimen/dp_49"
android:scaleType="fitCenter"
/>
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iv_top"
android:layout_marginTop="@dimen/dp_10"
android:text="title"
android:textStyle="bold"
android:textSize="@dimen/sp_18"
android:textColor="@color/black"
/>
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
android:id="@+id/tv_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
android:layout_marginTop="@dimen/dp_20"
android:text="des"
android:textSize="@dimen/sp_14"
android:textColor="@color/gray9"
/>
<com.remax.visualnovel.widget.uitoken.view.UITokenLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_description"
android:layout_marginTop="@dimen/dp_50"
android:orientation="horizontal">
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
android:id="@+id/tv_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_10"
android:layout_weight="1"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
android:layout_marginTop="@dimen/dp_20"
android:text="@string/no"
android:textSize="@dimen/sp_15"
android:textStyle="bold"
android:textColor="@color/black"
android:padding="@dimen/dp_15"
app:advStrokeColor="@color/gray9"
app:advStrokeWidth="@dimen/dp_2"
app:advRadius="@dimen/dp_25"
/>
<com.remax.visualnovel.widget.uitoken.view.UITokenTextView
android:id="@+id/tv_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_10"
android:layout_weight="1"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
android:layout_marginTop="@dimen/dp_20"
android:text="@string/sure"
android:textSize="@dimen/sp_15"
android:textStyle="bold"
android:textColor="@color/white"
android:padding="@dimen/dp_15"
app:advBgColor="@color/red_ff3b30"
app:advRadius="@dimen/dp_25"
/>
</com.remax.visualnovel.widget.uitoken.view.UITokenLinearLayout>
</com.remax.visualnovel.widget.uitoken.view.UITokenConstraintLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<com.remax.visualnovel.widget.uitoken.view.UITokenLinearLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@ -27,17 +27,12 @@
app:tag_expand_drawable="@mipmap/tag_flow_expand"
app:tag_shrink_drawable="@mipmap/tag_flow_shrink" />
<com.drake.brv.PageRefreshLayout
android:id="@+id/refreshLayout"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/m_actors_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/dp_4">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/m_actors_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.drake.brv.PageRefreshLayout>
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="@dimen/dp_4"
/>
</com.remax.visualnovel.widget.uitoken.view.UITokenLinearLayout>
</LinearLayout>

View File

@ -44,7 +44,7 @@
</com.remax.visualnovel.widget.uitoken.view.UITokenFrameLayout>
<com.remax.visualnovel.ui.chat.ui.MyScrollView
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -349,7 +349,7 @@
</com.remax.visualnovel.widget.uitoken.view.UITokenLinearLayout>
</com.remax.visualnovel.ui.chat.ui.MyScrollView>
</androidx.core.widget.NestedScrollView>
</com.remax.visualnovel.widget.uitoken.view.UITokenRelativeLayout>

View File

@ -61,7 +61,6 @@
<!-- new added 2025.10.29 -->
<attr name="advBgColor" format="color" />
<attr name="advBgDrawable" format="reference" />
<attr name="advRadius" format="dimension" />
<attr name="advTopLeftRadius" format="dimension" />
<attr name="advTopRightRadius" format="dimension" />
@ -113,7 +112,6 @@
<!-- new added 2025.10.29 -->
<attr name="advBgColor" />
<attr name="advBgDrawable" />
<attr name="advRadius" />
<attr name="advTopLeftRadius" />
<attr name="advTopRightRadius" />
@ -152,7 +150,6 @@
<!-- new added 2025.10.29 -->
<attr name="advBgColor" />
<attr name="advBgDrawable" />
<attr name="advRadius" />
<attr name="advTopLeftRadius" />
<attr name="advTopRightRadius" />
@ -186,7 +183,6 @@
<!-- new added 2025.10.29 -->
<attr name="advBgColor" />
<attr name="advBgDrawable" />
<attr name="advRadius" />
<attr name="advTopLeftRadius" />
<attr name="advTopRightRadius" />
@ -220,7 +216,6 @@
<!-- new added 2025.10.29 -->
<attr name="advBgColor" />
<attr name="advBgDrawable" />
<attr name="advRadius" />
<attr name="advTopLeftRadius" />
<attr name="advTopRightRadius" />
@ -254,7 +249,6 @@
<!-- new added 2025.10.29 -->
<attr name="advBgColor" />
<attr name="advBgDrawable" />
<attr name="advRadius" />
<attr name="advTopLeftRadius" />
<attr name="advTopRightRadius" />
@ -288,7 +282,6 @@
<!-- new added 2025.10.29 -->
<attr name="advBgColor" />
<attr name="advBgDrawable" />
<attr name="advRadius" />
<attr name="advTopLeftRadius" />
<attr name="advTopRightRadius" />
@ -322,7 +315,6 @@
<!-- new added 2025.10.29 -->
<attr name="advBgColor" />
<attr name="advBgDrawable" />
<attr name="advRadius" />
<attr name="advTopLeftRadius" />
<attr name="advTopRightRadius" />

View File

@ -486,11 +486,5 @@
<string name="call_connecting">Connecting...</string>
<string name="call_fail_hint">Network connection issue. Please check your connection and try again.</string>
<string name="start_new_chat">Start New Chat</string>
<string name="delete_chat">DELETE CHAT</string>
<string name="delete_chat_hint">This will permanently remove all data. This action is irreversible. Are you sure</string>
<string name="sure">SURE</string>
<string name="no">NO</string>
</resources>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">54.223.196.180</domain>
</domain-config>
</network-security-config>