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