init project uploading
This commit is contained in:
parent
f854b33aba
commit
b535a7667d
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".configs.NovelApplication"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.main.MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.remax.visualnovel.api.interceptor.util;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class AES {
|
||||||
|
|
||||||
|
private static final String KEY_SUFFIX = "90e8kDQUIWpXg8Jp";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a 128-bit AES key you need 16 bytes.
|
||||||
|
* For a 256-bit AES key you need 32 bytes.
|
||||||
|
*/
|
||||||
|
public static String KEY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IV length: must be 16 bytes long
|
||||||
|
*/
|
||||||
|
public static final String IV = "sdf4ddfsFD86Vdf2";
|
||||||
|
|
||||||
|
private AES() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void genKey(String token) {
|
||||||
|
//token加密最多截取前32位
|
||||||
|
String encodeStr = (token.length() > 32 ? token.substring(0, 32) : token) + KEY_SUFFIX;
|
||||||
|
KEY = Md5.encode(encodeStr).toUpperCase();
|
||||||
|
Timber.d("genKey: %s", KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encrypt(String data) {
|
||||||
|
return encrypt(data, KEY, IV);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String desEncrypt(String data) {
|
||||||
|
return desEncrypt(data, KEY, IV);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encrypt(String data, String key, String initVector) {
|
||||||
|
try {
|
||||||
|
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes(StandardCharsets.UTF_8));
|
||||||
|
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
|
||||||
|
|
||||||
|
byte[] encrypted = cipher.doFinal(data.getBytes());
|
||||||
|
return Base64.encode(encrypted);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String desEncrypt(String data, String key, String initVector) {
|
||||||
|
try {
|
||||||
|
byte[] encrypted = Base64.decode(data);
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
|
||||||
|
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
|
||||||
|
IvParameterSpec ivParameterSpec = new IvParameterSpec(initVector.getBytes());
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
|
||||||
|
|
||||||
|
byte[] original = cipher.doFinal(encrypted);
|
||||||
|
|
||||||
|
return new String(original);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.remax.visualnovel.app
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
|
||||||
|
|
||||||
|
interface AbsView : LifecycleOwner{
|
||||||
|
|
||||||
|
fun showLoading()
|
||||||
|
|
||||||
|
fun hideLoading()
|
||||||
|
|
||||||
|
fun showToast(text: String?)
|
||||||
|
|
||||||
|
fun showToast(resId: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
package com.remax.visualnovel.app.activityresultapi
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.ActivityResult
|
||||||
|
import androidx.activity.result.ActivityResultCallback
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.alibaba.android.arouter.core.LogisticsCenter
|
||||||
|
import com.alibaba.android.arouter.exception.NoRouteFoundException
|
||||||
|
import com.alibaba.android.arouter.facade.Postcard
|
||||||
|
import com.alibaba.android.arouter.facade.callback.InterceptorCallback
|
||||||
|
import com.alibaba.android.arouter.facade.callback.NavigationCallback
|
||||||
|
import com.alibaba.android.arouter.facade.enums.RouteType
|
||||||
|
import com.alibaba.android.arouter.facade.service.DegradeService
|
||||||
|
import com.alibaba.android.arouter.facade.service.InterceptorService
|
||||||
|
import com.alibaba.android.arouter.facade.service.PretreatmentService
|
||||||
|
import com.alibaba.android.arouter.launcher.ARouter
|
||||||
|
import com.remax.visualnovel.app.initializer.impl.ActivityLifecycleInitializer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取activityResultLauncher
|
||||||
|
*/
|
||||||
|
fun FragmentActivity.activityResultLauncher(): XActivityResultContract<Intent, ActivityResult>? {
|
||||||
|
val activityKey = intent.getStringExtra(ActivityLifecycleInitializer.KEY_ACTIVITY_RESULT_API)
|
||||||
|
return if (TextUtils.isEmpty(activityKey)) null else ActivityLifecycleInitializer.resultLauncherMap[activityKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在Activity使用registerForActivityResult
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun FragmentActivity.registerForActivityResult(
|
||||||
|
intent: Intent,
|
||||||
|
activityResultCallback: ActivityResultCallback<ActivityResult>? = null
|
||||||
|
) {
|
||||||
|
activityResultLauncher()?.launch(intent, activityResultCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在Activity使用registerForActivityResult
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
inline fun <reified T : FragmentActivity> FragmentActivity.registerForActivityResult(
|
||||||
|
intentExtra: (intent: Intent) -> Unit = {},
|
||||||
|
activityResultCallback: ActivityResultCallback<ActivityResult>? = null
|
||||||
|
) {
|
||||||
|
val intent = Intent(this, T::class.java)
|
||||||
|
intentExtra(intent)
|
||||||
|
registerForActivityResult(intent, activityResultCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在Fragment使用registerForActivityResult
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun Fragment.registerForActivityResult(
|
||||||
|
intent: Intent,
|
||||||
|
activityResultCallback: ActivityResultCallback<ActivityResult>? = null
|
||||||
|
) {
|
||||||
|
requireActivity().activityResultLauncher()?.launch(intent, activityResultCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在Fragment使用registerForActivityResult
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
inline fun <reified T : FragmentActivity> Fragment.registerForActivityResult(
|
||||||
|
intentExtra: (intent: Intent) -> Unit = {},
|
||||||
|
activityResultCallback: ActivityResultCallback<ActivityResult>? = null
|
||||||
|
) {
|
||||||
|
val intent = Intent(this.requireActivity(), T::class.java)
|
||||||
|
intentExtra(intent)
|
||||||
|
registerForActivityResult(intent, activityResultCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity中ARouter导航
|
||||||
|
* @param [activity] activity
|
||||||
|
* @param [activityResultCallback] 返回数据回调
|
||||||
|
*/
|
||||||
|
fun Postcard.navigation(
|
||||||
|
activity: FragmentActivity?,
|
||||||
|
activityResultCallback: ActivityResultCallback<ActivityResult>
|
||||||
|
): Any? {
|
||||||
|
return navigation(activity, null, activityResultCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment中ARouter导航
|
||||||
|
* @param [fragment] Fragment
|
||||||
|
* @param [activityResultCallback] 返回数据回调
|
||||||
|
*/
|
||||||
|
fun Postcard.navigation(
|
||||||
|
fragment: Fragment?,
|
||||||
|
activityResultCallback: ActivityResultCallback<ActivityResult>
|
||||||
|
): Any? {
|
||||||
|
return navigation(fragment?.requireActivity(), null, activityResultCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment中ARouter导航
|
||||||
|
* @param [fragment] Fragment
|
||||||
|
* @param [callback] 回调
|
||||||
|
* @param [activityResultCallback] 返回数据回调
|
||||||
|
*/
|
||||||
|
fun Postcard.navigation(
|
||||||
|
fragment: Fragment?,
|
||||||
|
callback: NavigationCallback?,
|
||||||
|
activityResultCallback: ActivityResultCallback<ActivityResult>
|
||||||
|
): Any? {
|
||||||
|
return navigation(fragment?.requireActivity(), callback, activityResultCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity中ARouter导航
|
||||||
|
* @param [activity] Fragment
|
||||||
|
* @param [callback] 回调
|
||||||
|
* @param [activityResultCallback] 返回数据回调
|
||||||
|
*/
|
||||||
|
fun Postcard.navigation(
|
||||||
|
activity: FragmentActivity?,
|
||||||
|
callback: NavigationCallback?,
|
||||||
|
activityResultCallback: ActivityResultCallback<ActivityResult>
|
||||||
|
): Any? {
|
||||||
|
if (activity == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val _postcard = this
|
||||||
|
val pretreatmentService = ARouter.getInstance().navigation(PretreatmentService::class.java)
|
||||||
|
if (null != pretreatmentService && !pretreatmentService.onPretreatment(activity, this)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
LogisticsCenter.completion(_postcard)
|
||||||
|
} catch (ex: NoRouteFoundException) {
|
||||||
|
debugLog(activity, path, group)
|
||||||
|
if (null != callback) {
|
||||||
|
callback.onLost(_postcard)
|
||||||
|
} else {
|
||||||
|
val degradeService = ARouter.getInstance().navigation(DegradeService::class.java)
|
||||||
|
degradeService?.onLost(activity, _postcard)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
callback?.onFound(_postcard)
|
||||||
|
val interceptorService = ARouter.getInstance().navigation(InterceptorService::class.java)
|
||||||
|
if (!isGreenChannel && interceptorService != null) {
|
||||||
|
interceptorService.doInterceptions(_postcard, object : InterceptorCallback {
|
||||||
|
|
||||||
|
override fun onContinue(postcard: Postcard?) {
|
||||||
|
_navigation(activity, _postcard, activityResultCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInterrupt(exception: Throwable?) {
|
||||||
|
callback?.onInterrupt(_postcard)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return _navigation(activity, this, activityResultCallback)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug模式下日志打印
|
||||||
|
*/
|
||||||
|
private fun debugLog(activity: FragmentActivity, path: String?, group: String?) {
|
||||||
|
if (ARouter.debuggable()) {
|
||||||
|
// Show friendly tips for user.
|
||||||
|
activity.runOnUiThread {
|
||||||
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
"There's no route matched!Path = [${path}]Group = [${group}]",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun _navigation(
|
||||||
|
activity: FragmentActivity,
|
||||||
|
postcard: Postcard,
|
||||||
|
activityResultCallback: ActivityResultCallback<ActivityResult>,
|
||||||
|
): Any? {
|
||||||
|
|
||||||
|
return when (postcard.type) {
|
||||||
|
RouteType.ACTIVITY -> {
|
||||||
|
|
||||||
|
val intent = Intent(activity, postcard.destination)
|
||||||
|
postcard.extras?.let { intent.putExtras(it) }
|
||||||
|
if (postcard.flags != -1) {
|
||||||
|
intent.flags = postcard.flags
|
||||||
|
}
|
||||||
|
|
||||||
|
postcard.action?.let { intent.action = postcard.action }
|
||||||
|
activity.runOnUiThread {
|
||||||
|
//适配动画
|
||||||
|
if ((postcard.enterAnim != -1 && postcard.exitAnim != -1)) {
|
||||||
|
activity.overridePendingTransition(postcard.enterAnim, postcard.exitAnim)
|
||||||
|
}
|
||||||
|
activity.registerForActivityResult(intent, activityResultCallback)
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
RouteType.PROVIDER -> {
|
||||||
|
postcard.provider
|
||||||
|
}
|
||||||
|
RouteType.FRAGMENT -> {
|
||||||
|
val fragmentMeta = postcard.destination
|
||||||
|
try {
|
||||||
|
val instance = fragmentMeta.getConstructor().newInstance()
|
||||||
|
if (instance is Fragment) {
|
||||||
|
instance.arguments = postcard.extras
|
||||||
|
}
|
||||||
|
instance
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.remax.visualnovel.app.initializer.impl
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.TextUtils
|
||||||
|
import androidx.activity.result.ActivityResult
|
||||||
|
import androidx.activity.result.ActivityResultCaller
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.remax.visualnovel.app.activityresultapi.XActivityResultContract
|
||||||
|
import com.remax.visualnovel.app.initializer.AppInitializers
|
||||||
|
import com.remax.visualnovel.app.viewmodel.AppIMViewModel
|
||||||
|
import com.remax.visualnovel.configs.NovelApplication
|
||||||
|
import com.remax.visualnovel.utils.StatusBarUtils
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2023/5/11
|
||||||
|
*/
|
||||||
|
class ActivityLifecycleInitializer(val application: Application, private val appIMViewModel: AppIMViewModel) : AppInitializers {
|
||||||
|
|
||||||
|
override fun init() {
|
||||||
|
application.registerActivityLifecycleCallbacks(AppLifecycleCallbacks(appIMViewModel))
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class AppLifecycleCallbacks constructor(private val appIMViewModel: AppIMViewModel) : Application.ActivityLifecycleCallbacks {
|
||||||
|
|
||||||
|
private var activityCount = 0
|
||||||
|
|
||||||
|
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||||
|
if (activity is ActivityResultCaller) {
|
||||||
|
//生成一个Key
|
||||||
|
val activityKey = activity.javaClass.simpleName + System.currentTimeMillis()
|
||||||
|
//添加一个默认ActivityResultLauncher
|
||||||
|
val resultLauncher =
|
||||||
|
XActivityResultContract(activity, ActivityResultContracts.StartActivityForResult())
|
||||||
|
//把生成的Key放到intent中,作为每一个Activity的唯一标识
|
||||||
|
activity.intent.putExtra(KEY_ACTIVITY_RESULT_API, activityKey)
|
||||||
|
//存放到Map中
|
||||||
|
resultLauncherMap[activityKey] = resultLauncher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStarted(activity: Activity) {
|
||||||
|
activityCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityPaused(activity: Activity) {
|
||||||
|
NovelApplication.setCurrentActivity(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResumed(activity: Activity) {
|
||||||
|
Timber.d("currentActivity:${activity::class.java.name}")
|
||||||
|
NovelApplication.setCurrentActivity(activity)
|
||||||
|
if (StatusBarUtils.statusBarHeight == 0) {
|
||||||
|
StatusBarUtils.getStatusBarHeight(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO - check pay info later
|
||||||
|
/*if (activity !is WelcomeActivity) {
|
||||||
|
GooglePayManager.checkProductDetails()
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStopped(activity: Activity) {
|
||||||
|
activityCount--
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityDestroyed(activity: Activity) {
|
||||||
|
if (activity is FragmentActivity) {
|
||||||
|
val activityKey = activity.intent.getStringExtra(KEY_ACTIVITY_RESULT_API)
|
||||||
|
if (!TextUtils.isEmpty(activityKey)) {
|
||||||
|
resultLauncherMap[activityKey]?.unregister()
|
||||||
|
//移除activity的resultLauncher
|
||||||
|
resultLauncherMap.remove(activityKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* 保存ActivityResultApi的Key
|
||||||
|
*/
|
||||||
|
const val KEY_ACTIVITY_RESULT_API = "activityResultApi"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存activity和Fragment的resultLauncher
|
||||||
|
*/
|
||||||
|
val resultLauncherMap: MutableMap<String, XActivityResultContract<Intent, ActivityResult>> =
|
||||||
|
mutableMapOf()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.remax.visualnovel.entity.request
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/26
|
||||||
|
*/
|
||||||
|
data class AIFeedback(
|
||||||
|
/**
|
||||||
|
* AI的ID
|
||||||
|
*/
|
||||||
|
val aiId: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本内容
|
||||||
|
*/
|
||||||
|
val content: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息ID
|
||||||
|
*/
|
||||||
|
val messageId: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作类型:空或0 无操作、1 赞、2 踩
|
||||||
|
*/
|
||||||
|
val optType: Int
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val NONE = 0
|
||||||
|
const val LIKE = 1
|
||||||
|
const val DISLIKE = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/7/18
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class AIDict(
|
||||||
|
val roleDictList: List<AIDictItem>,
|
||||||
|
val characterDictList: List<AIDictItem>,
|
||||||
|
val imageStyleDictList: List<AIDictItem>,
|
||||||
|
val timbreDictList: List<AIVoice>,
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
fun setInitSelect(character: Character?) {
|
||||||
|
val imageStyleCode = character?.aiUserExt?.imageStyleCode
|
||||||
|
val findImageStyle = imageStyleDictList.find { character -> character.code == imageStyleCode }
|
||||||
|
(findImageStyle ?: imageStyleDictList.firstOrNull())?.select = true
|
||||||
|
|
||||||
|
val roleCode = character?.roleCode
|
||||||
|
if (roleCode == null) {
|
||||||
|
roleDictList.firstOrNull()?.childDictList?.firstOrNull()?.select = true
|
||||||
|
} else {
|
||||||
|
roleDictList.forEach { roleChild ->
|
||||||
|
roleChild.childDictList?.forEach { role ->
|
||||||
|
role.select = role.code == roleCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val characterCode = character?.characterCode
|
||||||
|
val findCharacter = characterDictList.find { character -> character.code == characterCode }
|
||||||
|
(findCharacter ?: characterDictList.firstOrNull())?.select = true
|
||||||
|
|
||||||
|
val tagCode = character?.tagCode
|
||||||
|
if (tagCode == null) {
|
||||||
|
characterDictList.firstOrNull()?.childDictList?.firstOrNull()?.select = true
|
||||||
|
} else {
|
||||||
|
characterDictList.forEach { characterChild ->
|
||||||
|
characterChild.childDictList?.forEach { tag ->
|
||||||
|
tag.select = tag.code == tagCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class AIDictItem(
|
||||||
|
val code: String,
|
||||||
|
val name: String,
|
||||||
|
val url: String?,
|
||||||
|
val prompt: String?,
|
||||||
|
val childDictList: List<AIDictItem>?,
|
||||||
|
var select: Boolean = false
|
||||||
|
) : Parcelable {
|
||||||
|
@IgnoredOnParcel
|
||||||
|
@DrawableRes
|
||||||
|
var filterIcon: Int? = null
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
@DrawableRes
|
||||||
|
var selectBackgroundRes: Int? = null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.remax.visualnovel.entity.response.base.BaseVoice
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/7/21
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class AIVoice(
|
||||||
|
val type: Int, // 1 男 2 女
|
||||||
|
var select: Boolean = false,
|
||||||
|
var name: String?,
|
||||||
|
val description: String?,
|
||||||
|
val voiceType: String?,
|
||||||
|
val supportEmotions: String?,
|
||||||
|
val voiceText: String?,
|
||||||
|
var url: String?,
|
||||||
|
var filePath: String?,
|
||||||
|
val code: String?,
|
||||||
|
val pitchRate: Int?,
|
||||||
|
val speechRate: Int?
|
||||||
|
) : Parcelable, BaseVoice() {
|
||||||
|
|
||||||
|
override fun filePathName(): String {
|
||||||
|
return filePath ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun id(): String {
|
||||||
|
return name ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun url(): String {
|
||||||
|
return url ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.remax.visualnovel.constant.LockTypeConstant
|
||||||
|
import com.remax.visualnovel.entity.model.base.BasePhoto
|
||||||
|
import com.remax.visualnovel.manager.login.LoginManager
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/7/14
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class Album(
|
||||||
|
val albumId: Long,
|
||||||
|
var userId: String?,
|
||||||
|
var aiId: String?,
|
||||||
|
var imgUrl: String?,
|
||||||
|
var img1: String?,
|
||||||
|
var img2: String?,
|
||||||
|
var img3: String?,
|
||||||
|
var likedStatus: String?,
|
||||||
|
var likedCount: Long,
|
||||||
|
var width: Long,
|
||||||
|
var height: Long,
|
||||||
|
var unlockPrice: Long?,
|
||||||
|
val imgOrder: Int,
|
||||||
|
var lockStatus: String?,
|
||||||
|
var isDefault: Boolean?
|
||||||
|
) : BasePhoto(), Parcelable {
|
||||||
|
|
||||||
|
fun isLike() = LIKED == likedStatus
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该图片是否解锁
|
||||||
|
*/
|
||||||
|
fun isUnLock() = LoginManager.isMyself(userId) || LockTypeConstant.isUnLock(lockStatus)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该图片是否上锁
|
||||||
|
*/
|
||||||
|
fun isOpen() = (unlockPrice ?: 0L) <= 0L
|
||||||
|
|
||||||
|
override fun paramId(): Long = albumId
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CANCELED: String = "CANCELED"
|
||||||
|
const val LIKED: String = "LIKED"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.remax.visualnovel.repository.api
|
||||||
|
|
||||||
|
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 bookService: BookService) : BaseRepository() {
|
||||||
|
suspend fun getBooks(): Response<Book> {
|
||||||
|
return bookService.getBooks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.remax.visualnovel.ui.main.actor
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import com.alibaba.android.arouter.facade.annotation.Route
|
||||||
|
import com.alibaba.android.arouter.launcher.ARouter
|
||||||
|
import com.dylanc.loadingstateview.BgColorType
|
||||||
|
import com.remax.visualnovel.app.base.BaseBindingFragment
|
||||||
|
import com.remax.visualnovel.databinding.FragmentMainActorBinding
|
||||||
|
import com.remax.visualnovel.databinding.FragmentMainBookBinding
|
||||||
|
import com.remax.visualnovel.ui.main.book.BookListViewModel
|
||||||
|
import com.remax.visualnovel.utils.Routers
|
||||||
|
import com.remax.visualnovel.widget.custom.TagItem
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlin.getValue
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
@Route(path = Routers.ROUTE_FRAG_ACTORLIST)
|
||||||
|
class ActorListFragment : BaseBindingFragment<FragmentMainActorBinding>() {
|
||||||
|
|
||||||
|
private val contactViewModel by viewModels<ActorListViewModel>()
|
||||||
|
|
||||||
|
override fun onCreated(bundle: Bundle?) {
|
||||||
|
setUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun backgroundColorType(): BgColorType {
|
||||||
|
return BgColorType.TRANSPARENT
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUI() {
|
||||||
|
initTagLayout()
|
||||||
|
|
||||||
|
with(binding) {
|
||||||
|
tagFlowLayout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initTagLayout() {
|
||||||
|
with(binding) {
|
||||||
|
// 模拟数据
|
||||||
|
val tags = listOf(
|
||||||
|
TagItem("1", "Youth"),
|
||||||
|
TagItem("2", "Lolita"),
|
||||||
|
TagItem("2", "Lolita2"),
|
||||||
|
TagItem("3", "Overbearing CEO ABCDEFG Overbearing CEO ABCDEFG Overbearing CEO ABCDEFG"),
|
||||||
|
TagItem("3", "ggggggggggg CEO ABCDEFG Overbearing CEO ABCDEFG Overbearing CEO ABCDEFG"),
|
||||||
|
TagItem("4", "Uncle"),
|
||||||
|
TagItem("5", "Character Status"),
|
||||||
|
TagItem("6", "Imouto"),
|
||||||
|
TagItem("7", "Fanwork"),
|
||||||
|
TagItem("8", "LastLine"),
|
||||||
|
)
|
||||||
|
|
||||||
|
tagFlowLayout.setTags(tags)
|
||||||
|
|
||||||
|
tagFlowLayout.setOnTagClickListener { tag ->
|
||||||
|
Toast.makeText(context, "Clicked: ${tag.text}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
tagFlowLayout.setOnExpandStateChangeListener { isExpanded ->
|
||||||
|
Log.d("TagFlowLayout", "Expand state: $isExpanded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(): ActorListFragment {
|
||||||
|
return ARouter.getInstance().build(Routers.ROUTE_FRAG_ACTORLIST)
|
||||||
|
.navigation() as ActorListFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
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 com.remax.visualnovel.repository.api.BooksRepository
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="300">
|
||||||
|
|
||||||
|
<translate
|
||||||
|
android:fromYDelta="100%"
|
||||||
|
android:toYDelta="0%" />
|
||||||
|
|
||||||
|
</set>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="300">
|
||||||
|
|
||||||
|
<translate
|
||||||
|
android:fromYDelta="0%"
|
||||||
|
android:toYDelta="100%" />
|
||||||
|
|
||||||
|
</set>
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
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="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!--<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/topBg"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:src="@mipmap/icon_main_top_bg"
|
||||||
|
app:layout_constraintDimensionRatio="h,786:400"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />-->
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/viewPager2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/bottomLayout"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.remax.visualnovel.widget.uitoken.view.UITokenConstraintLayout
|
||||||
|
android:id="@+id/bottomLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="74dp"
|
||||||
|
app:backgroundColorToken="@string/color_surface_district_normal"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:topLeftRadiusToken="@string/radius_xxl"
|
||||||
|
app:topRightRadiusToken="@string/radius_xxl"
|
||||||
|
>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/bookItem"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@mipmap/main_tab_book_on"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/mangaItem"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/mangaItem"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@mipmap/main_tab_manga_off"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/actorItem"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/bookItem"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/actorItem"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@mipmap/main_tab_actor_off"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/historyItem"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/mangaItem"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/historyItem"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@mipmap/main_tab_history_off"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/actorItem"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
</com.remax.visualnovel.widget.uitoken.view.UITokenConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<manifest />
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020. Dylan Cai
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.dylanc.viewbinding.base
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.view.View
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
|
||||||
|
interface ActivityBinding<out VB : ViewBinding> {
|
||||||
|
val binding: VB
|
||||||
|
fun Activity.setContentViewWithBinding(): View
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActivityBindingDelegate<VB : ViewBinding> : ActivityBinding<VB> {
|
||||||
|
private lateinit var _binding: VB
|
||||||
|
|
||||||
|
override val binding: VB get() = _binding
|
||||||
|
|
||||||
|
override fun Activity.setContentViewWithBinding(): View {
|
||||||
|
_binding = ViewBindingUtil.inflateWithGeneric(this, layoutInflater)
|
||||||
|
setContentView(_binding.root)
|
||||||
|
return _binding.root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020. Dylan Cai
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.dylanc.viewbinding.nonreflection
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.databinding.ViewDataBinding
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
|
|
||||||
|
fun <VB : ViewBinding> ComponentActivity.binding(inflate: (LayoutInflater) -> VB, setContentView: Boolean = true) = lazy(NONE) {
|
||||||
|
inflate(layoutInflater).also { binding ->
|
||||||
|
if (setContentView) setContentView(binding.root)
|
||||||
|
if (binding is ViewDataBinding) binding.lifecycleOwner = this
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue