init project uploading

This commit is contained in:
renhaoting 2025-10-21 11:43:55 +08:00
parent f854b33aba
commit b535a7667d
22 changed files with 945 additions and 0 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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 ?: ""
}
}

View File

@ -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"
}
}

View File

@ -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()
}
}

View File

@ -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
}
}
}

View File

@ -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())
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
>
</manifest>

View File

@ -0,0 +1 @@
<manifest />

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
>
</manifest>

View File

@ -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
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
>
</manifest>

View File

@ -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
}
}