Compare commits
17 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
db2f338acd | |
|
|
c9d15ccc99 | |
|
|
2026845237 | |
|
|
e120975f26 | |
|
|
89eb7a13fa | |
|
|
7459af2f0a | |
|
|
636375fe15 | |
|
|
d721fa025c | |
|
|
6971784f98 | |
|
|
c0480c12e6 | |
|
|
817e7bf467 | |
|
|
821f00386e | |
|
|
e65ed84e03 | |
|
|
cdc13082de | |
|
|
2426f6b8bd | |
|
|
bb87a3d138 | |
|
|
b535a7667d |
|
|
@ -0,0 +1,17 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
*/build/
|
||||
**/build/
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
|
|
@ -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,123 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<compositeConfiguration>
|
||||
<compositeBuild compositeDefinitionSource="SCRIPT">
|
||||
<builds>
|
||||
<build path="$PROJECT_DIR$/buildSrc" name="buildSrc">
|
||||
<projects>
|
||||
<project path="$PROJECT_DIR$/buildSrc" />
|
||||
</projects>
|
||||
</build>
|
||||
</builds>
|
||||
</compositeBuild>
|
||||
</compositeConfiguration>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/buildSrc" />
|
||||
<option value="$PROJECT_DIR$/loadingstateview" />
|
||||
<option value="$PROJECT_DIR$/loadingstateview-ktx" />
|
||||
<option value="$PROJECT_DIR$/viewbinding-base" />
|
||||
<option value="$PROJECT_DIR$/viewbinding-nonreflection-ktx" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.0.21" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,310 @@
|
|||
import com.android.build.api.dsl.ApplicationProductFlavor
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
id("kotlin-parcelize")
|
||||
kotlin("kapt")
|
||||
id("dagger.hilt.android.plugin")
|
||||
//TODO - enable later: id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = Version.applicationId
|
||||
compileSdk = 36
|
||||
|
||||
|
||||
defaultConfig {
|
||||
applicationId = Version.applicationId
|
||||
minSdk = Version.minSdk
|
||||
targetSdk = Version.targetSdk
|
||||
compileSdk = Version.targetSdk
|
||||
versionCode = Version.versionCode
|
||||
versionName = Version.versionName
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
setProperty("archivesBaseName", "${Version.applicationId}-build${Version.versionCode}")
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
}
|
||||
renderscriptTargetApi = 23
|
||||
renderscriptSupportModeEnabled = true
|
||||
}
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
arguments {
|
||||
arg("AROUTER_MODULE_NAME", project.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
hilt {
|
||||
enableExperimentalClasspathAggregation = true
|
||||
enableAggregatingTask = false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
val storeFileName = "storeFile"
|
||||
val storePasswordName = "KEYSTORE_PWD"
|
||||
val keyAliasName = "KEY_ALIAS"
|
||||
val keyPasswordName = "KEY_PWD"
|
||||
|
||||
fun names(suffix: String = "") =
|
||||
listOf(storeFileName + suffix, storePasswordName + suffix, keyAliasName + suffix, keyPasswordName + suffix)
|
||||
|
||||
create("release") {
|
||||
storeFile = file(project.properties[names()[0]]?.toString() ?: "")
|
||||
storePassword = project.properties[names()[1]] as? String?
|
||||
keyAlias = project.properties[names()[2]] as? String?
|
||||
keyPassword = project.properties[names()[3]] as? String?
|
||||
}
|
||||
getByName("debug") {
|
||||
storeFile = file(project.properties[names()[0]]?.toString() ?: "")
|
||||
storePassword = project.properties[names()[1]] as? String?
|
||||
keyAlias = project.properties[names()[2]] as? String?
|
||||
keyPassword = project.properties[names()[3]] as? String?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
targetCompatibility(JavaVersion.VERSION_17)
|
||||
sourceCompatibility(JavaVersion.VERSION_17)
|
||||
}
|
||||
|
||||
|
||||
|
||||
val flavorDimensionName = "VisualNovel"
|
||||
flavorDimensions.add(flavorDimensionName)
|
||||
productFlavors {
|
||||
fun ApplicationProductFlavor.buildConfigString(name: String, value: String) =
|
||||
buildConfigField("String", name, "\"$value\"")
|
||||
|
||||
fun ApplicationProductFlavor.buildConfigBoolean(name: String, value: String) =
|
||||
buildConfigField("Boolean", name, value)
|
||||
|
||||
create("novelTest") {
|
||||
dimension = flavorDimensionName
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
|
||||
buildConfigString("HOST", "https://www.xxxxx.ai/")
|
||||
buildConfigString("ABOUT_US", "https://www.xxxxx.ai/about")
|
||||
buildConfigString("API_FROG", "https://www.test-frog.xxxxx.ai")
|
||||
buildConfigString("EPAL_TERMS_SERVICES", "https://www.xxxxx.ai/policy/tos")
|
||||
buildConfigString("API_SHARK", "https://test-shark.xxxxx.ai")
|
||||
buildConfigString("API_COW", "https://test-cow.xxxxx.ai")
|
||||
buildConfigString("API_PIGEON", "https://test-pigeon.xxxx.ai")
|
||||
buildConfigString("API_LION", "https://test-lion.xxxx.ai")
|
||||
buildConfigString("RECHAEGE_SERVICES", "https://test.xxxxx.ai/policy/recharge")
|
||||
|
||||
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
|
||||
}
|
||||
|
||||
|
||||
create("product") {
|
||||
dimension = flavorDimensionName
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
|
||||
buildConfigString("HOST", "https://test.xxxxx.ai/")
|
||||
buildConfigString("ABOUT_US", "https://test.xxxxx.ai/about")
|
||||
buildConfigString("API_FROG", "https://test-frog.xxxxx.ai")
|
||||
buildConfigString("EPAL_TERMS_SERVICES", "https://test.xxxxx.ai/policy/tos")
|
||||
buildConfigString("API_SHARK", "https://test-shark.xxxxx.ai")
|
||||
buildConfigString("API_COW", "https://test-cow.xxxxx.ai")
|
||||
buildConfigString("API_PIGEON", "https://test-pigeon.xxxx.ai")
|
||||
buildConfigString("API_LION", "https://test-lion.xxxx.ai")
|
||||
buildConfigString("RECHAEGE_SERVICES", "https://test.xxxxx.ai/policy/recharge")
|
||||
|
||||
buildConfigString("RTC_APP_ID", "689ade491323ae01797818e0-XXX-TODO")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree("dir" to "libs", "include" to listOf("*.jar", "*.aar")))
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||
|
||||
coreLibraryDesugaring(Deps.desugar)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
||||
|
||||
|
||||
|
||||
//dagger hilt
|
||||
implementation(Deps.hilt)
|
||||
kapt(Deps.hiltAndroidCompiler)
|
||||
kapt(Deps.hiltCompiler)
|
||||
|
||||
//Retrofit2
|
||||
implementation(Deps.retrofit2)
|
||||
implementation(Deps.retrofitAdapterRxjava2)
|
||||
implementation(Deps.retrofit2ConverterGson)
|
||||
implementation(Deps.okhttp3LoggingInterceptor)
|
||||
|
||||
|
||||
// timber used for: recording logs
|
||||
implementation(Deps.timber)
|
||||
|
||||
|
||||
// tecent mmkv
|
||||
implementation(Deps.mmkv)
|
||||
implementation(Deps.material)
|
||||
|
||||
// gson
|
||||
implementation(Deps.gson)
|
||||
|
||||
// viewpager2
|
||||
implementation(Deps.viewpager2)
|
||||
|
||||
// for multiple dex support
|
||||
implementation(Deps.multidex)
|
||||
|
||||
|
||||
//google firebase
|
||||
implementation(platform(Deps.firebaseBom))
|
||||
implementation(Deps.firebaseMessageKtx)
|
||||
implementation(Deps.firebaseAnalyticsKtx)
|
||||
implementation(Deps.firebaseCrashlyticsKtx)
|
||||
implementation(Deps.firebaseAuthKtx)
|
||||
implementation(Deps.credentials)
|
||||
implementation(Deps.credentialsAuth)
|
||||
implementation(Deps.googleId)
|
||||
|
||||
|
||||
// coroutine support
|
||||
implementation(Deps.kotlinCoroutinesCore)
|
||||
implementation(Deps.kotlinCoroutinesAndroid)
|
||||
|
||||
implementation(Deps.appcompat)
|
||||
implementation(Deps.constraintlayout)
|
||||
implementation(Deps.flexbox)
|
||||
implementation(Deps.coreKtx)
|
||||
implementation(Deps.activityKtx)
|
||||
implementation(Deps.activityCompose)
|
||||
implementation(Deps.fragmentKtx)
|
||||
|
||||
// vm and lifecycle
|
||||
implementation(Deps.viewModel)
|
||||
implementation(Deps.livedata)
|
||||
implementation(Deps.lifecycleJava8)
|
||||
implementation(Deps.lifecycleRuntime)
|
||||
implementation(Deps.datastore)
|
||||
|
||||
|
||||
//glide
|
||||
implementation(Deps.glide)
|
||||
kapt(Deps.glideCompiler)
|
||||
implementation(Deps.glideTransformations)
|
||||
implementation(Deps.glideWebpdecoder)
|
||||
|
||||
// eventbus
|
||||
implementation(Deps.modularEventbus)
|
||||
kapt(Deps.modularEventbusCompiler)
|
||||
|
||||
// indicator
|
||||
implementation(Deps.magicIndicator)
|
||||
|
||||
// BlurView and Luban
|
||||
implementation(Deps.Luban)
|
||||
implementation(Deps.BlurView)
|
||||
|
||||
//ali aRouter
|
||||
implementation(Deps.arouter)
|
||||
kapt(Deps.arouterCompiler)
|
||||
|
||||
|
||||
//Permission
|
||||
implementation(Deps.permission)
|
||||
// lottie
|
||||
implementation(Deps.lottie)
|
||||
// float window
|
||||
implementation(Deps.easyFloat)
|
||||
// guide
|
||||
implementation(Deps.newbieGuide)
|
||||
// refreshLayout
|
||||
implementation(Deps.refreshLayout)
|
||||
// apng
|
||||
implementation(Deps.apng)
|
||||
// softKey board
|
||||
implementation(Deps.skbGlobal)
|
||||
|
||||
|
||||
// baseRecyclerAdapter
|
||||
implementation(Deps.brvah)
|
||||
implementation(Deps.swipeMenuLayout)
|
||||
implementation(Deps.BRV)
|
||||
|
||||
//banner
|
||||
implementation(Deps.banner)
|
||||
|
||||
// spannable
|
||||
implementation(Deps.spannablex)
|
||||
|
||||
// Media related libs
|
||||
implementation(Deps.mp3Recorder)
|
||||
implementation(Deps.photoView)
|
||||
implementation(Deps.transition)
|
||||
implementation(Deps.paging)
|
||||
implementation(Deps.exoplayer)
|
||||
implementation(Deps.subsamplingScaleImageView)
|
||||
|
||||
//s3图片上传 oss
|
||||
implementation(Deps.awsS3)
|
||||
implementation(Deps.awsCore)
|
||||
|
||||
// 网易 云信
|
||||
implementation(Deps.nimBase)
|
||||
implementation(Deps.nimPush)
|
||||
|
||||
//内购 / 充值
|
||||
implementation(Deps.billing)
|
||||
|
||||
// RTC : 实时通信
|
||||
implementation(Deps.BytePlusRTC)
|
||||
|
||||
|
||||
implementation(project(mapOf("path" to ":loadingstateview")))
|
||||
implementation(project(mapOf("path" to ":loadingstateview-ktx")))
|
||||
implementation(project(mapOf("path" to ":viewbinding-base")))
|
||||
implementation(project(mapOf("path" to ":viewbinding-nonreflection-ktx")))
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.remax.visualnovel
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.remax.visualnovel", appContext.packageName)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="com.android.providers.media.MediaProvider" />
|
||||
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.FLASHLIGHT" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
|
||||
<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.splash.SplashActivity"
|
||||
android:theme="@style/AppTheme.Launcher"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.MainActivity"
|
||||
android:exported="false" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.search.SearchActivity"
|
||||
android:exported="false" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.discussion.DiscussionActivity"
|
||||
android:exported="false" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.checkin.CheckInActivity"
|
||||
android:exported="false" >
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.chat.ChatActivity"
|
||||
android:exported="false" >
|
||||
</activity>
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,202 @@
|
|||
{
|
||||
"color.context.vip.normal": "@GRA:$glo.deg.ltr,$glo.color.red.20,$glo.color.violet.20,$glo.color.mint.20",
|
||||
"glo.color.orange.10": "$glo.color.orange.10",
|
||||
|
||||
|
||||
"color.primary.normal": "$glo.color.magenta.50",
|
||||
"color.primary.hover": "$glo.color.magenta.40",
|
||||
"color.primary.press": "$glo.color.magenta.60",
|
||||
"color.primary.disabled": "$color.surface.nest.disabled",
|
||||
"color.primary.variant.normal": "$glo.color.magenta.40",
|
||||
"color.primary.variant.hover": "$glo.color.magenta.30",
|
||||
"color.primary.variant.press": "$glo.color.magenta.50",
|
||||
"color.primary.variant.disabled": "$color.surface.nest.disabled",
|
||||
"color.secondary.gradient.normal": "@GRA:$glo.deg.ltr,$glo.color.red.20,$glo.color.violet.20,$glo.color.mint.20",
|
||||
"color.primary.gradient.normal": "@GRA:$glo.deg.ltr,$glo.color.magenta.30,$glo.color.purple.40",
|
||||
"color.primary.gradient.hover": "@GRA:$glo.deg.ltr,$glo.color.magenta.20,$glo.color.purple.30",
|
||||
"color.primary.gradient.press": "@GRA:$glo.deg.ltr,$glo.color.magenta.40,$glo.color.purple.50",
|
||||
"color.primary.gradient.disabled": "$color.surface.nest.disabled",
|
||||
"color.primary.onpic.normal": "$glo.color.violet.40,$glo.transparent.t85",
|
||||
"color.primary.onpic.hover": "$glo.color.violet.30,$glo.transparent.t85",
|
||||
"color.primary.onpic.press": "$glo.color.violet.50,$glo.transparent.t85",
|
||||
"color.important.normal": "$glo.color.red.50",
|
||||
"color.important.hover": "$glo.color.red.40",
|
||||
"color.important.press": "$glo.color.red.60",
|
||||
"color.important.disabled": "$color.surface.nest.disabled",
|
||||
"color.important.variant.normal": "$glo.color.red.40",
|
||||
"color.important.variant.hover": "$glo.color.red.30",
|
||||
"color.important.variant.press": "$glo.color.red.50",
|
||||
"color.important.variant.disabled": "$glo.color.blue.10,$glo.transparent.t25",
|
||||
"color.important.gradient.normal": "@GRA:$glo.deg.ltr,$glo.color.orange.50,$glo.color.red.50",
|
||||
"color.important.gradient.hover": "@GRA:$glo.deg.ltr,$glo.color.orange.40,$glo.color.red.40",
|
||||
"color.important.gradient.press": "@GRA:$glo.deg.ltr,$glo.color.orange.60,$glo.color.red.60",
|
||||
"color.important.gradient.disabled": "$color.surface.nest.disabled",
|
||||
"color.important.onpic.normal": "$glo.color.red.50,$glo.transparent.t85",
|
||||
"color.positive.normal": "$glo.color.mint.60",
|
||||
"color.positive.hover": "$glo.color.mint.50",
|
||||
"color.positive.press": "$glo.color.mint.70",
|
||||
"color.positive.disabled": "$color.surface.nest.disabled",
|
||||
"color.positive.variant.normal": "$glo.color.mint.40",
|
||||
"color.positive.variant.hover": "$glo.color.mint.30",
|
||||
"color.positive.variant.press": "$glo.color.mint.50",
|
||||
"color.positive.variant.disabled": "$glo.color.blue.10,$glo.transparent.t25",
|
||||
"color.positive.gradient.normal": "@GRA:$glo.deg.ltr,$glo.color.green.40,$glo.color.mint.60",
|
||||
"color.positive.gradient.hover": "$glo.border.1",
|
||||
"color.positive.gradient.press": "@GRA:$glo.deg.ltr,$glo.color.green.60,$glo.color.mint.70",
|
||||
"color.positive.gradient.disabled": "$color.surface.nest.disabled",
|
||||
"color.positive.onpic.normal": "$glo.color.mint.60,$glo.transparent.t85",
|
||||
"color.warning.normal": "$glo.color.orange.50",
|
||||
"color.warning.hover": "$glo.color.orange.40",
|
||||
"color.warning.press": "$glo.color.orange.60",
|
||||
"color.warning.disabled": "$color.surface.nest.disabled",
|
||||
"color.warning.variant.normal": "$glo.color.orange.40",
|
||||
"color.warning.variant.hover": "$glo.color.orange.30",
|
||||
"color.warning.variant.press": "$glo.color.orange.50",
|
||||
"color.warning.variant.disabled": "$glo.color.blue.10,$glo.transparent.t25",
|
||||
"color.warning.gradient.normal": "@GRA:$glo.deg.ltr,$glo.color.yellow.40,$glo.color.orange.50",
|
||||
"color.warning.gradient.hover": "@GRA:$glo.deg.ltr,$glo.color.yellow.30,$glo.color.orange.40",
|
||||
"color.warning.gradient.press": "@GRA:$glo.deg.ltr,$glo.color.yellow.50,$glo.color.orange.60",
|
||||
"color.warning.gradient.disabled": "$color.surface.nest.disabled",
|
||||
"color.warning.onpic.normal": "$glo.color.orange.50,$glo.transparent.t85",
|
||||
"color.emphasis.normal": "$glo.color.blue.40",
|
||||
"color.emphasis.hover": "$glo.color.blue.30",
|
||||
"color.emphasis.press": "$glo.color.blue.50",
|
||||
"color.emphasis.disabled": "$color.surface.nest.disabled",
|
||||
"color.emphasis.variant.normal": "$glo.color.blue.30",
|
||||
"color.emphasis.variant.hover": "$glo.color.blue.20",
|
||||
"color.emphasis.variant.press": "$glo.color.blue.40",
|
||||
"color.emphasis.variant.disabled": "$glo.color.blue.10,$glo.transparent.t25",
|
||||
"color.emphasis.grandient.normal": "@GRA:$glo.deg.ltr,$glo.color.sky.30,$glo.color.blue.40",
|
||||
"color.emphasis.grandient.hover": "@GRA:$glo.deg.ltr,$glo.color.sky.20,$glo.color.blue.30",
|
||||
"color.emphasis.grandient.press": "@GRA:$glo.deg.ltr,$glo.color.sky.40,$glo.color.blue.50",
|
||||
"color.emphasis.grandient.disabled": "$color.surface.nest.disabled",
|
||||
"color.emphasis.onpic.normal": "$glo.color.blue.40,$glo.transparent.t85",
|
||||
"color.background.default": "$glo.color.grey.100",
|
||||
"color.background.specialmap": "$glo.color.grey.100",
|
||||
"color.background.district": "$glo.color.black,$glo.transparent.t30",
|
||||
"color.surface.base.normal": "$glo.color.grey.80",
|
||||
"color.surface.base.hover": "$glo.color.grey.70",
|
||||
"color.surface.base.press": "$glo.color.grey.90",
|
||||
"color.surface.base.disabled": "$glo.color.grey.90",
|
||||
"color.surface.base.specialmap.normal": "$glo.color.grey.80",
|
||||
"color.surface.base.specialmap.hover": "$glo.color.grey.70",
|
||||
"color.surface.base.specialmap.press": "$glo.color.grey.90,$glo.transparent.t30",
|
||||
"color.surface.base.specialmap.disabled": "$glo.color.white,$glo.transparent.t8",
|
||||
"color.surface.float.normal": "$glo.color.grey.70",
|
||||
"color.surface.float.hover": "$glo.color.grey.60",
|
||||
"color.surface.float.press": "$glo.color.grey.80",
|
||||
"color.surface.float.disabled": "$glo.color.grey.80",
|
||||
"color.surface.top.normal": "$glo.color.black,$glo.transparent.t65",
|
||||
"color.surface.top.hover": "$glo.color.black,$glo.transparent.t45",
|
||||
"color.surface.top.press": "$glo.color.black,$glo.transparent.t85",
|
||||
"color.surface.top.disabled": "$glo.color.black,$glo.transparent.t30",
|
||||
"color.surface.district.normal": "$glo.color.purple.0,$glo.transparent.t4",
|
||||
"color.surface.district.hover": "$glo.color.purple.0,$glo.transparent.t12",
|
||||
"color.surface.district.press": "$glo.color.black,$glo.transparent.t25",
|
||||
"color.surface.district.disabled": "$glo.color.black,$glo.transparent.t25",
|
||||
"color.surface.nest.normal": "$glo.color.purple.0,$glo.transparent.t8",
|
||||
"color.surface.nest.hover": "$glo.color.purple.0,$glo.transparent.t12",
|
||||
"color.surface.nest.press": "$glo.color.purple.0,$glo.transparent.t4",
|
||||
"color.surface.nest.disabled": "$glo.color.purple.0,$glo.transparent.t4",
|
||||
"color.surface.element.normal": "$color.surface.nest.normal",
|
||||
"color.surface.element.hover": "$color.surface.nest.hover",
|
||||
"color.surface.element.press": "$color.surface.nest.press",
|
||||
"color.surface.element.disabled": "$color.surface.nest.disabled",
|
||||
"color.surface.element.dark.normal": "$glo.color.black,$glo.transparent.t65",
|
||||
"color.surface.element.dark.hover": "$glo.color.black,$glo.transparent.t45",
|
||||
"color.surface.element.dark.press": "$glo.color.black,$glo.transparent.t85",
|
||||
"color.surface.element.dark.disabled": "$glo.color.black,$glo.transparent.t45",
|
||||
"color.surface.element.light.normal": "$glo.color.white,$glo.transparent.t15",
|
||||
"color.surface.element.light.hover": "$glo.color.white,$glo.transparent.t25",
|
||||
"color.surface.element.light.press": "$glo.color.white,$glo.transparent.t8",
|
||||
"color.surface.element.light.disabled": "$glo.color.white,$glo.transparent.t8",
|
||||
"color.surface.white.normal": "$glo.color.white",
|
||||
"color.surface.white.hover": "$glo.color.white,$glo.transparent.t85",
|
||||
"color.surface.white.press": "$glo.color.white,$glo.transparent.t65",
|
||||
"color.surface.white.disabled": "$glo.color.white,$glo.transparent.t45",
|
||||
"color.outline.normal": "$glo.color.purple.0,$glo.transparent.t20",
|
||||
"color.outline.hover": "$glo.color.purple.0,$glo.transparent.t30",
|
||||
"color.outline.press": "$glo.color.purple.0,$glo.transparent.t8",
|
||||
"color.outline.disabled": "$color.surface.element.disabled",
|
||||
"color.overlay.primary": "$glo.color.violet.30,$glo.transparent.t30",
|
||||
"color.overlay.gradient": "@GRA:$glo.deg.ttb,$glo.color.black&$glo.transparent.t0,$glo.color.black&$glo.transparent.t100",
|
||||
"color.overlay.dark": "$glo.color.black,$glo.transparent.t65",
|
||||
"color.overlay.base": "@GRA:$glo.deg.ttb,$glo.color.grey.80&$glo.transparent.t0,$glo.color.grey.80&$glo.transparent.t100",
|
||||
"color.context.subscribe.normal": "@GRA:$glo.deg.ltr,$glo.color.purple.50,$glo.color.violet.50",
|
||||
"color.context.subscribe.hover": "@GRA:$glo.deg.ltr,$glo.color.purple.30,$glo.color.violet.30",
|
||||
"color.context.subscribe.press": "@GRA:$glo.deg.ltr,$glo.color.purple.70,$glo.color.violet.70",
|
||||
"color.context.subscribe.disabled": "$color.surface.nest.disabled",
|
||||
"color.context.legends.normal": "@GRA:$glo.deg.ltr,$glo.color.yellow.20,$glo.color.yellow.60",
|
||||
"color.context.legends.hover": "@GRA:$glo.deg.ltr,$glo.color.yellow.10,$glo.color.yellow.40",
|
||||
"color.context.legends.press": "@GRA:$glo.deg.ltr,$glo.color.yellow.60,$glo.color.yellow.90",
|
||||
"color.context.legends.disabled": "$color.surface.nest.disabled",
|
||||
"color.context.legends.variant.normal": "$glo.color.yellow.20",
|
||||
"color.context.legends.variant.hover": "$glo.color.yellow.10",
|
||||
"color.context.legends.variant.press": "$glo.color.yellow.40",
|
||||
"color.context.legends.variant.disabled": "$color.surface.nest.disabled",
|
||||
"color.txt.primary.normal": "$glo.color.white",
|
||||
"color.txt.primary.hover": "$glo.color.magenta.30",
|
||||
"color.txt.primary.press": "$glo.color.magenta.40",
|
||||
"color.txt.primary.disabled": "$color.txt.disabled",
|
||||
"color.txt.primary.specialmap.normal": "$glo.color.white",
|
||||
"color.txt.primary.specialmap.hover": "$glo.color.white,$glo.transparent.t85",
|
||||
"color.txt.primary.specialmap.press": "$glo.color.white,$glo.transparent.t65",
|
||||
"color.txt.primary.specialmap.disabled": "$glo.color.white,$glo.transparent.t45",
|
||||
"color.txt.secondary.normal": "$glo.color.grey.30",
|
||||
"color.txt.secondary.hover": "$glo.color.magenta.30",
|
||||
"color.txt.secondary.press": "$glo.color.magenta.40",
|
||||
"color.txt.secondary.disabled": "$glo.color.grey.50",
|
||||
"color.txt.tertiary.normal": "$glo.color.grey.40",
|
||||
"color.txt.tertiary.hover": "$glo.color.grey.30",
|
||||
"color.txt.tertiary": "$glo.color.grey.50",
|
||||
"color.txt.tertiary.disabled": "$color.txt.disabled",
|
||||
"color.txt.grass": "$glo.color.grass.40",
|
||||
"color.txt.disabled": "$glo.color.grey.50",
|
||||
"txt.display.xl": "$glo.font.family.display,$glo.font.size.64,$glo.font.weight.regular,$glo.font.lineheight.size64",
|
||||
"txt.display.l": "$glo.font.family.display,$glo.font.size.36,$glo.font.weight.regular,$glo.font.lineheight.size36",
|
||||
"txt.display.m": "$glo.font.family.display,$glo.font.size.24,$glo.font.weight.regular,$glo.font.lineheight.size24",
|
||||
"txt.display.s": "$glo.font.family.display,$glo.font.size.16,$glo.font.weight.regular,$glo.font.lineheight.size16",
|
||||
"txt.headline.l": "$glo.font.family.sys,$glo.font.size.36,$glo.font.weight.bold,$glo.font.lineheight.size36",
|
||||
"txt.headline.m": "$glo.font.family.sys,$glo.font.size.24,$glo.font.weight.bold,$glo.font.lineheight.size24",
|
||||
"txt.headline.s": "$glo.font.family.sys,$glo.font.size.20,$glo.font.weight.bold,$glo.font.lineheight.size20",
|
||||
"txt.title.l": "$glo.font.family.sys,$glo.font.size.20,$glo.font.weight.semibold,$glo.font.lineheight.size20",
|
||||
"txt.title.m": "$glo.font.family.sys,$glo.font.size.18,$glo.font.weight.semibold,$glo.font.lineheight.size18",
|
||||
"txt.title.s": "$glo.font.family.sys,$glo.font.size.16,$glo.font.weight.semibold,$glo.font.lineheight.size16",
|
||||
"txt.bodySemibold.l": "$glo.font.family.sys,$glo.font.size.16,$glo.font.weight.semibold,$glo.font.lineheight.size16",
|
||||
"txt.bodySemibold.m": "$glo.font.family.sys,$glo.font.size.14,$glo.font.weight.semibold,$glo.font.lineheight.size14",
|
||||
"txt.bodySemibold.s": "$glo.font.family.sys,$glo.font.size.12,$glo.font.weight.semibold,$glo.font.lineheight.size12",
|
||||
"txt.body.l": "$glo.font.family.sys,$glo.font.size.16,$glo.font.weight.regular,$glo.font.lineheight.size16",
|
||||
"txt.body.m": "$glo.font.family.sys,$glo.font.size.14,$glo.font.weight.regular,$glo.font.lineheight.size14",
|
||||
"txt.body.s": "$glo.font.family.sys,$glo.font.size.12,$glo.font.weight.regular,$glo.font.lineheight.size12",
|
||||
"txt.label.l": "$glo.font.family.sys,$glo.font.size.16,$glo.font.weight.medium,$glo.font.lineheight.size16",
|
||||
"txt.label.m": "$glo.font.family.sys,$glo.font.size.14,$glo.font.weight.medium,$glo.font.lineheight.size14",
|
||||
"txt.label.s": "$glo.font.family.sys,$glo.font.size.12,$glo.font.weight.medium,$glo.font.lineheight.size12",
|
||||
"txt.numDisplay.xl": "$glo.font.family.numDisplay,$glo.font.size.48,$glo.font.weight.bold,$glo.font.lineheight.size48",
|
||||
"txt.numDisplay.l": "$glo.font.family.numDisplay,$glo.font.size.36,$glo.font.weight.bold,$glo.font.lineheight.size36",
|
||||
"txt.numDisplay.m": "$glo.font.family.numDisplay,$glo.font.size.24,$glo.font.weight.bold,$glo.font.lineheight.size24",
|
||||
"txt.numDisplay.s": "$glo.font.family.numDisplay,$glo.font.size.20,$glo.font.weight.bold,$glo.font.lineheight.size20",
|
||||
"txt.numMonotype.xl": "$glo.font.family.sys,$glo.font.size.20,$glo.font.weight.bold,$glo.font.lineheight.size20",
|
||||
"txt.numMonotype.l": "$glo.font.family.sys,$glo.font.size.18,$glo.font.weight.bold,$glo.font.lineheight.size18",
|
||||
"txt.numMonotype.m": "$glo.font.family.sys,$glo.font.size.16,$glo.font.weight.bold,$glo.font.lineheight.size16",
|
||||
"txt.numMonotype.s": "$glo.font.family.sys,$glo.font.size.14,$glo.font.weight.medium,$glo.font.lineheight.size14",
|
||||
"txt.numMonotype.xs": "$glo.font.family.sys,$glo.font.size.12,$glo.font.weight.medium,$glo.font.lineheight.size12",
|
||||
"shadow.s": "@SHA:$glo.color.black,$glo.transparent.t15,0&0,4",
|
||||
"shadow.m": "@SHA:$glo.color.black,$glo.transparent.t15,0&0,8",
|
||||
"shadow.l": "@SHA:$glo.color.black,$glo.transparent.t15,0&0,16",
|
||||
"radius.xs": "$glo.radius.4",
|
||||
"radius.s": "$glo.radius.8",
|
||||
"radius.m": "$glo.radius.12",
|
||||
"radius.l": "$glo.radius.16",
|
||||
"radius.xl": "$glo.radius.20",
|
||||
"radius.xxl": "$glo.radius.24",
|
||||
"radius.40": "$glo.radius.40",
|
||||
"radius.42": "$glo.radius.42",
|
||||
"radius.80": "$glo.radius.80",
|
||||
"radius.round": "$glo.radius.round",
|
||||
"radius.pill": "$glo.radius.round",
|
||||
"border.divider": "$glo.border.half",
|
||||
"border.s": "$glo.border.1",
|
||||
"border.m": "$glo.border.2",
|
||||
"border.l": "$glo.border.4"
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.remax.visualnovel.api.factory
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.api.interceptor.GlobalInterceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object ServiceFactory {
|
||||
/**
|
||||
* 受信任的host列表
|
||||
*/
|
||||
private val hostNameList: List<String?> by lazy {
|
||||
listOf(
|
||||
BuildConfig.API_FROG,
|
||||
BuildConfig.HOST
|
||||
).map {
|
||||
it.parseHost()
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parseHost() = this.toUri().host
|
||||
|
||||
private val retrofit: Retrofit by lazy {
|
||||
createRetrofit()
|
||||
}
|
||||
|
||||
private val okHttpClient: OkHttpClient by lazy {
|
||||
createOkHttpClient()
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建OkHttpClient对象
|
||||
*/
|
||||
private fun createOkHttpClient() = OkHttpClient.Builder()
|
||||
.addInterceptor(GlobalInterceptor())
|
||||
.callTimeout(30, TimeUnit.SECONDS)
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.hostnameVerifier { hostname, _ ->
|
||||
hostname in hostNameList
|
||||
}
|
||||
.apply {
|
||||
if (BuildConfig.DEBUG) {
|
||||
val interceptor = HttpLoggingInterceptor()
|
||||
interceptor.level = HttpLoggingInterceptor.Level.BODY
|
||||
addNetworkInterceptor(interceptor)
|
||||
}
|
||||
}.build()
|
||||
|
||||
/**
|
||||
* 构建Retrofit对象
|
||||
*/
|
||||
private fun createRetrofit() = Retrofit.Builder()
|
||||
.baseUrl(BuildConfig.API_FROG)
|
||||
.client(okHttpClient)
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
|
||||
inline fun <reified T> createService(): T = create(T::class.java)
|
||||
|
||||
fun <T> create(clazz: Class<T>): T {
|
||||
return retrofit.create(clazz)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.remax.visualnovel.api.interceptor
|
||||
|
||||
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.constant.AppConstant
|
||||
import com.remax.visualnovel.manager.login.LoginManager
|
||||
import com.remax.visualnovel.utils.AppUtils
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||
import okio.Buffer
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/10/17
|
||||
*/
|
||||
class GlobalInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
var request = chain.request()
|
||||
val bodyStr = readBody(chain.request().body)
|
||||
val emptyBody = "{}".toRequestBody("application/json;charset=utf-8".toMediaType())
|
||||
val requestBody = if (bodyStr.isNotBlank()) request.body else emptyBody
|
||||
|
||||
// val platform = "ANDROID_${BuildConfig.VERSION_NAME}"
|
||||
// val userAgent = String.format(
|
||||
// "E-Pal/%s (%s; android %s)",
|
||||
// BuildConfig.VERSION_NAME,
|
||||
// Build.MODEL,
|
||||
// Build.VERSION.RELEASE
|
||||
// )
|
||||
|
||||
val headersBuilder = Headers.Builder()
|
||||
.add("AUTH_TK", LoginManager.token ?: "")
|
||||
.add("AUTH_DID", AppUtils.getAndroidID())
|
||||
.add("platform", AppConstant.APP_CLIENT)
|
||||
.add("versionNum", "100")
|
||||
|
||||
val headers = headersBuilder.build()
|
||||
|
||||
request = chain.request().newBuilder()
|
||||
.headers(headers)
|
||||
.post(requestBody ?: emptyBody)
|
||||
.build()
|
||||
|
||||
val response = chain.proceed(request)
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
try {
|
||||
Timber.tag("发起请求")
|
||||
Timber.d(
|
||||
"""
|
||||
———————————————— 我是开始分割线 ——————————————————————————————
|
||||
${request.url}
|
||||
入参:
|
||||
${readBody(requestBody)}
|
||||
响应:
|
||||
${clone(response.body)?.string()}
|
||||
———————————————— 我是结束分割线 ———————————————————————————————
|
||||
""".trimIndent()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.e("GlobalInterceptor request.exception : ${e.localizedMessage}}")
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private fun readBody(body: RequestBody?): String {
|
||||
val buffer = Buffer()
|
||||
body?.writeTo(buffer)
|
||||
return buffer.readUtf8()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun clone(body: ResponseBody?): ResponseBody? {
|
||||
val source = body?.source()
|
||||
if (source?.request(Long.MAX_VALUE) == true) throw IOException("body too long!")
|
||||
val bufferedCopy = source?.buffer?.clone()
|
||||
return bufferedCopy?.asResponseBody(body.contentType(), body.contentLength())
|
||||
}
|
||||
}
|
||||
|
|
@ -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,102 @@
|
|||
package com.remax.visualnovel.api.interceptor.util;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class Base64 {
|
||||
|
||||
private static final char[] ENCODE_CHARS = new char[]{'A', 'B', 'C', 'D',
|
||||
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
|
||||
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
|
||||
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
|
||||
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '+', '/'};
|
||||
|
||||
private static final byte[] DECODE_CHARS = new byte[]{-1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59,
|
||||
60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1,
|
||||
-1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
|
||||
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1,
|
||||
-1, -1};
|
||||
|
||||
public static String encode(byte[] data) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
int length = data.length;
|
||||
int indexx = 0;
|
||||
int b1, b2, b3;
|
||||
while (indexx < length) {
|
||||
b1 = data[indexx++] & 0xff;
|
||||
if (indexx == length) {
|
||||
buffer.append(ENCODE_CHARS[b1 >>> 2]);
|
||||
buffer.append(ENCODE_CHARS[(b1 & 0x3) << 4]);
|
||||
buffer.append("==");
|
||||
break;
|
||||
}
|
||||
b2 = data[indexx++] & 0xff;
|
||||
if (indexx == length) {
|
||||
buffer.append(ENCODE_CHARS[b1 >>> 2]);
|
||||
buffer.append(ENCODE_CHARS[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
|
||||
buffer.append(ENCODE_CHARS[(b2 & 0x0f) << 2]);
|
||||
buffer.append("=");
|
||||
break;
|
||||
}
|
||||
b3 = data[indexx++] & 0xff;
|
||||
buffer.append(ENCODE_CHARS[b1 >>> 2]);
|
||||
buffer.append(ENCODE_CHARS[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
|
||||
buffer.append(ENCODE_CHARS[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]);
|
||||
buffer.append(ENCODE_CHARS[b3 & 0x3f]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public static byte[] decode(String str) throws UnsupportedEncodingException {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
byte[] data = str.getBytes("US-ASCII");
|
||||
int len = data.length;
|
||||
int i = 0;
|
||||
int b1, b2, b3, b4;
|
||||
while (i < len) {
|
||||
do {
|
||||
b1 = DECODE_CHARS[data[i++]];
|
||||
} while (i < len && b1 == -1);
|
||||
if (b1 == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
do {
|
||||
b2 = DECODE_CHARS[data[i++]];
|
||||
} while (i < len && b2 == -1);
|
||||
if (b2 == -1) {
|
||||
break;
|
||||
}
|
||||
sb.append((char) ((b1 << 2) | ((b2 & 0x30) >>> 4)));
|
||||
|
||||
do {
|
||||
b3 = data[i++];
|
||||
if (b3 == 61) {
|
||||
return sb.toString().getBytes("iso8859-1");
|
||||
}
|
||||
b3 = DECODE_CHARS[b3];
|
||||
} while (i < len && b3 == -1);
|
||||
if (b3 == -1) {
|
||||
break;
|
||||
}
|
||||
sb.append((char) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)));
|
||||
|
||||
do {
|
||||
b4 = data[i++];
|
||||
if (b4 == 61) {
|
||||
return sb.toString().getBytes("iso8859-1");
|
||||
}
|
||||
b4 = DECODE_CHARS[b4];
|
||||
} while (i < len && b4 == -1);
|
||||
if (b4 == -1) {
|
||||
break;
|
||||
}
|
||||
sb.append((char) (((b3 & 0x03) << 6) | b4));
|
||||
}
|
||||
return sb.toString().getBytes("iso8859-1");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.remax.visualnovel.api.interceptor.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class Md5 {
|
||||
|
||||
private Md5() {
|
||||
}
|
||||
|
||||
public static String encode(String str) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(str.getBytes());
|
||||
byte[] byteDigest = md.digest();
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
for (int b : byteDigest) {
|
||||
int i = b;
|
||||
if (i < 0) {
|
||||
i += 256;
|
||||
}
|
||||
|
||||
if (i < 16) {
|
||||
buf.append("0");
|
||||
}
|
||||
|
||||
buf.append(Integer.toHexString(i));
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
} catch (NoSuchAlgorithmException var6) {
|
||||
var6.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
package com.remax.visualnovel.api.service
|
||||
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.entity.request.AIGenerate
|
||||
import com.remax.visualnovel.entity.request.AIGenerateImage
|
||||
import com.remax.visualnovel.entity.request.AIHeadImgRequest
|
||||
import com.remax.visualnovel.entity.request.AIIDRequest
|
||||
import com.remax.visualnovel.entity.request.AlbumCreate
|
||||
import com.remax.visualnovel.entity.request.AlbumDTO
|
||||
import com.remax.visualnovel.entity.request.CardRequest
|
||||
import com.remax.visualnovel.entity.request.ChatAlbum
|
||||
import com.remax.visualnovel.entity.request.ClassificationRequest
|
||||
import com.remax.visualnovel.entity.request.Gift
|
||||
import com.remax.visualnovel.entity.request.QueryAlbumDTO
|
||||
import com.remax.visualnovel.entity.request.SimpleCountDTO
|
||||
import com.remax.visualnovel.entity.response.Album
|
||||
import com.remax.visualnovel.entity.response.AlbumCreateCountOutput
|
||||
import com.remax.visualnovel.entity.response.AppearanceImage
|
||||
import com.remax.visualnovel.entity.response.Character
|
||||
import com.remax.visualnovel.entity.response.ContentRes
|
||||
import com.remax.visualnovel.entity.response.ExploreInfo
|
||||
import com.remax.visualnovel.entity.response.MeetSdOutput
|
||||
import com.remax.visualnovel.entity.response.Pageable
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface AIService {
|
||||
|
||||
/**
|
||||
* 卡片上报绑定
|
||||
*/
|
||||
@POST("/web/meet/bd")
|
||||
suspend fun cardBind(@Body request: AIIDRequest): Response<Character>
|
||||
|
||||
/**
|
||||
* 卡片被喜欢推荐
|
||||
*/
|
||||
@POST("/web/meet/rc")
|
||||
suspend fun cardLiked(): Response<Album>
|
||||
|
||||
/**
|
||||
* 卡片上报
|
||||
*/
|
||||
@POST("/web/meet/sd")
|
||||
suspend fun reportCard(@Body request: CardRequest): Response<MeetSdOutput>
|
||||
|
||||
/**
|
||||
* 获取首页卡片列表
|
||||
*/
|
||||
@POST("/web/home/rm-list")
|
||||
suspend fun getHomeCard(@Body request: ClassificationRequest): Response<List<Character>>
|
||||
|
||||
|
||||
/**
|
||||
* 获取单个首页卡片
|
||||
*/
|
||||
@POST(" /web/home/meet-detail")
|
||||
suspend fun getHomeCardDetail(@Body request: AIIDRequest): Response<Character>
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
*/
|
||||
@POST("/web/home/classification-list")
|
||||
suspend fun getClassificationList(@Body request: ClassificationRequest): Response<List<Character>>
|
||||
|
||||
/**
|
||||
* 获取榜单
|
||||
*/
|
||||
@POST("/web/rank/heartbeat")
|
||||
suspend fun getHeartbeatRank(): Response<List<Character>>
|
||||
|
||||
/**
|
||||
* 获取榜单
|
||||
*/
|
||||
@POST("/web/rank/gift")
|
||||
suspend fun getGiftRank(): Response<List<Character>>
|
||||
|
||||
/**
|
||||
* 获取榜单
|
||||
*/
|
||||
@POST("/web/rank/chat")
|
||||
suspend fun getChatRank(): Response<List<Character>>
|
||||
|
||||
/**
|
||||
* 获取发现页顶部数据
|
||||
*/
|
||||
@POST("/web/explore/info")
|
||||
suspend fun getExploreInfo(): Response<ExploreInfo>
|
||||
|
||||
/**
|
||||
* 解锁加密图片
|
||||
*/
|
||||
@POST("/web/ai-user/unlock-album-img")
|
||||
suspend fun unlockAlbum(@Body dto: ChatAlbum): Response<Album>
|
||||
|
||||
/**
|
||||
* 解锁秘密爱慕者
|
||||
*/
|
||||
@POST("/web/meet/unlock")
|
||||
suspend fun unlockSecret(@Body dto: AIIDRequest): Response<Album>
|
||||
|
||||
/**
|
||||
* 设置当前图片价格
|
||||
*/
|
||||
@POST("/web/ai-user/set-album-unlock-price")
|
||||
suspend fun setAlbumUnlockPrice(@Body dto: AlbumDTO): Response<Any>
|
||||
|
||||
/**
|
||||
* 删除AI角色
|
||||
*/
|
||||
@POST("/web/ai-user/del")
|
||||
suspend fun deleteAICharacter(@Body request: Character): Response<Any>
|
||||
|
||||
/**
|
||||
* 设置当前默认图片
|
||||
*/
|
||||
@POST("/web/ai-user/set-default-album")
|
||||
suspend fun setAlbumDefault(@Body dto: AlbumDTO): Response<Any>
|
||||
|
||||
@POST("/web/ai-user/create-edit")
|
||||
suspend fun createOrEditAICharacter(@Body request: Character): Response<Character>
|
||||
|
||||
@POST("/web/ai-user/edit-head-img")
|
||||
suspend fun editAIAvatar(@Body request: AIHeadImgRequest): Response<Any>
|
||||
|
||||
|
||||
@POST(BuildConfig.API_COW + "/web/gen/user-content-v1")
|
||||
suspend fun generateAICharacter(@Body request: AIGenerate): Response<ContentRes>
|
||||
|
||||
/**
|
||||
* 编辑时获取我的ai角色信息
|
||||
*/
|
||||
@POST("/web/ai-user/get-my-ai-user/info")
|
||||
suspend fun getAICharacter(@Body request: Character): Response<Character>
|
||||
|
||||
/**
|
||||
* 访问AI个人主页时获取信息
|
||||
*/
|
||||
@POST("/web/ai-user-search/base-info")
|
||||
suspend fun getAICharacterProfile(@Body request: Character): Response<Character>
|
||||
|
||||
/**
|
||||
* 访问AI的统计信息
|
||||
*/
|
||||
@POST("/web/ai-user/stat")
|
||||
suspend fun getAICharacterStat(@Body request: Character): Response<Character>
|
||||
|
||||
/**
|
||||
* 修改点赞状态
|
||||
*/
|
||||
@POST("/web/ai-user/like-or-cancel")
|
||||
suspend fun setAILikeOrCancel(@Body request: AlbumDTO): Response<Any>
|
||||
|
||||
/**
|
||||
* 喜欢或取消喜欢相片
|
||||
*/
|
||||
@POST("/web/album/like_or_cancel")
|
||||
suspend fun setLikeOrDislike(@Body dto: AlbumDTO): Response<Any>
|
||||
|
||||
/**
|
||||
* 删除相片
|
||||
*/
|
||||
@POST("/web/ai-user/album-del")
|
||||
suspend fun deleteAlbum(@Body dto: AlbumDTO): Response<Any>
|
||||
|
||||
/**
|
||||
* 批量添加图片到相册
|
||||
*/
|
||||
@POST("/web/ai-user/batch-add-album")
|
||||
suspend fun addAlbum(@Body dto: AlbumCreate): Response<Any>
|
||||
|
||||
/**
|
||||
* 获取创作次数
|
||||
*/
|
||||
@POST("/web/user/get-user-create-count")
|
||||
suspend fun getAlbumCreateCount(): Response<AlbumCreateCountOutput>
|
||||
|
||||
/**
|
||||
* 购买创作次数
|
||||
*/
|
||||
@POST("/web/ai/buy-create-image-count")
|
||||
suspend fun buyAlbumCreateCount(@Body dto: SimpleCountDTO): Response<Any>
|
||||
|
||||
/**
|
||||
* 批量添加图片到聊天背景
|
||||
*/
|
||||
@POST("/web/chat-background/batch-add")
|
||||
suspend fun addChatBackground(@Body dto: AlbumCreate): Response<Any>
|
||||
|
||||
/**
|
||||
* 获取相册 分页
|
||||
*/
|
||||
@POST("/web/ai-user/album-list")
|
||||
suspend fun getAlbumList(@Body dto: QueryAlbumDTO): Response<Pageable<Album>>
|
||||
|
||||
/**
|
||||
* 获取用户礼物 分页
|
||||
*/
|
||||
@POST("/web/ai-user-gift/list")
|
||||
suspend fun getUserGiftList(@Body dto: QueryAlbumDTO): Response<Pageable<Gift>>
|
||||
|
||||
|
||||
/**
|
||||
* AI一键生成-创建生成人物形象图片任务
|
||||
*/
|
||||
@POST(BuildConfig.API_COW + "/web/gen/image-ct")
|
||||
suspend fun generateImageBatch(@Body request: AIGenerateImage): Response<AIGenerateImage>
|
||||
|
||||
/**
|
||||
* AI一键生成-删除图片生成任务
|
||||
*/
|
||||
@POST(BuildConfig.API_COW + "/web/gen/del")
|
||||
suspend fun generateImageBatchDel(@Body request: AIGenerateImage): Response<Any>
|
||||
|
||||
/**
|
||||
* AI一键生成-轮询查询图片生成结果
|
||||
*/
|
||||
@POST(BuildConfig.API_COW + "/web/gen/image-pl")
|
||||
suspend fun generateImageBatchQuery(@Body request: AIGenerateImage): Response<List<AppearanceImage>>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.remax.visualnovel.api.service
|
||||
|
||||
import com.remax.visualnovel.entity.response.Book
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import retrofit2.http.POST
|
||||
|
||||
|
||||
interface BookService {
|
||||
@POST("/web/si/asi")
|
||||
suspend fun getBooks(): Response<Book>
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package com.remax.visualnovel.api.service
|
||||
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.entity.request.AIFeedback
|
||||
import com.remax.visualnovel.entity.request.AIIDRequest
|
||||
import com.remax.visualnovel.entity.request.AIIsShowDTO
|
||||
import com.remax.visualnovel.entity.request.ChatAlbum
|
||||
import com.remax.visualnovel.entity.request.ChatSetting
|
||||
import com.remax.visualnovel.entity.request.HeartbeatBuy
|
||||
import com.remax.visualnovel.entity.request.RTCRequest
|
||||
import com.remax.visualnovel.entity.request.SearchPage
|
||||
import com.remax.visualnovel.entity.request.SimpleDataDTO
|
||||
import com.remax.visualnovel.entity.request.VoiceTTS
|
||||
import com.remax.visualnovel.entity.response.Album
|
||||
import com.remax.visualnovel.entity.response.Character
|
||||
import com.remax.visualnovel.entity.response.ChatBackground
|
||||
import com.remax.visualnovel.entity.response.ChatSet
|
||||
import com.remax.visualnovel.entity.response.Friends
|
||||
import com.remax.visualnovel.entity.response.HeartbeatLevelOutput
|
||||
import com.remax.visualnovel.entity.response.Pageable
|
||||
import com.remax.visualnovel.entity.response.Token
|
||||
import com.remax.visualnovel.entity.response.VoiceASR
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface ChatService {
|
||||
|
||||
/**
|
||||
* 发送开场白消息
|
||||
*/
|
||||
@POST("/web/chat/send-dialogue-prologue-message")
|
||||
suspend fun sendDialogueMsg(@Body request: AIIDRequest): Response<Any>
|
||||
|
||||
/**
|
||||
* 获取IM中AI的基础信息
|
||||
*/
|
||||
@POST("/web/ai-user-search/im-base-info")
|
||||
suspend fun getIMAICharacterProfile(@Body request: Character): Response<Character>
|
||||
|
||||
/**
|
||||
* 访问解锁加密图片
|
||||
*/
|
||||
@POST("/web/ai-user/view-unlock-album-img")
|
||||
suspend fun viewAlbumImg(@Body request: ChatAlbum): Response<Album>
|
||||
|
||||
/**
|
||||
* 关系列表
|
||||
*/
|
||||
@POST("/web/ai-user/heartbeat-relation-list")
|
||||
suspend fun getMyFriends(@Body request: SearchPage): Response<Pageable<Friends>>
|
||||
|
||||
|
||||
@POST("/web/ai-user/heartbeat-rank")
|
||||
suspend fun getMyFriendRank(): Response<Double>
|
||||
|
||||
/**
|
||||
* 生成提示词
|
||||
*/
|
||||
@POST(BuildConfig.API_COW + "/web/gen/sup-content-v2")
|
||||
suspend fun getPrompts(@Body request: AIIDRequest): Response<List<String>>
|
||||
|
||||
/**
|
||||
* AI回话点赞/点踩
|
||||
*/
|
||||
@POST(BuildConfig.API_PIGEON + "/web/fb/v1")
|
||||
suspend fun aiFeedback(@Body request: AIFeedback): Response<Any>
|
||||
|
||||
/**
|
||||
* 获取RTC
|
||||
*/
|
||||
@POST(BuildConfig.API_COW + "/web/voice-chat/gen-rtc-tk")
|
||||
suspend fun getRTCToken(@Body request: RTCRequest): Response<Token>
|
||||
|
||||
/**
|
||||
* 操作通话
|
||||
*/
|
||||
@POST(BuildConfig.API_COW + "/web/voice-chat/opt")
|
||||
suspend fun voiceChatOpt(@Body request: RTCRequest): Response<Any>
|
||||
|
||||
/**
|
||||
* 获取聊天背景列表
|
||||
*/
|
||||
@POST("/web/chat-background/list")
|
||||
suspend fun getChatBackgroundList(@Body request: AIIDRequest): Response<List<ChatBackground>>
|
||||
|
||||
/**
|
||||
* 获取聊天设置
|
||||
*/
|
||||
@POST("/web/chat-set/get-my")
|
||||
suspend fun getChatSetting(@Body request: ChatSetting): Response<ChatSet>
|
||||
|
||||
/**
|
||||
* 修改聊天设置
|
||||
*/
|
||||
@POST("/web/chat-set/set")
|
||||
suspend fun setChatSetting(@Body request: ChatSet): Response<Any>
|
||||
|
||||
/**
|
||||
* 修改聊天气泡
|
||||
*/
|
||||
@POST("/web/chat-set/set-chat-bubble")
|
||||
suspend fun setChatBubble(@Body request: ChatSetting): Response<Any>
|
||||
|
||||
/**
|
||||
* 修改聊天模型
|
||||
*/
|
||||
@POST("/web/chat-set/set-chat-model")
|
||||
suspend fun setChatModel(@Body request: ChatSetting): Response<Any>
|
||||
|
||||
/**
|
||||
* 修改是否自动播放语音
|
||||
*/
|
||||
@POST("/web/chat-set/auto-play-voice")
|
||||
suspend fun setChatAutoPlay(@Body request: ChatSetting): Response<Any>
|
||||
|
||||
/**
|
||||
* 修改聊天背景图
|
||||
*/
|
||||
@POST("/web/chat-background/set-background")
|
||||
suspend fun setChatBackground(@Body request: ChatSetting): Response<Any>
|
||||
|
||||
/**
|
||||
* 删除聊天背景图
|
||||
*/
|
||||
@POST("/web/chat-background/del")
|
||||
suspend fun deleteChatBackground(@Body request: ChatSetting): Response<Any>
|
||||
|
||||
/**
|
||||
* 展示心动关系开关
|
||||
*/
|
||||
@POST("/web/ai-user/heartbeat-relation-switch")
|
||||
suspend fun relationSwitch(@Body request: AIIsShowDTO): Response<Any>
|
||||
|
||||
/**
|
||||
* 语音转文本
|
||||
*/
|
||||
@POST(BuildConfig.API_COW + "/web/voice/asr-v2")
|
||||
suspend fun voiceASR(@Body request: SimpleDataDTO): Response<VoiceASR>
|
||||
|
||||
/**
|
||||
* 生成语音
|
||||
*/
|
||||
@POST(BuildConfig.API_COW + "/web/voice/tts-v2")
|
||||
suspend fun voiceTTS(@Body request: VoiceTTS): Response<String>
|
||||
|
||||
/**
|
||||
* 获取心动等级
|
||||
*/
|
||||
@POST("/web/ai-user/heartbeat-level")
|
||||
suspend fun getHeartbeatLevel(@Body request: Character): Response<HeartbeatLevelOutput>
|
||||
|
||||
/**
|
||||
* 购买心动值
|
||||
*/
|
||||
@POST("/web/ai-user/buy-heartbeat-val")
|
||||
suspend fun buyHeartbeatVal(@Body request: HeartbeatBuy): Response<Any>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.remax.visualnovel.api.service
|
||||
|
||||
|
||||
import com.remax.visualnovel.entity.request.AIIDRequest
|
||||
import com.remax.visualnovel.entity.request.Gift
|
||||
import com.remax.visualnovel.entity.request.PageQuery
|
||||
import com.remax.visualnovel.entity.response.AIDict
|
||||
import com.remax.visualnovel.entity.response.ChatBubble
|
||||
import com.remax.visualnovel.entity.response.ChatModel
|
||||
import com.remax.visualnovel.entity.response.Pageable
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface DictService {
|
||||
|
||||
/**
|
||||
* 获取聊天气泡字典
|
||||
*/
|
||||
@POST("/web/chat-set/get-chat-bubble-list")
|
||||
suspend fun getChatBubbleList(@Body request: AIIDRequest): Response<List<ChatBubble>>
|
||||
|
||||
/**
|
||||
* AI标签
|
||||
*/
|
||||
@POST("/web/get-ai-dict")
|
||||
suspend fun getAIDict(): Response<AIDict>
|
||||
|
||||
/**
|
||||
* 礼物字典
|
||||
*/
|
||||
@POST("/web/gift/dict-list")
|
||||
suspend fun getGiftDict(@Body pageQuery: PageQuery = PageQuery(1).apply { page.ps = 100 }): Response<Pageable<Gift>>
|
||||
|
||||
/**
|
||||
* chat模型
|
||||
*/
|
||||
@POST("/web/chat-model/dict-list")
|
||||
suspend fun getAIChatModel(): Response<List<ChatModel>>
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.remax.visualnovel.api.service
|
||||
|
||||
import com.remax.visualnovel.entity.request.CompleteUserInfoInput
|
||||
import com.remax.visualnovel.entity.request.PlatformAccountVerifyDTO
|
||||
import com.remax.visualnovel.entity.response.PlatformAccountVerify
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface LoginService {
|
||||
|
||||
/**
|
||||
* 重复昵称验证
|
||||
*/
|
||||
@POST("/web/user/nickname-check")
|
||||
suspend fun checkUserNickname(@Body request: CompleteUserInfoInput): Response<Boolean>
|
||||
|
||||
/**
|
||||
* 三方账号验证
|
||||
*/
|
||||
@POST("/web/third/login")
|
||||
suspend fun platformThirdVerify(@Body request: PlatformAccountVerifyDTO): Response<PlatformAccountVerify>
|
||||
|
||||
@POST("/web/user/logout")
|
||||
suspend fun logout(): Response<Any>
|
||||
|
||||
@POST("/web/user/complete-user-info")
|
||||
suspend fun register(@Body request: CompleteUserInfoInput): Response<Any>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.remax.visualnovel.api.service
|
||||
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.entity.request.AIListRequest
|
||||
import com.remax.visualnovel.entity.request.PageQuery
|
||||
import com.remax.visualnovel.entity.request.SendGift
|
||||
import com.remax.visualnovel.entity.response.MessageListOutput
|
||||
import com.remax.visualnovel.entity.response.MessageStatOutput
|
||||
import com.remax.visualnovel.entity.response.Pageable
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface MessageService {
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
*/
|
||||
@POST(BuildConfig.API_COW + "/web/ai-message/del")
|
||||
suspend fun deleteConversation(@Body request: AIListRequest): Response<Any>
|
||||
|
||||
/**
|
||||
* 送礼物
|
||||
*/
|
||||
@POST("/web/ai-user-gift/send")
|
||||
suspend fun sendGift(@Body dto: SendGift): Response<Any>
|
||||
|
||||
/**
|
||||
* 未读消息统计
|
||||
*/
|
||||
@POST(BuildConfig.API_PIGEON + "/web/message/stat")
|
||||
suspend fun getMessageStat(): Response<MessageStatOutput>
|
||||
|
||||
/**
|
||||
* 系统通知列表
|
||||
*/
|
||||
@POST(BuildConfig.API_PIGEON + "/web/message/list")
|
||||
suspend fun getMessageList(@Body dto: PageQuery): Response<Pageable<MessageListOutput>>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.remax.visualnovel.api.service
|
||||
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.entity.request.ImgCheckDTO
|
||||
import com.remax.visualnovel.entity.request.S3TypeDTO
|
||||
import com.remax.visualnovel.entity.request.SimpleContentDTO
|
||||
import com.remax.visualnovel.entity.response.BucketBean
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* OSS文件上传
|
||||
*
|
||||
*/
|
||||
interface OssService {
|
||||
/**
|
||||
* 获取aws s3 bucket信息
|
||||
*/
|
||||
@POST(BuildConfig.API_SHARK + "/web/file/sts-tk")
|
||||
suspend fun getS3Bucket(@Body dto: S3TypeDTO): Response<BucketBean>
|
||||
|
||||
/**
|
||||
* 图片鉴黄
|
||||
*/
|
||||
@POST(BuildConfig.API_SHARK + "/web/file/check")
|
||||
suspend fun checkS3Img(
|
||||
@Body imgCheckDTO: ImgCheckDTO
|
||||
): Response<Any>
|
||||
|
||||
/**
|
||||
* 关键字校验
|
||||
*/
|
||||
@POST("/web/check_text")
|
||||
suspend fun checkText(
|
||||
@Body simpleContentDTO: SimpleContentDTO
|
||||
): Response<Any>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.remax.visualnovel.api.service
|
||||
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.entity.request.ChargeOrderDTO
|
||||
import com.remax.visualnovel.entity.request.ChargeProductDTO
|
||||
import com.remax.visualnovel.entity.request.ChargeProductInfo
|
||||
import com.remax.visualnovel.entity.request.SearchPage
|
||||
import com.remax.visualnovel.entity.request.SubPriceDTO
|
||||
import com.remax.visualnovel.entity.request.ValidateTransactionDTO
|
||||
import com.remax.visualnovel.entity.response.ChargeOrder
|
||||
import com.remax.visualnovel.entity.response.Membership
|
||||
import com.remax.visualnovel.entity.response.SubPrice
|
||||
import com.remax.visualnovel.entity.response.Transaction
|
||||
import com.remax.visualnovel.entity.response.UserSubInfo
|
||||
import com.remax.visualnovel.entity.response.Wallet
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface PayService {
|
||||
/**
|
||||
* 获取我的流水
|
||||
*/
|
||||
@POST(BuildConfig.API_LION + "/web/pay/account/bill-list")
|
||||
suspend fun getTransactionList(@Body request: SearchPage): Response<Transaction>
|
||||
|
||||
/**
|
||||
* 获取我的钱包
|
||||
*/
|
||||
@POST(BuildConfig.API_LION + "/web/pay/account/wallet")
|
||||
suspend fun getMyWallet(): Response<Wallet>
|
||||
|
||||
/**
|
||||
* 获取充值产品
|
||||
*/
|
||||
@POST(BuildConfig.API_LION + "/web/pay/config/charge-product-list")
|
||||
suspend fun getChargeProducts(
|
||||
@Body dto: ChargeProductDTO = ChargeProductDTO()
|
||||
): Response<ChargeProductInfo>
|
||||
|
||||
|
||||
/**
|
||||
* 获取vip订阅价格列表
|
||||
*/
|
||||
@POST(BuildConfig.API_LION + "/web/pay/config/sub-product-list")
|
||||
suspend fun getSubPriceList(
|
||||
@Body subPriceDTO: SubPriceDTO = SubPriceDTO()
|
||||
): Response<List<SubPrice>>
|
||||
|
||||
/**
|
||||
* 会员特权列表
|
||||
*/
|
||||
@POST(BuildConfig.API_LION + "/web/member/detail")
|
||||
suspend fun getVipPrivilegeList(): Response<Membership>
|
||||
|
||||
/**
|
||||
* 创建一个订单
|
||||
*/
|
||||
@POST(BuildConfig.API_LION +"/web/pay/trade/pre-charge-google")
|
||||
suspend fun createOrder(
|
||||
@Body dto: ChargeOrderDTO
|
||||
): Response<ChargeOrder>
|
||||
|
||||
/**
|
||||
* 验证支付是否成功
|
||||
*/
|
||||
@POST(BuildConfig.API_LION +"/web/pay/webhooks/google/v2")
|
||||
suspend fun validateTranslation(
|
||||
@Body dto: ValidateTransactionDTO
|
||||
): Response<Any>
|
||||
|
||||
/**
|
||||
* 验证订阅是否成功
|
||||
*/
|
||||
@POST(BuildConfig.API_LION +"/web/pay/subscribe/upload-google-receipt")
|
||||
suspend fun uploadGoogleReceipt(
|
||||
@Body dto: ValidateTransactionDTO
|
||||
): Response<String>
|
||||
|
||||
/**
|
||||
* 订阅/升级VIP前查询订阅信息
|
||||
*/
|
||||
@POST(BuildConfig.API_LION +"/web/pay/appStore/getUserSubscription")
|
||||
suspend fun checkSubInfo(): Response<UserSubInfo>
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.remax.visualnovel.api.service
|
||||
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.entity.request.CompleteUserInfoInput
|
||||
import com.remax.visualnovel.entity.response.Character
|
||||
import com.remax.visualnovel.entity.response.User
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface UserService{
|
||||
|
||||
/**
|
||||
* 签到
|
||||
*/
|
||||
@POST("/web/si/asi")
|
||||
suspend fun signToday(): Response<Boolean>
|
||||
|
||||
/**
|
||||
* 获取签到周期数据
|
||||
*/
|
||||
/*@POST("/web/si/list")
|
||||
suspend fun getSignList(): Response<SignInRoundOutput>*/
|
||||
|
||||
/**
|
||||
* 获取登录用户基础信息
|
||||
*/
|
||||
@POST("/web/user/base-info")
|
||||
suspend fun getMyBaseInfo(): Response<User>
|
||||
|
||||
/**
|
||||
* 获取me页面的ai列表
|
||||
*/
|
||||
@POST("/web/ai-user-search/base-list")
|
||||
suspend fun getMyCharactersList(): Response<List<Character>>
|
||||
|
||||
/**
|
||||
* 删除账号
|
||||
*/
|
||||
@POST("/web/user/del")
|
||||
suspend fun deleteAccount(): Response<Any>
|
||||
|
||||
@POST("/web/user/edit-user-info")
|
||||
suspend fun updateUserInfo(@Body request: CompleteUserInfoInput): Response<Any>
|
||||
|
||||
/**
|
||||
* 获取云信appKey account token
|
||||
*/
|
||||
/*@POST(BuildConfig.API_PIGEON + "/web/im-user/get-account")
|
||||
suspend fun getNimInfo(): Response<NimBean>*/
|
||||
|
||||
}
|
||||
|
|
@ -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,47 @@
|
|||
package com.remax.visualnovel.app
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
||||
|
||||
/**
|
||||
* Created by HJW on 2021/10/19
|
||||
*/
|
||||
abstract class BaseBindingQuickAdapter<T, out VB : ViewBinding>(
|
||||
private val inflate: (LayoutInflater, ViewGroup, Boolean) -> VB,
|
||||
layoutResId: Int = -1,
|
||||
data: MutableList<T>? = null
|
||||
) :
|
||||
BaseQuickAdapter<T, BaseBindingQuickAdapter.BaseBindingHolder>(layoutResId, data) {
|
||||
|
||||
override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
BaseBindingHolder(inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
|
||||
class BaseBindingHolder(private val binding: ViewBinding) : BaseViewHolder(binding.root) {
|
||||
constructor(itemView: View) : this(ViewBinding { itemView })
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <VB : ViewBinding> getViewBinding() = binding as VB
|
||||
|
||||
var extraObj: Any? = null
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 带左右滑动的item收起
|
||||
*/
|
||||
// fun resetDeleteView(position: Int) {
|
||||
// try {
|
||||
// (getViewByPosition(
|
||||
// position + headerLayoutCount,
|
||||
// R.id.easySwipeMenuLayout
|
||||
// ) as? EasySwipeMenuLayout)?.resetStatus()
|
||||
// } catch (e: Exception) {
|
||||
// Timber.d("resetDeleteView position:$position Exception:${e.localizedMessage}")
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.remax.visualnovel.app
|
||||
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.remax.visualnovel.app.initializer.utils.FirebaseHelper
|
||||
import com.remax.visualnovel.app.viewmodel.AppGlobalViewModel
|
||||
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/9/21
|
||||
* 监听整个app生命周期
|
||||
*/
|
||||
object ProcessLifecycleObserver : DefaultLifecycleObserver {
|
||||
|
||||
var isOnResume = false
|
||||
private var refreshFirebaseTokenTime = 0L
|
||||
|
||||
private var appGlobalViewModel: AppGlobalViewModel? = null
|
||||
|
||||
fun setAppGlobalViewModel(appGlobalViewModel: AppGlobalViewModel) {
|
||||
ProcessLifecycleObserver.appGlobalViewModel = appGlobalViewModel
|
||||
}
|
||||
|
||||
/**
|
||||
* APP在前台回调
|
||||
*/
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
isOnResume = true
|
||||
val currTime = System.currentTimeMillis()
|
||||
if (refreshFirebaseTokenTime != 0L && currTime - refreshFirebaseTokenTime > 60 * 1000) {
|
||||
refreshFirebaseTokenTime = currTime
|
||||
FirebaseHelper.getToken {
|
||||
appGlobalViewModel?.updateTerminal(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* APP进入后台回调
|
||||
*/
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
isOnResume = false
|
||||
}
|
||||
}
|
||||
|
|
@ -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,37 @@
|
|||
package com.remax.visualnovel.app.activityresultapi
|
||||
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.result.ActivityResultCaller
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
|
||||
class XActivityResultContract<I, O>(
|
||||
activityResultCaller: ActivityResultCaller,
|
||||
activityResultContract: ActivityResultContract<I, O>
|
||||
) {
|
||||
|
||||
private var activityResultCallback: ActivityResultCallback<O>? = null
|
||||
|
||||
|
||||
private val launcher: ActivityResultLauncher<I> =
|
||||
activityResultCaller.registerForActivityResult(activityResultContract) {
|
||||
activityResultCallback?.onActivityResult(it)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 启动
|
||||
*/
|
||||
fun launch(input: I, activityResultCallback: ActivityResultCallback<O>?) {
|
||||
this.activityResultCallback = activityResultCallback
|
||||
launcher.launch(input)
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销
|
||||
*/
|
||||
fun unregister() {
|
||||
launcher.unregister()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package com.remax.visualnovel.app.base
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.remax.visualnovel.app.AbsView
|
||||
import com.remax.visualnovel.app.widget.LoadingDialog
|
||||
import com.remax.visualnovel.extension.fixedFontSize
|
||||
import com.remax.visualnovel.extension.getBgColor
|
||||
import com.remax.visualnovel.extension.isShouldHideKeyboard
|
||||
import com.remax.visualnovel.extension.setStatusBarColor
|
||||
import com.remax.visualnovel.extension.toast
|
||||
import com.remax.visualnovel.extension.transitionFromAlpha
|
||||
import com.remax.visualnovel.extension.transitionFromBottom
|
||||
import com.remax.visualnovel.extension.withTransitionFromAlpha
|
||||
import com.remax.visualnovel.extension.withTransitionFromBottom
|
||||
import com.remax.visualnovel.utils.KeyboardUtils
|
||||
import com.remax.visualnovel.utils.StatusBarUtils
|
||||
import com.dylanc.loadingstateview.ActivityTransitionType
|
||||
import com.dylanc.loadingstateview.BgColorType
|
||||
import com.dylanc.loadingstateview.Decorative
|
||||
import com.dylanc.loadingstateview.LoadingState
|
||||
import com.dylanc.loadingstateview.LoadingStateDelegate
|
||||
import com.dylanc.loadingstateview.OnReloadListener
|
||||
import com.dylanc.viewbinding.base.ActivityBinding
|
||||
import com.dylanc.viewbinding.base.ActivityBindingDelegate
|
||||
|
||||
|
||||
/**
|
||||
* Activity基类
|
||||
*/
|
||||
abstract class BaseBindingActivity<out VB : ViewBinding> : AppCompatActivity(), AbsView,
|
||||
LoadingState by LoadingStateDelegate(), OnReloadListener, Decorative,
|
||||
ActivityBinding<VB> by ActivityBindingDelegate() {
|
||||
|
||||
private val loadingDialog by lazy {
|
||||
createLoadingDialog()
|
||||
}
|
||||
|
||||
private fun createLoadingDialog(): LoadingDialog {
|
||||
val dialog = LoadingDialog()
|
||||
dialog.build(this)
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun getResources(): Resources {
|
||||
return super.getResources().fixedFontSize(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentViewWithBinding()
|
||||
binding.root.decorate(this, this)
|
||||
StatusBarUtils.setStatusBarAndNavBarIsLight(this, false)
|
||||
setStatusBarColor(backgroundColorType())
|
||||
binding.root.setBackgroundColor(getBgColor(backgroundColorType()))
|
||||
when (transitionType()) {
|
||||
ActivityTransitionType.BOTTOM -> withTransitionFromBottom()
|
||||
ActivityTransitionType.ALPHA -> withTransitionFromAlpha()
|
||||
else -> {}
|
||||
}
|
||||
initView()
|
||||
initData()
|
||||
subscribeUi()
|
||||
}
|
||||
|
||||
protected open fun backgroundColorType() = BgColorType.SPECIAL_MAP
|
||||
|
||||
protected abstract fun initView()
|
||||
protected open fun subscribeUi() {}
|
||||
protected open fun initData() {}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (KeyboardUtils.isSoftInputVisible(this)) {
|
||||
KeyboardUtils.hideSoftInput(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun finish() {
|
||||
super.finish()
|
||||
when (transitionType()) {
|
||||
ActivityTransitionType.BOTTOM -> transitionFromBottom()
|
||||
ActivityTransitionType.ALPHA -> transitionFromAlpha()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
protected open var touchEventFun: ((MotionEvent) -> Unit)? = null
|
||||
protected open fun touchHideKeyboardViewList(): List<View>? = null
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||
touchEventFun?.let {
|
||||
runCatching { ev?.run(it) }
|
||||
}
|
||||
if (touchHideKeyboardViewList() != null) {
|
||||
if (ev?.action == MotionEvent.ACTION_DOWN && KeyboardUtils.isSoftInputVisible(this) && touchHideKeyboardViewList()?.any {
|
||||
it.isShouldHideKeyboard(
|
||||
ev
|
||||
)
|
||||
} == true) {
|
||||
KeyboardUtils.hideSoftInput(this)
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面开关方向
|
||||
*/
|
||||
protected open fun transitionType() = ActivityTransitionType.DEFAULT
|
||||
|
||||
override fun showLoading() {
|
||||
if (!this.isDestroyed) {
|
||||
runOnUiThread {
|
||||
loadingDialog.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hideLoading() {
|
||||
if (!this.isDestroyed) {
|
||||
runOnUiThread {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun showToast(text: String?) {
|
||||
toast(text)
|
||||
}
|
||||
|
||||
override fun showToast(resId: Int) {
|
||||
toast(resId)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package com.remax.visualnovel.app.base
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.dylanc.loadingstateview.BgColorType
|
||||
import com.dylanc.loadingstateview.Decorative
|
||||
import com.dylanc.loadingstateview.LoadingState
|
||||
import com.dylanc.loadingstateview.LoadingStateDelegate
|
||||
import com.dylanc.loadingstateview.OnReloadListener
|
||||
import com.dylanc.viewbinding.base.FragmentBinding
|
||||
import com.dylanc.viewbinding.base.FragmentBindingDelegate
|
||||
import com.remax.visualnovel.app.AbsView
|
||||
import com.remax.visualnovel.app.widget.LoadingDialog
|
||||
import com.remax.visualnovel.extension.getBgColor
|
||||
import com.remax.visualnovel.extension.toast
|
||||
|
||||
/**
|
||||
* 基类Fragment
|
||||
*/
|
||||
abstract class BaseBindingFragment<VB : ViewBinding> : Fragment(), AbsView,
|
||||
LoadingState by LoadingStateDelegate(), OnReloadListener, Decorative,
|
||||
FragmentBinding<VB> by FragmentBindingDelegate() {
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = createViewWithBinding(inflater, container).decorate(this, this)
|
||||
view.setBackgroundColor(requireContext().getBgColor(backgroundColorType()))
|
||||
return view
|
||||
}
|
||||
|
||||
open fun backgroundColorType() = BgColorType.SPECIAL_MAP
|
||||
|
||||
private val loadingDialog by lazy {
|
||||
createLoadingDialog()
|
||||
}
|
||||
|
||||
private var isLoaded = false
|
||||
|
||||
private fun createLoadingDialog(): LoadingDialog {
|
||||
val dialog = LoadingDialog()
|
||||
dialog.build(requireActivity())
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
onCreated(arguments)
|
||||
subscribeUi()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (!isLoaded && !isHidden) {
|
||||
lazyInit()
|
||||
isLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onCreated(bundle: Bundle?)
|
||||
|
||||
open fun lazyInit() {}
|
||||
|
||||
open fun subscribeUi() {}
|
||||
|
||||
override fun showLoading() {
|
||||
if (activity?.isDestroyed == false) {
|
||||
activity?.runOnUiThread {
|
||||
loadingDialog.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hideLoading() {
|
||||
if (activity?.isDestroyed == false) {
|
||||
activity?.runOnUiThread {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun showToast(text: String?) {
|
||||
if (!isDetached) {
|
||||
activity?.let {
|
||||
if (!it.isDestroyed) {
|
||||
it.toast(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun showToast(resId: Int) {
|
||||
if (!isDetached) {
|
||||
activity?.let {
|
||||
if (!it.isDestroyed) {
|
||||
it.toast(resId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
if (loadingDialog.getDialog().isShowing) {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
super.onDetach()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.remax.visualnovel.app.base
|
||||
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/7/18
|
||||
*/
|
||||
abstract class BaseCommonNavigatorAdapter(
|
||||
open val viewPager2: ViewPager2?,
|
||||
open val viewPager: ViewPager? = null
|
||||
) : CommonNavigatorAdapter()
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.remax.visualnovel.app.base.app
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import com.remax.visualnovel.app.viewmodel.AppGlobalViewModel
|
||||
import com.remax.visualnovel.app.viewmodel.AppIMViewModel
|
||||
import com.remax.visualnovel.repository.api.MessageRepository
|
||||
import com.remax.visualnovel.repository.api.UserRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* 用于创建[AppIMViewModel, AppGlobalViewModel]等实例
|
||||
*/
|
||||
class AppViewModelFactory @Inject constructor(
|
||||
private val application: Application,
|
||||
private val userRepository: UserRepository,
|
||||
private val messageRepository: MessageRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
|
||||
return when (modelClass) {
|
||||
AppIMViewModel::class.java -> AppIMViewModel(application)
|
||||
AppGlobalViewModel::class.java -> AppGlobalViewModel(application, userRepository, messageRepository)
|
||||
else -> throw IllegalArgumentException("Unknown class $modelClass")
|
||||
} as T
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.remax.visualnovel.app.base.app
|
||||
|
||||
import android.app.Application
|
||||
|
||||
interface ApplicationProxy {
|
||||
|
||||
fun onCreate(application: Application)
|
||||
|
||||
fun onTerminate()
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.remax.visualnovel.app.base.app
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
|
||||
object CommonApplicationProxy : ApplicationProxy, ViewModelStoreOwner {
|
||||
|
||||
lateinit var application: Application
|
||||
private set
|
||||
|
||||
override val viewModelStore = ViewModelStore()
|
||||
|
||||
override fun onCreate(application: Application) {
|
||||
CommonApplicationProxy.application = application
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
viewModelStore.clear()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
package com.remax.visualnovel.app.delegate
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import com.remax.visualnovel.R
|
||||
import com.remax.visualnovel.databinding.LayoutToolbarBinding
|
||||
import com.remax.visualnovel.extension.findActivityContext
|
||||
import com.remax.visualnovel.extension.getBgColor
|
||||
import com.remax.visualnovel.extension.getNavHeight
|
||||
import com.remax.visualnovel.extension.setOnClick
|
||||
import com.remax.visualnovel.extension.setSize
|
||||
import com.remax.visualnovel.utils.StatusBarUtils
|
||||
import com.remax.visualnovel.utils.spannablex.utils.dp
|
||||
import com.remax.visualnovel.widget.ui.buttons.IconButtonView
|
||||
import com.remax.visualnovel.widget.uitoken.changeTextColor
|
||||
import com.remax.visualnovel.widget.uitoken.expend.dsl.expand
|
||||
import com.remax.visualnovel.widget.uitoken.handleUIToken
|
||||
import com.dylanc.loadingstateview.BaseToolbarViewDelegate
|
||||
import com.dylanc.loadingstateview.NavBtnType
|
||||
import com.dylanc.loadingstateview.ToolbarConfig
|
||||
import com.dylanc.loadingstateview.toolbarExtras
|
||||
|
||||
var ToolbarConfig.titleTextColorToken: Int? by toolbarExtras()
|
||||
var ToolbarConfig.titleTextColor: Int? by toolbarExtras()
|
||||
var ToolbarConfig.titleTextAlpha: Float? by toolbarExtras()
|
||||
|
||||
/**
|
||||
* 整个导航栏相关
|
||||
*/
|
||||
var ToolbarConfig.navBgColorToken: Int? by toolbarExtras()
|
||||
var ToolbarConfig.navBgColor: Int? by toolbarExtras()
|
||||
var ToolbarConfig.navBgAlpha: Float? by toolbarExtras() //一般和isFull = true 配合使用,因为只有当navbar和content布局重叠时,才需要滑动渐隐导航栏
|
||||
var ToolbarConfig.navIsShow: Boolean? by toolbarExtras()
|
||||
|
||||
var ToolbarConfig.confirmEnabled: Boolean? by toolbarExtras()
|
||||
var ToolbarConfig.isFull: Boolean? by toolbarExtras()
|
||||
var ToolbarConfig.confirmContent: String? by toolbarExtras()
|
||||
var ToolbarConfig.contractUSEnabled: Boolean? by toolbarExtras()
|
||||
|
||||
/**
|
||||
* 返回键相关
|
||||
*/
|
||||
var ToolbarConfig.navBackColorToken: Int? by toolbarExtras()
|
||||
var ToolbarConfig.navBackColor: Int? by toolbarExtras()
|
||||
var ToolbarConfig.onNavBackClick: (() -> Unit)? by toolbarExtras()
|
||||
var ToolbarConfig.onNavBackIconToken: Int? by toolbarExtras()
|
||||
|
||||
var ToolbarConfig.iconButtonType: IconButtonType? by toolbarExtras()
|
||||
|
||||
|
||||
enum class IconButtonType {
|
||||
ON_PIC, NORMAL
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共导航栏
|
||||
*/
|
||||
class ToolbarViewDelegate : BaseToolbarViewDelegate() {
|
||||
private lateinit var binding: LayoutToolbarBinding
|
||||
private var context: Context? = null
|
||||
|
||||
override fun onCreateToolbar(inflater: LayoutInflater, parent: ViewGroup): View {
|
||||
context = parent.context
|
||||
binding = LayoutToolbarBinding.inflate(inflater, parent, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onBindToolbar(config: ToolbarConfig) {
|
||||
binding.apply {
|
||||
val expendSize = 4.dp
|
||||
tvTitle.text = config.title?.ifEmpty { " " } ?: " "
|
||||
context?.run {
|
||||
if (config.isFull == true) {
|
||||
root.tag = "isFull"
|
||||
(context?.findActivityContext() as? Activity)?.let {
|
||||
StatusBarUtils.setTransparent(it)
|
||||
navBg.setSize(height = it.getNavHeight())
|
||||
}
|
||||
navBg.alpha = 0f
|
||||
tvTitle.alpha = 0f
|
||||
}
|
||||
//设置导航栏是否显示
|
||||
config.navIsShow?.let { root.isVisible = it }
|
||||
navBg.setBackgroundColor(getBgColor(config.colorType))
|
||||
//设置导航栏颜色
|
||||
config.navBgColorToken?.let { navBg.setBackgroundColor(handleUIToken(it)?.color ?: 0) }
|
||||
config.navBgColor?.let { navBg.setBackgroundColor(it) }
|
||||
//设置导航栏透明度
|
||||
config.navBgAlpha?.let { navBg.alpha = it }
|
||||
|
||||
//设置标题颜色
|
||||
config.titleTextColorToken?.let { tvTitle.setTextColor(handleUIToken(it)?.color ?: 0) }
|
||||
config.titleTextColor?.let { tvTitle.setTextColor(it) }
|
||||
//设置标题透明度
|
||||
config.titleTextAlpha?.let { tvTitle.alpha = it }
|
||||
|
||||
//设置返回按钮颜色
|
||||
config.navBackColorToken?.let { navBack.changeTextColor { textUIColorToken = getString(it) } }
|
||||
config.navBackColor?.let { navBack.setTextColor(it) }
|
||||
|
||||
//设置confirm按钮是否可点
|
||||
config.confirmEnabled?.let { rightConfirmBtn.isEnabled = it }
|
||||
//设置confirm按钮的文字
|
||||
config.confirmContent?.let { rightConfirmBtn.text = it }
|
||||
//设置contract us按钮是否可点
|
||||
config.contractUSEnabled?.let { contractUsBtn.isEnabled = it }
|
||||
|
||||
//设置iconfont按钮样式
|
||||
when (config.iconButtonType) {
|
||||
IconButtonType.ON_PIC -> {
|
||||
navBack.setButtonStyle(buttonName = IconButtonView.NavButton_OnPic)
|
||||
rightIconBtn1.setButtonStyle(buttonName = IconButtonView.NavButton_OnPic)
|
||||
rightIconBtn2.setButtonStyle(buttonName = IconButtonView.NavButton_OnPic)
|
||||
}
|
||||
|
||||
else -> {
|
||||
navBack.setButtonStyle(buttonName = IconButtonView.NavButton)
|
||||
rightIconBtn1.setButtonStyle(buttonName = IconButtonView.NavButton)
|
||||
rightIconBtn2.setButtonStyle(buttonName = IconButtonView.NavButton)
|
||||
}
|
||||
}
|
||||
|
||||
navBack.expand(expendSize, expendSize)
|
||||
}
|
||||
|
||||
val setBackClick = {
|
||||
setOnClick(navBack) {
|
||||
if (config.onNavBackClick != null) {
|
||||
config.onNavBackClick?.invoke()
|
||||
} else {
|
||||
(context.findActivityContext() as? Activity)?.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (config.navBtnType) {
|
||||
//navBack是返回按钮
|
||||
NavBtnType.BACK -> {
|
||||
navBack.setText(R.string.icon_arrow_left_border)
|
||||
setBackClick.invoke()
|
||||
navBack.isVisible = true
|
||||
}
|
||||
//navBack是关闭按钮
|
||||
NavBtnType.ClOSE -> {
|
||||
navBack.setText(R.string.icon_close)
|
||||
setBackClick.invoke()
|
||||
navBack.isVisible = true
|
||||
}
|
||||
|
||||
//navBack是向下关闭按钮
|
||||
NavBtnType.DOWN -> {
|
||||
navBack.setText(R.string.icon_arrow_down_border)
|
||||
setBackClick.invoke()
|
||||
navBack.isVisible = true
|
||||
}
|
||||
|
||||
//navBack是自定义按钮
|
||||
NavBtnType.CUSTOM -> {
|
||||
if (config.onNavBackIconToken != null) {
|
||||
navBack.setText(config.onNavBackIconToken!!)
|
||||
navBack.isVisible = true
|
||||
setBackClick.invoke()
|
||||
} else {
|
||||
navBack.isInvisible = true
|
||||
}
|
||||
}
|
||||
|
||||
NavBtnType.INVISIBLE -> {
|
||||
navBack.isInvisible = true
|
||||
}
|
||||
|
||||
NavBtnType.NONE -> {
|
||||
navBack.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
with(contractUsBtn) {
|
||||
if (config.contractUsBtnText != null) {
|
||||
isVisible = true
|
||||
val expandX = 24.dp
|
||||
val expandY = 10.dp
|
||||
expand(expandX, expandY)
|
||||
if (config.contractUsBtnText != null) {
|
||||
setText(config.contractUsBtnText ?: 0)
|
||||
}
|
||||
setOnClick(this) {
|
||||
config.onContractUsBtnBlock?.invoke(this)
|
||||
}
|
||||
} else {
|
||||
isVisible = false
|
||||
setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
with(rightConfirmBtn) {
|
||||
if (config.showConfirmBtn == true) {
|
||||
isVisible = true
|
||||
setOnClick(this) {
|
||||
config.onConfirmBtnBlock?.invoke(this)
|
||||
}
|
||||
} else {
|
||||
isVisible = false
|
||||
setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
with(rightIconBtn1) {
|
||||
if (config.showRightIconBtn1 != null) {
|
||||
isVisible = true
|
||||
setText(config.showRightIconBtn1!!)
|
||||
expand(expendSize, expendSize)
|
||||
setOnClick(this) {
|
||||
config.onRightIconBtnBlock1?.invoke(this)
|
||||
}
|
||||
} else {
|
||||
isVisible = false
|
||||
setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
with(rightIconBtn2) {
|
||||
if (config.showRightIconBtn2 != null) {
|
||||
isVisible = true
|
||||
setText(config.showRightIconBtn2!!)
|
||||
changeTextColor {
|
||||
textUIColorToken = if (config.rightIconColorBtn2 != null) {
|
||||
context.getString(config.rightIconColorBtn2!!)
|
||||
} else {
|
||||
context.getString(R.string.color_txt_primary_normal)
|
||||
}
|
||||
}
|
||||
|
||||
expand(expendSize, expendSize)
|
||||
setOnClick(this) {
|
||||
config.onRightIconBtnBlock2?.invoke(this)
|
||||
}
|
||||
} else {
|
||||
isVisible = false
|
||||
setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
with(rightBtn) {
|
||||
if (config.showRightBtn != null) {
|
||||
isVisible = true
|
||||
setText(config.showRightBtn!!)
|
||||
setOnClick(this) {
|
||||
config.onRightBtnBlock?.invoke(this)
|
||||
}
|
||||
} else {
|
||||
isVisible = false
|
||||
setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.remax.visualnovel.app.di
|
||||
|
||||
|
||||
import com.remax.visualnovel.api.factory.ServiceFactory
|
||||
import com.remax.visualnovel.api.service.AIService
|
||||
import com.remax.visualnovel.api.service.BookService
|
||||
import com.remax.visualnovel.api.service.ChatService
|
||||
import com.remax.visualnovel.api.service.DictService
|
||||
import com.remax.visualnovel.api.service.LoginService
|
||||
import com.remax.visualnovel.api.service.MessageService
|
||||
import com.remax.visualnovel.api.service.OssService
|
||||
import com.remax.visualnovel.api.service.PayService
|
||||
import com.remax.visualnovel.api.service.UserService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* hilt注入service请求
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object ApiServiceModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun userService() = create<UserService>()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun loginService() = create<LoginService>()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun messageService() = create<MessageService>()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun dictService() = create<DictService>()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun bookService() = create<BookService>()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun aiService() = create<AIService>()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun ossService() = create<OssService>()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun payService() = create<PayService>()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun chatService() = create<ChatService>()
|
||||
|
||||
|
||||
private inline fun <reified T> create(): T {
|
||||
return ServiceFactory.createService()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.remax.visualnovel.app.di
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.remax.visualnovel.app.base.app.AppViewModelFactory
|
||||
import com.remax.visualnovel.app.base.app.CommonApplicationProxy
|
||||
import com.remax.visualnovel.app.viewmodel.AppGlobalViewModel
|
||||
import com.remax.visualnovel.app.viewmodel.AppIMViewModel
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* [AppIMViewModel] 提供者
|
||||
*/
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object ViewModelModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAppIMViewModel(factory: AppViewModelFactory) =
|
||||
ViewModelProvider(CommonApplicationProxy.viewModelStore, factory)[AppIMViewModel::class.java]
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAppGlobalViewModel(factory: AppViewModelFactory) =
|
||||
ViewModelProvider(CommonApplicationProxy.viewModelStore, factory)[AppGlobalViewModel::class.java]
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.remax.visualnovel.app.initializer
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
*
|
||||
* 启动类型
|
||||
*/
|
||||
enum class AppInitializerStartType {
|
||||
/**
|
||||
* 串行执行
|
||||
*/
|
||||
TYPE_SERIES,
|
||||
|
||||
/**
|
||||
* 并发执行
|
||||
*/
|
||||
TYPE_PARALLEL,
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.remax.visualnovel.app.initializer
|
||||
|
||||
import com.remax.visualnovel.app.initializer.di.AppInitializerPriority
|
||||
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
* 后续都需要依赖此框架
|
||||
*/
|
||||
interface AppInitializers {
|
||||
/**
|
||||
* 初始化代码
|
||||
*/
|
||||
fun init()
|
||||
|
||||
/**
|
||||
* @return 初始化类型
|
||||
*/
|
||||
fun getStartType(): AppInitializerStartType = AppInitializerStartType.TYPE_SERIES
|
||||
|
||||
/**
|
||||
* TYPE_SERIES 类型时,权重越大,越先执行
|
||||
*/
|
||||
fun widget(): Int = AppInitializerPriority.SERIES_1.priority
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.remax.visualnovel.app.initializer
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import com.remax.visualnovel.app.initializer.di.AppInitializerEntryPoint
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
*/
|
||||
class AppInitializersProvider @Inject constructor(private val application: Application) {
|
||||
|
||||
private val TAG = "AppInitializersProvider"
|
||||
|
||||
private val initializers: Set<AppInitializers> by lazy {
|
||||
EntryPointAccessors.fromApplication(application, AppInitializerEntryPoint::class.java)
|
||||
.getAppInitializers()
|
||||
}
|
||||
|
||||
fun startInit() {
|
||||
val seriesList = initializers.filter {
|
||||
it.getStartType() == AppInitializerStartType.TYPE_SERIES
|
||||
}.sortedByDescending { it.widget() }
|
||||
val parallelList =
|
||||
initializers.filter { it.getStartType() == AppInitializerStartType.TYPE_PARALLEL }
|
||||
Log.d(TAG, "AppInitializersProvider 开始执行 并行")
|
||||
parallelList.parallelStream().forEach {
|
||||
MainScope().launch {
|
||||
Log.d(TAG, "AppInitializersProvider $it 初始化模块协程开始执行: ${this.coroutineContext}")
|
||||
it.init()
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "AppInitializersProvider 结束执行 并行")
|
||||
seriesList.forEach {
|
||||
it.init()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.remax.visualnovel.app.initializer.di
|
||||
|
||||
|
||||
import com.remax.visualnovel.app.initializer.AppInitializers
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
* 启动框架容器注解
|
||||
*/
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface AppInitializerEntryPoint {
|
||||
fun getAppInitializers(): Set<AppInitializers>
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.remax.visualnovel.app.initializer.di
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/6/12
|
||||
*/
|
||||
enum class AppInitializerPriority(val priority: Int) {
|
||||
/**
|
||||
* 并发,无优先级
|
||||
*/
|
||||
PARALLEL(1),
|
||||
|
||||
/**
|
||||
* 串行,数字越大,优先级越高
|
||||
*/
|
||||
SERIES_1(1),
|
||||
SERIES_2(2),
|
||||
SERIES_3(3),
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.remax.visualnovel.app.initializer.di
|
||||
|
||||
import android.app.Application
|
||||
import com.remax.visualnovel.app.initializer.AppInitializers
|
||||
import com.remax.visualnovel.app.initializer.impl.ActivityLifecycleInitializer
|
||||
import com.remax.visualnovel.app.initializer.impl.LocalDataInitializer
|
||||
import com.remax.visualnovel.app.initializer.impl.RouterInitializer
|
||||
import com.remax.visualnovel.app.initializer.impl.SystemInitializer
|
||||
import com.remax.visualnovel.app.initializer.impl.ThirdInitializer
|
||||
import com.remax.visualnovel.app.initializer.impl.UserInitializer
|
||||
import com.remax.visualnovel.app.viewmodel.AppGlobalViewModel
|
||||
import com.remax.visualnovel.app.viewmodel.AppIMViewModel
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
*
|
||||
* 添加容器注入
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AppInitializersModule {
|
||||
|
||||
@Provides
|
||||
fun provideAppInitializers(application: Application,
|
||||
appIMViewModel: AppIMViewModel,
|
||||
appGlobalViewModel: AppGlobalViewModel
|
||||
): Set<AppInitializers> = setOf(
|
||||
// FirebaseInitializer(application), TODO- add firebase support later
|
||||
UserInitializer(application),
|
||||
//JsInitializer(application),
|
||||
LocalDataInitializer(application),
|
||||
RouterInitializer(application),
|
||||
ThirdInitializer(application),
|
||||
ActivityLifecycleInitializer(application, appIMViewModel),
|
||||
SystemInitializer(application, appGlobalViewModel)
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -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,27 @@
|
|||
package com.remax.visualnovel.app.initializer.impl
|
||||
|
||||
import android.app.Application
|
||||
import com.remax.visualnovel.app.initializer.AppInitializers
|
||||
import com.remax.visualnovel.app.initializer.di.AppInitializerPriority
|
||||
import com.remax.visualnovel.utils.datastore.IDataStoreOwner
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
* 本地数据保存相关初始化
|
||||
*/
|
||||
class LocalDataInitializer(val application: Application) : AppInitializers {
|
||||
|
||||
override fun init() {
|
||||
//初始化mmkv
|
||||
val dir = application.filesDir.absolutePath + "/mmkv_epal"
|
||||
MMKV.initialize(application, dir)
|
||||
//初始化datastore
|
||||
IDataStoreOwner.application = application
|
||||
}
|
||||
|
||||
override fun widget(): Int {
|
||||
return AppInitializerPriority.SERIES_3.priority
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.remax.visualnovel.app.initializer.impl
|
||||
|
||||
import android.app.Application
|
||||
import com.alibaba.android.arouter.launcher.ARouter
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.app.initializer.AppInitializers
|
||||
import com.remax.visualnovel.app.initializer.di.AppInitializerPriority
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
* 路由相关初始化
|
||||
*/
|
||||
class RouterInitializer(val application: Application) : AppInitializers {
|
||||
|
||||
override fun init() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
ARouter.openLog()
|
||||
ARouter.openDebug()
|
||||
}
|
||||
ARouter.init(application)
|
||||
}
|
||||
|
||||
override fun widget(): Int {
|
||||
return AppInitializerPriority.SERIES_3.priority
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.remax.visualnovel.app.initializer.impl
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.webkit.WebView
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.remax.visualnovel.app.ProcessLifecycleObserver
|
||||
import com.remax.visualnovel.app.initializer.AppInitializers
|
||||
import com.remax.visualnovel.app.initializer.di.AppInitializerPriority
|
||||
import com.remax.visualnovel.app.viewmodel.AppGlobalViewModel
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
* 系统相关初始化
|
||||
*/
|
||||
class SystemInitializer(val application: Application, val appGlobalViewModel: AppGlobalViewModel) : AppInitializers {
|
||||
|
||||
override fun init() {
|
||||
//监听application生命周期
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(ProcessLifecycleObserver)
|
||||
ProcessLifecycleObserver.setAppGlobalViewModel(appGlobalViewModel)
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
//启用矢量图兼容
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
var processName = ""
|
||||
(application.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager)
|
||||
?.runningAppProcesses
|
||||
?.asSequence()
|
||||
?.forEach { processInfo ->
|
||||
if (processInfo.pid == android.os.Process.myPid()) {
|
||||
processName = processInfo.processName
|
||||
}
|
||||
}
|
||||
if (processName != application.packageName) {
|
||||
WebView.setDataDirectorySuffix(processName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun widget(): Int {
|
||||
return AppInitializerPriority.SERIES_2.priority
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
package com.remax.visualnovel.app.initializer.impl
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.http.HttpResponseCache
|
||||
import android.os.Environment
|
||||
import android.text.TextUtils
|
||||
import com.chad.library.adapter.base.module.LoadMoreModuleConfig
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
import com.remax.visualnovel.app.delegate.ToolbarViewDelegate
|
||||
import com.remax.visualnovel.app.initializer.AppInitializers
|
||||
import com.remax.visualnovel.app.widget.CustomLoadMoreView
|
||||
import com.remax.visualnovel.app.widget.LoadMoreFooter
|
||||
import com.remax.visualnovel.app.widget.RefreshHeader
|
||||
import com.remax.visualnovel.utils.NotLoggingTree
|
||||
import com.dylanc.loadingstateview.LoadingStateView
|
||||
import com.github.boybeak.skbglobal.SoftKeyboardGlobal
|
||||
import com.github.sahasbhop.apngview.ApngImageLoader
|
||||
import com.lzf.easyfloat.EasyFloat
|
||||
import com.pengxr.modular.eventbus.facade.launcher.IEventListener
|
||||
import com.pengxr.modular.eventbus.facade.launcher.ModularEventBus
|
||||
import com.pengxr.modular.eventbus.facade.template.BaseEvent
|
||||
import com.scwang.smart.refresh.layout.SmartRefreshLayout
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
* 三方库相关初始化
|
||||
*/
|
||||
class ThirdInitializer(val application: Application) : AppInitializers {
|
||||
|
||||
override fun init() {
|
||||
ApngImageLoader.getInstance().init(application)
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
} else {
|
||||
Timber.plant(NotLoggingTree())
|
||||
}
|
||||
SoftKeyboardGlobal.install(application, false)
|
||||
|
||||
LoadingStateView.setViewDelegatePool {
|
||||
register(ToolbarViewDelegate())
|
||||
}
|
||||
/**
|
||||
* SVGA http缓存
|
||||
*/
|
||||
val cacheDir = File(application.cacheDir, "http")
|
||||
HttpResponseCache.install(cacheDir, 1024 * 1024 * 200)
|
||||
EasyFloat.init(application, BuildConfig.DEBUG)
|
||||
|
||||
LoadMoreModuleConfig.defLoadMoreView = CustomLoadMoreView()
|
||||
//设置全局默认配置(优先级最低,会被其他设置覆盖)
|
||||
SmartRefreshLayout.setDefaultRefreshInitializer { _, layout -> //全局设置(优先级最低)
|
||||
layout.setEnableLoadMore(false)
|
||||
layout.setEnableAutoLoadMore(false)
|
||||
layout.setEnableOverScrollDrag(true)
|
||||
layout.setEnableOverScrollBounce(true)
|
||||
layout.setEnableLoadMoreWhenContentNotFull(false)
|
||||
layout.setEnableScrollContentWhenRefreshed(true)
|
||||
layout.setHeaderMaxDragRate(3.0f)
|
||||
}
|
||||
SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, layout -> //全局设置header
|
||||
layout.setEnableHeaderTranslationContent(true)
|
||||
RefreshHeader(context)
|
||||
}
|
||||
SmartRefreshLayout.setDefaultRefreshFooterCreator { context, layout -> //全局设置footer
|
||||
layout.setEnableFooterTranslationContent(true)
|
||||
LoadMoreFooter(context)
|
||||
}
|
||||
|
||||
ModularEventBus.debug(BuildConfig.DEBUG)
|
||||
.throwNullEventException(false)
|
||||
.setEventListener(object : IEventListener {
|
||||
override fun <T> onEventPost(eventName: String, event: BaseEvent<T>, data: T?) {
|
||||
Timber.d("ModularEventBus发送事件 eventName: $eventName, event=$event data=$data")
|
||||
}
|
||||
})
|
||||
|
||||
//NIMClient.initV2(application, getSDKOptions(application))
|
||||
initLocalNimString()
|
||||
}
|
||||
|
||||
/*private fun getSDKOptions(context: Context): SDKOptions {
|
||||
val options = SDKOptions()
|
||||
with(options) {
|
||||
this.appKey = "2d6abc076f434fc52320c7118de5fead"
|
||||
enableV2CloudConversation = true
|
||||
//在线多端同步未读数
|
||||
sessionReadAck = true
|
||||
// 采用异步加载SDK
|
||||
asyncInitSDK = true
|
||||
//禁止后台进程唤醒UI进程
|
||||
disableAwake = true
|
||||
useXLog = true
|
||||
enableBackOffReconnectStrategy = true
|
||||
checkManifestConfig = BuildConfig.DEBUG
|
||||
loginCustomTag = ClientType.Android.toString()
|
||||
notifyStickTopSession = true
|
||||
mixPushConfig = MixPushConfig().apply {
|
||||
fcmCertificateName = "VisualNovel"
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
sdkStorageRootPath = getAppCacheDir(context) + "/nim"
|
||||
}
|
||||
}
|
||||
return options
|
||||
}*/
|
||||
|
||||
/**
|
||||
* 配置 APP 保存图片/语音/文件/log等数据的目录
|
||||
* 这里示例用SD卡的应用扩展存储目录
|
||||
*/
|
||||
private fun getAppCacheDir(context: Context): String? {
|
||||
var storageRootPath: String? = null
|
||||
try {
|
||||
// SD卡应用扩展存储区(APP卸载后,该目录下被清除,用户也可以在设置界面中手动清除),请根据APP对数据缓存的重要性及生命周期来决定是否采用此缓存目录.
|
||||
// 该存储区在API 19以上不需要写权限,即可配置 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>
|
||||
if (context.externalCacheDir != null) {
|
||||
storageRootPath = context.externalCacheDir!!.canonicalPath
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
if (TextUtils.isEmpty(storageRootPath)) {
|
||||
// SD卡应用公共存储区(APP卸载后,该目录不会被清除,下载安装APP后,缓存数据依然可以被加载。SDK默认使用此目录),该存储区域需要写权限!
|
||||
storageRootPath = Environment.getExternalStorageDirectory().toString() + "/" + context.packageName
|
||||
}
|
||||
return storageRootPath
|
||||
}
|
||||
|
||||
private fun initLocalNimString() {
|
||||
/*NimStrings.DEFAULT.apply {
|
||||
status_bar_multi_messages_incoming = application.getString(R.string.status_bar_multi_messages_incoming)
|
||||
status_bar_ticker_text = application.getString(R.string.status_bar_ticker_text)
|
||||
status_bar_image_message = application.getString(R.string.status_bar_image_message)
|
||||
status_bar_audio_message = application.getString(R.string.status_bar_audio_message)
|
||||
status_bar_video_message = application.getString(R.string.status_bar_video_message)
|
||||
status_bar_file_message = application.getString(R.string.status_bar_file_message)
|
||||
status_bar_location_message = application.getString(R.string.status_bar_location_message)
|
||||
status_bar_notification_message = application.getString(R.string.status_bar_notification_message)
|
||||
status_bar_avchat_message = application.getString(R.string.status_bar_avchat_message)
|
||||
status_bar_tip_message = application.getString(R.string.status_bar_tip_message)
|
||||
status_bar_custom_message = application.getString(R.string.status_bar_custom_message)
|
||||
status_bar_unsupported_message = application.getString(R.string.status_bar_unsupported_message)
|
||||
status_bar_hidden_message_content = application.getString(R.string.status_bar_hidden_message_content)
|
||||
status_bar_hidden_message_title = application.getString(R.string.status_bar_hidden_message_title)
|
||||
}*/
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.remax.visualnovel.app.initializer.impl
|
||||
|
||||
import android.app.Application
|
||||
import com.remax.visualnovel.manager.login.LoginManager
|
||||
import com.remax.visualnovel.app.initializer.AppInitializers
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/5/11
|
||||
* user相关初始化
|
||||
*/
|
||||
class UserInitializer(val application: Application) : AppInitializers {
|
||||
|
||||
/**
|
||||
* 初始化必须放在本地数据存储框架初始化之后才行
|
||||
*/
|
||||
override fun init() {
|
||||
LoginManager.init()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.remax.visualnovel.app.initializer.utils
|
||||
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/10/18
|
||||
*/
|
||||
object FirebaseHelper {
|
||||
|
||||
fun getToken(tokenCallback: ((String?) -> Unit)? = null) {
|
||||
FirebaseMessaging.getInstance().token.addOnCompleteListener { task: Task<String?> ->
|
||||
try {
|
||||
Timber.d("firebase CompleteListener isSuccessful : %s", task.isSuccessful)
|
||||
if (task.isSuccessful && task.result != null) {
|
||||
val token = task.result
|
||||
Timber.d("firebase token : %s", token)
|
||||
tokenCallback?.invoke(token)
|
||||
} else {
|
||||
tokenCallback?.invoke(null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.d("firebase token exception: %s", e.localizedMessage)
|
||||
tokenCallback?.invoke(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.remax.visualnovel.app.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.remax.visualnovel.manager.login.LoginManager
|
||||
import com.remax.visualnovel.repository.api.MessageRepository
|
||||
import com.remax.visualnovel.repository.api.UserRepository
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/11/1
|
||||
*/
|
||||
@HiltViewModel
|
||||
class AppGlobalViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val userRepository: UserRepository,
|
||||
private val messageRepository: MessageRepository
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 更新firebase设备码到后端,推送用
|
||||
*/
|
||||
fun updateTerminal(terminalCode: String?) {
|
||||
if (LoginManager.isLogin) {
|
||||
viewModelScope.launch {
|
||||
// userRepository.updateTerminal(terminalCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.remax.visualnovel.app.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.gson.Gson
|
||||
import com.remax.visualnovel.utils.TimeUtils
|
||||
|
||||
/*import com.netease.nimlib.sdk.NIMClient
|
||||
import com.netease.nimlib.sdk.Observer
|
||||
import com.netease.nimlib.sdk.StatusCode
|
||||
import com.netease.nimlib.sdk.msg.MsgServiceObserve
|
||||
import com.netease.nimlib.sdk.msg.constant.MsgTypeEnum
|
||||
import com.netease.nimlib.sdk.msg.model.IMMessage*/
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/10/18
|
||||
* [Application]生命周期内的[AndroidViewModel]
|
||||
*/
|
||||
@HiltViewModel
|
||||
class AppIMViewModel @Inject constructor(application: Application) : AndroidViewModel(application),
|
||||
DefaultLifecycleObserver {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.remax.visualnovel.app.viewmodel.base
|
||||
|
||||
import com.remax.visualnovel.app.viewmodel.base.UserViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/10/28
|
||||
*
|
||||
* 应用相关viewmodel的父类
|
||||
*/
|
||||
@HiltViewModel
|
||||
open class AppViewModel @Inject constructor() : UserViewModel() {
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.remax.visualnovel.app.viewmodel.base
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/10/27
|
||||
*/
|
||||
open class BaseViewModel : ViewModel() {
|
||||
|
||||
open fun onStart() {
|
||||
}
|
||||
|
||||
open fun onDestroy() {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
package com.remax.visualnovel.app.viewmodel.base
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import com.amazonaws.auth.BasicSessionCredentials
|
||||
import com.amazonaws.mobileconnectors.s3.transferutility.TransferListener
|
||||
import com.amazonaws.mobileconnectors.s3.transferutility.TransferState
|
||||
import com.amazonaws.mobileconnectors.s3.transferutility.TransferUtility
|
||||
import com.amazonaws.services.s3.AmazonS3Client
|
||||
import com.amazonaws.services.s3.S3ClientOptions
|
||||
import com.amazonaws.services.s3.model.ObjectMetadata
|
||||
import com.remax.visualnovel.R
|
||||
import com.remax.visualnovel.app.base.app.CommonApplicationProxy
|
||||
import com.remax.visualnovel.constant.StatusCode
|
||||
import com.remax.visualnovel.entity.request.ImgCheckDTO
|
||||
import com.remax.visualnovel.entity.response.BucketBean
|
||||
import com.remax.visualnovel.entity.response.base.ApiFailedResponse
|
||||
import com.remax.visualnovel.entity.response.base.ApiSuccessResponse
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import com.remax.visualnovel.extension.resumeWithActive
|
||||
import com.remax.visualnovel.repository.api.OssRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/11/9
|
||||
*
|
||||
* oss上传相关
|
||||
*/
|
||||
@HiltViewModel
|
||||
open class OssViewModel @Inject constructor() : UserViewModel() {
|
||||
|
||||
@Inject
|
||||
lateinit var ossRepository: OssRepository
|
||||
|
||||
data class LoadFileData(
|
||||
var isSuccess: Boolean,
|
||||
val isViolation: Boolean = false,
|
||||
val errorMsg: String = "",
|
||||
var urlPath: String = "",
|
||||
var filePath: String = "",
|
||||
var width: Int = 0,
|
||||
var height: Int = 0
|
||||
)
|
||||
|
||||
data class FileUpLoadRes(
|
||||
val loadFileData: LoadFileData,
|
||||
val fileOption: FileOption? = null,
|
||||
)
|
||||
|
||||
data class FileOption(
|
||||
val path: String,
|
||||
val urlPath: String,
|
||||
val ossType: String,
|
||||
val width: Int,
|
||||
val height: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* 请求oss的token
|
||||
*/
|
||||
suspend fun getBucketToken(postfix: String, ossType: String): Response<BucketBean> {
|
||||
return ossRepository.getS3Bucket(ossType, postfix)
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂起函数上传图片
|
||||
* @param filePath String
|
||||
* @param ossType String
|
||||
* @param isImg Boolean
|
||||
* @param checkNSFW Boolean 是否鉴黄
|
||||
* @param checkRealPerson Boolean 是否鉴定真人
|
||||
* @param checkKid Boolean 是否鉴定儿童
|
||||
* @return Response<LoadFileData> 封装成服务器返回一致类型处理
|
||||
*/
|
||||
suspend fun ossUploadFile(
|
||||
filePath: String,
|
||||
ossType: String,
|
||||
isImg: Boolean = true,
|
||||
checkNSFW: Boolean = true,
|
||||
checkRealPerson: Boolean = false,
|
||||
checkKid: Boolean = false,
|
||||
token: BucketBean? = null
|
||||
): Response<LoadFileData> {
|
||||
/**
|
||||
* 获取S3 STS Token对象
|
||||
*/
|
||||
var s3BucketRes = token
|
||||
if (s3BucketRes == null) {
|
||||
val postfix = if (filePath.isNotEmpty()) filePath.substring(filePath.lastIndexOf(".") + 1) else "png"
|
||||
val getTokenRes = getBucketToken(postfix, ossType)
|
||||
//请求s3 token失败
|
||||
if (!getTokenRes.isApiSuccess) {
|
||||
return ApiFailedResponse(
|
||||
errorMsg = getTokenRes.errorMsg,
|
||||
errorData = createNormalErrorFileData(filePath).loadFileData
|
||||
)
|
||||
} else {
|
||||
s3BucketRes = getTokenRes.data!!
|
||||
}
|
||||
}
|
||||
val uploadRes = uploadFile(s3BucketRes, isImg, filePath, ossType)
|
||||
//上传图片失败
|
||||
if (!uploadRes.loadFileData.isSuccess) {
|
||||
return ApiFailedResponse(errorMsg = uploadRes.loadFileData.errorMsg, errorData = uploadRes.loadFileData)
|
||||
}
|
||||
//如果不是图片 或者 不需要鉴黄、鉴定真人、鉴定儿童,直接返回成功结果
|
||||
if (!isImg || (!checkNSFW && !checkRealPerson && !checkKid)) {
|
||||
return ApiSuccessResponse(uploadRes.loadFileData)
|
||||
}
|
||||
val fileOption = uploadRes.fileOption!!
|
||||
val checkDTO = ImgCheckDTO(fileOption.ossType, fileOption.path)
|
||||
//鉴黄
|
||||
val checkNSFWRes = if (checkNSFW) ossRepository.checkS3Img(checkDTO) else ApiSuccessResponse()
|
||||
return when {
|
||||
checkNSFWRes.isApiSuccess -> {
|
||||
ApiSuccessResponse(uploadRes.loadFileData)
|
||||
}
|
||||
|
||||
else -> {
|
||||
ApiFailedResponse(StatusCode.UPLOAD_FILE_VIOLATION.code, checkNSFWRes.errorMsg, uploadRes.loadFileData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装上传失败的实体
|
||||
* @param filePath String 本地图片地址
|
||||
* @return FileUpLoadRes
|
||||
*/
|
||||
private fun createNormalErrorFileData(filePath: String) = FileUpLoadRes(
|
||||
LoadFileData(
|
||||
isSuccess = false,
|
||||
errorMsg = CommonApplicationProxy.application.getString(R.string.upload_error),
|
||||
filePath = filePath
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* 协程处理亚马逊上传图片
|
||||
*
|
||||
* 使用带取消回调的协程,当上传回调时,需要判断协程isActive以防报错崩溃
|
||||
* @param stsToken BucketBean 授权信息
|
||||
* @param isImg Boolean 是否是图片
|
||||
* @param filePath String 本地地址
|
||||
* @param ossType String 上传类型
|
||||
* @return FileUpLoadRes 返回结果封装
|
||||
*/
|
||||
private suspend fun uploadFile(
|
||||
stsToken: BucketBean,
|
||||
isImg: Boolean,
|
||||
filePath: String,
|
||||
ossType: String,
|
||||
) = suspendCancellableCoroutine {
|
||||
it.invokeOnCancellation { _ ->
|
||||
it.resumeWithActive(createNormalErrorFileData(filePath))
|
||||
}
|
||||
|
||||
val awsCreds = BasicSessionCredentials(
|
||||
stsToken.accessKeyId,
|
||||
stsToken.accessKeySecret,
|
||||
stsToken.securityToken
|
||||
)
|
||||
val uploadClient = AmazonS3Client(
|
||||
awsCreds,
|
||||
com.amazonaws.regions.Region.getRegion(stsToken.region)
|
||||
).apply {
|
||||
setS3ClientOptions(
|
||||
S3ClientOptions.builder()
|
||||
.setAccelerateModeEnabled(false)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
val transferUtility = TransferUtility.builder()
|
||||
.s3Client(uploadClient)
|
||||
.context(CommonApplicationProxy.application)
|
||||
.build()
|
||||
|
||||
val fileName = filePath.substring(filePath.lastIndexOf("/") + 1)
|
||||
val path = if (stsToken.path.endsWith("*")) {
|
||||
stsToken.path.replace("*", fileName)
|
||||
} else {
|
||||
stsToken.path
|
||||
}
|
||||
val urlPath = if (stsToken.urlPath.endsWith("*")) {
|
||||
stsToken.urlPath.replace("*", fileName)
|
||||
} else {
|
||||
stsToken.urlPath
|
||||
}
|
||||
Timber.d("oss上传 - AmazonS3 Token path:$path urlPath:$urlPath")
|
||||
|
||||
val obj = ObjectMetadata()
|
||||
obj.addUserMetadata("x-amz-tagging", "temp=1")
|
||||
|
||||
val transferListener = object :
|
||||
TransferListener {
|
||||
override fun onStateChanged(id: Int, state: TransferState?) {
|
||||
Timber.d("oss上传 - AmazonS3 onStateChanged:$state")
|
||||
Timber.d("oss上传 - 协程状态 isActive: ${it.isActive} isCancelled: ${it.isCancelled} isCompleted: ${it.isCompleted}")
|
||||
when (state) {
|
||||
TransferState.COMPLETED -> {
|
||||
//此方法是上传图片完成后再打标签
|
||||
// uploadClient.setObjectTagging(SetObjectTaggingRequest(stsToken.bucket, stsToken.path, ObjectTagging(listOf(Tag("temp", "1")))))
|
||||
if (it.isActive) {
|
||||
if (isImg) {
|
||||
val options = BitmapFactory.Options()
|
||||
options.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFile(filePath, options)
|
||||
val wid = options.outWidth
|
||||
val hei = options.outHeight
|
||||
val res = FileUpLoadRes(
|
||||
LoadFileData(
|
||||
isSuccess = true,
|
||||
urlPath = urlPath,
|
||||
width = wid,
|
||||
height = hei,
|
||||
filePath = filePath
|
||||
),
|
||||
FileOption(path, urlPath, ossType, wid, hei)
|
||||
)
|
||||
it.resumeWithActive(res)
|
||||
} else {
|
||||
val res = FileUpLoadRes(
|
||||
LoadFileData(
|
||||
isSuccess = true,
|
||||
urlPath = urlPath,
|
||||
filePath = filePath
|
||||
)
|
||||
)
|
||||
it.resumeWithActive(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TransferState.FAILED, TransferState.CANCELED -> {
|
||||
it.resumeWithActive(createNormalErrorFileData(filePath))
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onProgressChanged(id: Int, bytesCurrent: Long, bytesTotal: Long) {
|
||||
Timber.d("oss上传 - AmazonS3 onProgressChanged - bytesTotal:${bytesTotal} - bytesCurrent:${bytesCurrent}")
|
||||
}
|
||||
|
||||
override fun onError(id: Int, ex: Exception?) {
|
||||
Timber.d("oss上传 - AmazonS3 onError:${ex?.localizedMessage} - id:$id")
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
filePath.isNotEmpty() -> {
|
||||
val file = File(filePath)
|
||||
if (!file.exists()) {
|
||||
it.cancel()
|
||||
}
|
||||
Timber.d("oss上传 - 上传文件大小 ${file.length() / 1024}")
|
||||
transferUtility.upload(stsToken.bucket, path, file, obj)
|
||||
.setTransferListener(transferListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkText(content: String): Response<Any> = ossRepository.checkText(content)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.remax.visualnovel.app.viewmodel.base
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/10/28
|
||||
*
|
||||
* 钱包。支付相关父类
|
||||
*/
|
||||
@HiltViewModel
|
||||
open class PayViewModel @Inject constructor() : BaseViewModel() {
|
||||
|
||||
// @Inject
|
||||
// lateinit var walletRepository: WalletRepository
|
||||
//
|
||||
// @Inject
|
||||
// lateinit var payRepository: PayRepository
|
||||
//
|
||||
// private val _walletFlow = MutableSharedFlow<Response<Wallet>>()
|
||||
// val walletFlow = _walletFlow.asSharedFlow()
|
||||
//
|
||||
// suspend fun getMyWallet(): Response<Wallet> {
|
||||
// return walletRepository.getMyWallet().apply { _walletFlow.emit(this) }
|
||||
// }
|
||||
//
|
||||
// suspend fun checkOut(tradeNo: String) = payRepository.checkOut(tradeNo)
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.remax.visualnovel.app.viewmodel.base
|
||||
|
||||
|
||||
import com.remax.visualnovel.R
|
||||
import com.remax.visualnovel.app.base.app.CommonApplicationProxy
|
||||
import com.remax.visualnovel.entity.response.User
|
||||
import com.remax.visualnovel.entity.response.base.ApiFailedResponse
|
||||
import com.remax.visualnovel.entity.response.base.ApiSuccessResponse
|
||||
import com.remax.visualnovel.entity.response.base.Response
|
||||
import com.remax.visualnovel.repository.api.LoginRepository
|
||||
import com.remax.visualnovel.repository.api.UserRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by HJW on 2022/10/28
|
||||
*
|
||||
* User相关viewmodel的父类
|
||||
*/
|
||||
@HiltViewModel
|
||||
open class UserViewModel @Inject constructor() : BaseViewModel() {
|
||||
|
||||
@Inject
|
||||
lateinit var userRepository: UserRepository
|
||||
|
||||
private val _userInfoFlow = MutableStateFlow<Response<User>>(Response())
|
||||
val userInfoFlow: StateFlow<Response<User>> = _userInfoFlow.asStateFlow()
|
||||
|
||||
suspend fun getMyBaseInfo(): Response<User> {
|
||||
return userRepository.getMyBaseInfo().also { _userInfoFlow.value = it }
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共返回,很多接口操作后需要再次请求用户数据
|
||||
*/
|
||||
protected suspend fun returnUserResponse(response: Response<*>): Response<User> {
|
||||
return if (response.isApiSuccess) {
|
||||
getMyBaseInfo()
|
||||
} else {
|
||||
ApiFailedResponse(response.errorCode, response.errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
//suspend fun getNimInfo() = userRepository.getNimInfo()
|
||||
|
||||
@Inject
|
||||
lateinit var loginRepository: LoginRepository
|
||||
|
||||
/**
|
||||
* 公共返回,检查nickname是否存在
|
||||
*/
|
||||
suspend fun <T> checkNickname(
|
||||
nickName: String?,
|
||||
exUserId: String? = null,
|
||||
apiCall: (suspend () -> Response<T>)? = null
|
||||
): Response<T> {
|
||||
val checkRes = loginRepository.checkUserNickname(nickName, exUserId)
|
||||
return if (checkRes.isApiSuccess) {
|
||||
if (checkRes.data == true) {
|
||||
ApiFailedResponse("", CommonApplicationProxy.application.getString(R.string.nickname_exist_error))
|
||||
} else {
|
||||
apiCall?.invoke() ?: ApiSuccessResponse()
|
||||
}
|
||||
} else {
|
||||
ApiFailedResponse(checkRes.errorCode, checkRes.errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.remax.visualnovel.app.widget
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.chad.library.adapter.base.loadmore.BaseLoadMoreView
|
||||
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
||||
import com.remax.visualnovel.R
|
||||
import com.remax.visualnovel.extension.setSize
|
||||
|
||||
/**
|
||||
* Created by HJW on 2020/9/18
|
||||
*/
|
||||
class CustomLoadMoreView(private val showLoading: Boolean = true) : BaseLoadMoreView() {
|
||||
|
||||
override fun getLoadComplete(holder: BaseViewHolder): View {
|
||||
return holder.getView(R.id.load_more_load_complete_view)
|
||||
}
|
||||
|
||||
override fun getLoadEndView(holder: BaseViewHolder): View {
|
||||
return holder.getView(R.id.load_more_load_end_view)
|
||||
}
|
||||
|
||||
override fun getLoadFailView(holder: BaseViewHolder): View {
|
||||
return holder.getView(R.id.load_more_load_fail_view)
|
||||
}
|
||||
|
||||
override fun getLoadingView(holder: BaseViewHolder): View {
|
||||
val loadingView = holder.getView<View>(R.id.load_more_loading_view)
|
||||
if (!showLoading) {
|
||||
loadingView.setSize(height = 0)
|
||||
}
|
||||
return loadingView
|
||||
}
|
||||
|
||||
override fun getRootView(parent: ViewGroup): View {
|
||||
// 整个 LoadMore 布局
|
||||
return LayoutInflater.from(parent.context).inflate(R.layout.view_load_more_common, parent, false)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
package com.remax.visualnovel.app.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.isVisible
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
import com.drake.brv.PageRefreshLayout
|
||||
import com.remax.visualnovel.R
|
||||
import com.remax.visualnovel.configs.NovelApplication
|
||||
import com.remax.visualnovel.databinding.LayoutEmptyBinding
|
||||
import com.remax.visualnovel.extension.setOnClick
|
||||
import com.remax.visualnovel.utils.spannablex.utils.dp
|
||||
|
||||
|
||||
fun PageRefreshLayout.setEmptyText(
|
||||
@StringRes emptyTextResId: Int = 0,
|
||||
@DrawableRes emptyIcon: Int = R.mipmap.icon_new_empty,
|
||||
topMargin: Int? = null,
|
||||
) {
|
||||
emptyLayout = R.layout.layout_empty
|
||||
stateEnabled = true
|
||||
onEmpty {
|
||||
LayoutEmptyBinding.bind(this).apply {
|
||||
if (emptyIcon != 0) {
|
||||
ivEmpty.setImageResource(emptyIcon)
|
||||
ivEmpty.isVisible = true
|
||||
}
|
||||
if (emptyTextResId != 0) {
|
||||
tvEmpty.setText(emptyTextResId)
|
||||
tvEmpty.isVisible = true
|
||||
}
|
||||
topMargin?.let {
|
||||
root.run {
|
||||
setPaddingRelative(paddingStart, topMargin.dp, paddingEnd, paddingBottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseQuickAdapter<*, *>.setMyEmptyView(
|
||||
@StringRes emptyTextResId: Int = 0,
|
||||
@DrawableRes emptyIcon: Int = R.mipmap.icon_new_empty,
|
||||
topMargin: Int? = null,
|
||||
@StringRes btnText: Int = 0,
|
||||
btnInvoke: (() -> Unit)? = null
|
||||
) {
|
||||
NovelApplication.getCurrentActivity()?.let { activity ->
|
||||
setEmptyView(
|
||||
EmptyView(activity).createEmptyView(emptyTextResId, emptyIcon, topMargin, btnText, btnInvoke)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyView(private val context: Context) {
|
||||
|
||||
private val viewBind: LayoutEmptyBinding = LayoutEmptyBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
fun createEmptyView(
|
||||
@StringRes emptyTextResId: Int = 0,
|
||||
@DrawableRes emptyIcon: Int = R.mipmap.icon_new_empty,
|
||||
topMargin: Int? = null,
|
||||
@StringRes btnText: Int = 0,
|
||||
btnInvoke: (() -> Unit)? = null
|
||||
): View {
|
||||
setEmptyView(emptyIcon, emptyTextResId)
|
||||
topMargin?.let {
|
||||
setMarginTop(it.dp)
|
||||
}
|
||||
if (btnText != 0) {
|
||||
setBtn(btnText, btnInvoke)
|
||||
}
|
||||
return viewBind.root
|
||||
}
|
||||
|
||||
private fun setEmptyView(@DrawableRes emptyIcon: Int = R.mipmap.icon_new_empty, @StringRes emptyTextResId: Int = 0): EmptyView {
|
||||
with(viewBind) {
|
||||
ivEmpty.isVisible = false
|
||||
emptyIcon.takeIf { it != 0 }?.let {
|
||||
ivEmpty.setImageResource(it)
|
||||
ivEmpty.isVisible = true
|
||||
}
|
||||
tvEmpty.isVisible = false
|
||||
emptyTextResId.takeIf { it != 0 }?.let {
|
||||
tvEmpty.text = context.getString(emptyTextResId)
|
||||
tvEmpty.isVisible = true
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun setBtn(@StringRes btnText: Int, btnInvoke: (() -> Unit)? = null): EmptyView {
|
||||
with(viewBind.btnEmptyMessage) {
|
||||
isVisible = true
|
||||
text = context.getString(btnText)
|
||||
setOnClick(this) {
|
||||
btnInvoke?.invoke()
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun setMarginTop(topMargin: Int): EmptyView {
|
||||
viewBind.root.run {
|
||||
setPaddingRelative(paddingStart, topMargin, paddingEnd, paddingBottom)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.remax.visualnovel.app.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.remax.visualnovel.R;
|
||||
import com.scwang.smart.refresh.layout.api.RefreshKernel;
|
||||
import com.scwang.smart.refresh.layout.api.RefreshLayout;
|
||||
import com.scwang.smart.refresh.layout.constant.RefreshState;
|
||||
import com.scwang.smart.refresh.layout.constant.SpinnerStyle;
|
||||
import com.scwang.smart.refresh.layout.util.SmartUtil;
|
||||
|
||||
public class LoadMoreFooter extends LinearLayout implements com.scwang.smart.refresh.layout.api.RefreshFooter {
|
||||
|
||||
public LoadMoreFooter(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public LoadMoreFooter(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs, 0);
|
||||
View.inflate(context, R.layout.load_more_loading_view, this);
|
||||
setMinimumHeight(SmartUtil.dp2px(50));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setNoMoreData(boolean noMoreData) {
|
||||
this.setVisibility(noMoreData ? View.GONE : View.VISIBLE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SpinnerStyle getSpinnerStyle() {
|
||||
return SpinnerStyle.Translate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryColors(int... colors) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportHorizontalDrag() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.remax.visualnovel.app.widget
|
||||
|
||||
import android.content.Context
|
||||
import com.remax.visualnovel.databinding.DialogLoadingBinding
|
||||
import com.remax.visualnovel.widget.dialoglib.LBindingDialog
|
||||
|
||||
class LoadingDialog {
|
||||
|
||||
private lateinit var dialog: LBindingDialog<DialogLoadingBinding>
|
||||
private var context: Context? = null
|
||||
|
||||
fun build(context: Context): LoadingDialog {
|
||||
this.context = context
|
||||
dialog = LBindingDialog(context,DialogLoadingBinding::inflate)
|
||||
dialog.with()
|
||||
.setCenter()
|
||||
.setBgRadius(4)
|
||||
.setWidth(80)
|
||||
.setHeight(80)
|
||||
dialog.setCancelable(false)
|
||||
dialog.setCanceledOnTouchOutside(false)
|
||||
return this
|
||||
}
|
||||
|
||||
fun getDialog(): LBindingDialog<DialogLoadingBinding> {
|
||||
return dialog
|
||||
}
|
||||
|
||||
fun show() {
|
||||
if (this::dialog.isInitialized) {
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
if (this::dialog.isInitialized) {
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
package com.remax.visualnovel.app.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
import com.remax.visualnovel.R;
|
||||
import com.scwang.smart.refresh.layout.api.RefreshKernel;
|
||||
import com.scwang.smart.refresh.layout.api.RefreshLayout;
|
||||
import com.scwang.smart.refresh.layout.constant.RefreshState;
|
||||
import com.scwang.smart.refresh.layout.constant.SpinnerStyle;
|
||||
import com.scwang.smart.refresh.layout.util.SmartUtil;
|
||||
|
||||
public class RefreshHeader extends LinearLayout implements com.scwang.smart.refresh.layout.api.RefreshHeader {
|
||||
private final LottieAnimationView mProgressView;//刷新动画视图
|
||||
|
||||
public RefreshHeader(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RefreshHeader(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs, 0);
|
||||
setGravity(Gravity.CENTER);
|
||||
mProgressView = new LottieAnimationView(context);
|
||||
mProgressView.setAnimation(R.raw.single_ring);
|
||||
mProgressView.setSafeMode(true);
|
||||
mProgressView.setRepeatCount(-1);
|
||||
addView(mProgressView, SmartUtil.dp2px(26), SmartUtil.dp2px(26));
|
||||
setMinimumHeight(SmartUtil.dp2px(30));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SpinnerStyle getSpinnerStyle() {
|
||||
return SpinnerStyle.Translate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryColors(int... colors) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {
|
||||
if (isDragging){
|
||||
float currPer = percent / 3.0f;
|
||||
if (currPer > 1.0f) currPer = 1.0f;
|
||||
float frameF = mProgressView.getMaxFrame() * currPer;
|
||||
int frame = (int) (frameF);
|
||||
mProgressView.setFrame(frame);
|
||||
mProgressView.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
|
||||
mProgressView.setRepeatCount(-1);
|
||||
mProgressView.setProgress(0f);
|
||||
mProgressView.playAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
|
||||
mProgressView.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mProgressView.cancelAnimation();
|
||||
}
|
||||
},1100);
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportHorizontalDrag() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
package com.remax.visualnovel.app.widget.tips
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.PopupWindow
|
||||
import com.remax.visualnovel.R
|
||||
import com.remax.visualnovel.databinding.PopwindowFeedsBackTipsBinding
|
||||
import com.remax.visualnovel.entity.request.AIFeedback
|
||||
import com.remax.visualnovel.extension.setOnClick
|
||||
|
||||
class TipsFeedbackWindow {
|
||||
|
||||
private var popupWindow: PopupWindow? = null
|
||||
|
||||
fun build(
|
||||
context: Context,
|
||||
optType: Int,
|
||||
clickCallback: (Int, optType: Int) -> Unit
|
||||
): TipsFeedbackWindow {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.popwindow_feeds_back_tips, null)
|
||||
popupWindow = PopupWindow(context).apply {
|
||||
isFocusable = true
|
||||
contentView = view
|
||||
isOutsideTouchable = true
|
||||
setBackgroundDrawable(ColorDrawable())
|
||||
}
|
||||
var currOptType = optType
|
||||
|
||||
PopwindowFeedsBackTipsBinding.bind(view).run {
|
||||
fun changeOptType() {
|
||||
val likeColorToken = context.getString(R.string.color_primary_variant_normal)
|
||||
val normalColorToken = context.getString(R.string.color_txt_primary_normal)
|
||||
val iconSize = 20
|
||||
val iconPadding = 16
|
||||
|
||||
fun updateLikeDislikeIcons(
|
||||
likeIconRes: String,
|
||||
likeColor: String,
|
||||
dislikeIconRes: String,
|
||||
dislikeColor: String,
|
||||
) {
|
||||
like.setIconFontDrawable(
|
||||
likeIconRes,
|
||||
iconColorToken = likeColor,
|
||||
iconSize = iconSize,
|
||||
iconPadding = iconPadding
|
||||
)
|
||||
dislike.setIconFontDrawable(
|
||||
dislikeIconRes,
|
||||
iconColorToken = dislikeColor,
|
||||
iconSize = iconSize,
|
||||
iconPadding = iconPadding
|
||||
)
|
||||
}
|
||||
|
||||
when (currOptType) {
|
||||
AIFeedback.LIKE -> updateLikeDislikeIcons(
|
||||
context.getString(R.string.icon_post_recommend_fill),
|
||||
likeColorToken,
|
||||
context.getString(R.string.icon_post_notrecommend),
|
||||
normalColorToken
|
||||
)
|
||||
|
||||
AIFeedback.DISLIKE -> updateLikeDislikeIcons(
|
||||
context.getString(R.string.icon_post_recommend),
|
||||
normalColorToken,
|
||||
context.getString(R.string.icon_post_notrecommend_fill),
|
||||
likeColorToken
|
||||
)
|
||||
|
||||
else -> updateLikeDislikeIcons(
|
||||
context.getString(R.string.icon_post_recommend),
|
||||
normalColorToken,
|
||||
context.getString(R.string.icon_post_notrecommend),
|
||||
normalColorToken
|
||||
)
|
||||
}
|
||||
|
||||
when (currOptType) {
|
||||
AIFeedback.LIKE -> {
|
||||
like.setIconFontDrawable(
|
||||
context.getString(R.string.icon_post_recommend_fill),
|
||||
iconColorToken = likeColorToken,
|
||||
iconSize = iconSize,
|
||||
iconPadding = iconPadding
|
||||
)
|
||||
dislike.setIconFontDrawable(
|
||||
context.getString(R.string.icon_post_notrecommend),
|
||||
iconColorToken = normalColorToken,
|
||||
iconSize = iconSize,
|
||||
iconPadding = iconPadding
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeOptType()
|
||||
setOnClick(copy, like, dislike) {
|
||||
when (this) {
|
||||
copy -> {
|
||||
clickCallback.invoke(0, currOptType)
|
||||
popupWindow?.dismiss()
|
||||
}
|
||||
|
||||
like -> {
|
||||
currOptType = if (currOptType == AIFeedback.LIKE) AIFeedback.NONE else AIFeedback.LIKE
|
||||
changeOptType()
|
||||
clickCallback.invoke(1, currOptType)
|
||||
}
|
||||
|
||||
dislike -> {
|
||||
currOptType = if (currOptType == AIFeedback.DISLIKE) AIFeedback.NONE else AIFeedback.DISLIKE
|
||||
changeOptType()
|
||||
clickCallback.invoke(2, currOptType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun showAsDropDown(view: View?) {
|
||||
if (popupWindow!!.isShowing) {
|
||||
popupWindow!!.dismiss()
|
||||
} else {
|
||||
popupWindow!!.showAsDropDown(view)
|
||||
}
|
||||
}
|
||||
|
||||
fun showAsDropDown(view: View?, xoff: Int, yoff: Int) {
|
||||
if (popupWindow!!.isShowing) {
|
||||
popupWindow!!.dismiss()
|
||||
} else {
|
||||
popupWindow!!.showAsDropDown(view, xoff, yoff)
|
||||
}
|
||||
}
|
||||
|
||||
fun showAtLocation(parent: View?, gravity: Int, x: Int, y: Int) {
|
||||
if (popupWindow!!.isShowing) {
|
||||
popupWindow!!.dismiss()
|
||||
} else {
|
||||
popupWindow!!.showAtLocation(parent, gravity, x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.remax.visualnovel.app.widget.tips
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.PopupWindow
|
||||
import androidx.annotation.StringRes
|
||||
import com.remax.visualnovel.R
|
||||
import com.remax.visualnovel.extension.setOnClick
|
||||
import com.remax.visualnovel.extension.setSize
|
||||
import com.remax.visualnovel.utils.spannablex.utils.dp
|
||||
import com.remax.visualnovel.widget.ui.IconFontTextView
|
||||
import com.remax.visualnovel.widget.uitoken.changeTextStyle
|
||||
|
||||
class TipsMoreWindow {
|
||||
data class TipsMoreUIData(
|
||||
@StringRes val titleRes: Int,
|
||||
@StringRes val iconRes: Int,
|
||||
)
|
||||
|
||||
private var popupWindow: PopupWindow? = null
|
||||
|
||||
fun build(
|
||||
context: Context,
|
||||
tipsData: List<TipsMoreUIData>?,
|
||||
clickCallback: (TipsMoreUIData) -> Unit = {}
|
||||
): TipsMoreWindow {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.popwindow_btn_tips, null)
|
||||
popupWindow = PopupWindow(context).apply {
|
||||
isFocusable = true
|
||||
contentView = view
|
||||
isOutsideTouchable = true
|
||||
setBackgroundDrawable(ColorDrawable())
|
||||
}
|
||||
val group = view.findViewById<LinearLayout>(R.id.group)
|
||||
group.removeAllViews()
|
||||
tipsData?.forEach { item ->
|
||||
val iconView = IconFontTextView(context)
|
||||
group.addView(iconView)
|
||||
iconView.apply {
|
||||
gravity = Gravity.START
|
||||
setSize(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
setPadding(8.dp, 12.dp, 8.dp, 12.dp)
|
||||
changeTextStyle {
|
||||
textUITextToken = context.getString(R.string.txt_label_l)
|
||||
textUIColorToken = context.getString(R.string.color_txt_primary_normal)
|
||||
|
||||
}
|
||||
setIconFontDrawable(
|
||||
startIconFont = context.getString(item.iconRes),
|
||||
iconColorToken = context.getString(R.string.color_txt_primary_normal),
|
||||
iconSize = 20,
|
||||
iconPadding = 16
|
||||
)
|
||||
setText(item.titleRes)
|
||||
setOnClick(this) {
|
||||
popupWindow?.dismiss()
|
||||
clickCallback(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun showAsDropDown(view: View?) {
|
||||
if (popupWindow!!.isShowing) {
|
||||
popupWindow!!.dismiss()
|
||||
} else {
|
||||
popupWindow!!.showAsDropDown(view)
|
||||
}
|
||||
}
|
||||
|
||||
fun showAsDropDown(view: View?, xoff: Int, yoff: Int) {
|
||||
if (popupWindow!!.isShowing) {
|
||||
popupWindow!!.dismiss()
|
||||
} else {
|
||||
popupWindow!!.showAsDropDown(view, xoff, yoff)
|
||||
}
|
||||
}
|
||||
|
||||
fun showAtLocation(parent: View?, gravity: Int, x: Int, y: Int) {
|
||||
if (popupWindow!!.isShowing) {
|
||||
popupWindow!!.dismiss()
|
||||
} else {
|
||||
popupWindow!!.showAtLocation(parent, gravity, x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.remax.visualnovel.app.widget.tips;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.remax.visualnovel.R;
|
||||
import com.remax.visualnovel.extension.ViewExtKt;
|
||||
|
||||
|
||||
public class TipsPopWindow {
|
||||
|
||||
private PopupWindow popupWindow;
|
||||
|
||||
public TipsPopWindow build(Context context, String content, int width) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.popwindow_tips, null);
|
||||
popupWindow = new PopupWindow(context);
|
||||
popupWindow.setFocusable(true);
|
||||
popupWindow.setContentView(view);
|
||||
if (width != ViewGroup.LayoutParams.MATCH_PARENT) {
|
||||
ViewExtKt.setMargin(view, 0, 0, 0, 0);
|
||||
TextView tv = view.findViewById(R.id.tvContent);
|
||||
textWidth = tv.getPaint().measureText(content);
|
||||
}
|
||||
popupWindow.setWidth(width);
|
||||
popupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
popupWindow.setOutsideTouchable(true);
|
||||
popupWindow.setBackgroundDrawable(new ColorDrawable());
|
||||
|
||||
TextView tvContent = view.findViewById(R.id.tvContent);
|
||||
tvContent.setText(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
private float textWidth;
|
||||
|
||||
public float getTextWidth() {
|
||||
return textWidth;
|
||||
}
|
||||
|
||||
public TipsPopWindow build(Context context, String content) {
|
||||
return build(context, content, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
}
|
||||
|
||||
public TipsPopWindow build(Context context, int resId) {
|
||||
return build(context, context.getString(resId));
|
||||
}
|
||||
|
||||
public void showAsDropDown(View view) {
|
||||
if (popupWindow.isShowing()) {
|
||||
popupWindow.dismiss();
|
||||
} else {
|
||||
popupWindow.showAsDropDown(view);
|
||||
}
|
||||
}
|
||||
|
||||
public void showAsDropDown(View view, int xoff, int yoff) {
|
||||
if (popupWindow.isShowing()) {
|
||||
popupWindow.dismiss();
|
||||
} else {
|
||||
popupWindow.showAsDropDown(view, xoff, yoff);
|
||||
}
|
||||
}
|
||||
|
||||
public void showAtLocation(View parent, int gravity, int x, int y) {
|
||||
if (popupWindow.isShowing()) {
|
||||
popupWindow.dismiss();
|
||||
} else {
|
||||
popupWindow.showAtLocation(parent, gravity, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.remax.visualnovel.app.widget.tips
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.PopupWindow
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import com.remax.visualnovel.R
|
||||
import com.remax.visualnovel.widget.ui.SwitchView
|
||||
|
||||
class TipsSwitchWindow {
|
||||
private var popupWindow: PopupWindow? = null
|
||||
|
||||
fun build(
|
||||
context: Context,
|
||||
@StringRes tips: Int,
|
||||
isChecked: Boolean = false,
|
||||
switchCallback: (SwitchView,Boolean) -> Unit
|
||||
): TipsSwitchWindow {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.popwindow_switch_tips, null)
|
||||
popupWindow = PopupWindow(context).apply {
|
||||
isFocusable = true
|
||||
contentView = view
|
||||
isOutsideTouchable = true
|
||||
setBackgroundDrawable(ColorDrawable())
|
||||
}
|
||||
view.findViewById<TextView>(R.id.tvContent).setText(tips)
|
||||
view.findViewById<SwitchView>(R.id.switchView).run {
|
||||
this.isChecked = isChecked
|
||||
setPressChanged { switchCallback(this,it) }
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun showAsDropDown(view: View?) {
|
||||
if (popupWindow!!.isShowing) {
|
||||
popupWindow!!.dismiss()
|
||||
} else {
|
||||
popupWindow!!.showAsDropDown(view)
|
||||
}
|
||||
}
|
||||
|
||||
fun showAsDropDown(view: View?, xoff: Int, yoff: Int) {
|
||||
if (popupWindow!!.isShowing) {
|
||||
popupWindow!!.dismiss()
|
||||
} else {
|
||||
popupWindow!!.showAsDropDown(view, xoff, yoff)
|
||||
}
|
||||
}
|
||||
|
||||
fun showAtLocation(parent: View?, gravity: Int, x: Int, y: Int) {
|
||||
if (popupWindow!!.isShowing) {
|
||||
popupWindow!!.dismiss()
|
||||
} else {
|
||||
popupWindow!!.showAtLocation(parent, gravity, x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.remax.visualnovel.configs
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import com.remax.visualnovel.app.base.app.ApplicationProxy
|
||||
import com.remax.visualnovel.app.base.app.CommonApplicationProxy
|
||||
import com.remax.visualnovel.app.initializer.AppInitializersProvider
|
||||
import com.remax.visualnovel.utils.StatusBarUtils
|
||||
import com.tencent.mmkv.MMKV
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@HiltAndroidApp
|
||||
class NovelApplication : MultiDexApplication() {
|
||||
|
||||
companion object {
|
||||
private var currentActivity: WeakReference<Activity>? = null
|
||||
|
||||
fun setCurrentActivity(activity: Activity?) {
|
||||
currentActivity = if (activity == null) null else WeakReference(activity)
|
||||
}
|
||||
|
||||
fun getCurrentActivity(): Activity? {
|
||||
return currentActivity?.get()
|
||||
}
|
||||
|
||||
private lateinit var instance: NovelApplication
|
||||
fun appContext(): Context = instance
|
||||
}
|
||||
|
||||
private val proxies = listOf<ApplicationProxy>(CommonApplicationProxy)
|
||||
|
||||
@Inject
|
||||
lateinit var appInitializersProvider: AppInitializersProvider
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
MultiDex.install(this)
|
||||
instance = this
|
||||
proxies.forEach { it.onCreate(this) }
|
||||
appInitializersProvider.startInit()
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
MMKV.onExit()
|
||||
super.onTerminate()
|
||||
proxies.forEach {
|
||||
it.onTerminate()
|
||||
}
|
||||
}
|
||||
|
||||
// 系统资源配置发生更改回调,例如主题模式,需要重新刷新多语言
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
StatusBarUtils.resetNavBarHeight()
|
||||
Timber.d("onConfigurationChanged ${newConfig.locales[0]}")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.remax.visualnovel.constant
|
||||
|
||||
/**
|
||||
* Created by HJW on 2021/12/7
|
||||
*/
|
||||
class AppConstant {
|
||||
|
||||
companion object {
|
||||
const val ANDROID = "android"
|
||||
const val APP_CLIENT = "ANDROID"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.remax.visualnovel.constant
|
||||
|
||||
import com.remax.visualnovel.BuildConfig
|
||||
|
||||
|
||||
class AppStatus {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* 是否是生产环境
|
||||
*/
|
||||
val isProduct
|
||||
get() = BuildConfig.FLAVOR == "product"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.remax.visualnovel.constant
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.remax.visualnovel.R
|
||||
|
||||
/**
|
||||
* 性别枚举
|
||||
*/
|
||||
enum class Gender(val value: Int, @StringRes val txtRes: Int) {
|
||||
MALE(0, R.string.male),
|
||||
FEMALE(1, R.string.female),
|
||||
NONCONFORMING(2, R.string.nonconforming),
|
||||
OTHER(2, R.string.other),
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.remax.visualnovel.constant
|
||||
|
||||
/**
|
||||
* Created by HJW on 2023/8/25
|
||||
*/
|
||||
class LockTypeConstant {
|
||||
companion object {
|
||||
const val PUBLIC = 1
|
||||
const val PRIVATE = 2
|
||||
|
||||
/**
|
||||
* 图片公开
|
||||
*/
|
||||
fun isOpen(pubType: Int?) = pubType != PRIVATE
|
||||
|
||||
/**
|
||||
* 图片是否解锁
|
||||
*/
|
||||
fun isUnLock(lockType: String?) = lockType != LOCK
|
||||
|
||||
const val LOCK = "LOCK"
|
||||
const val UNLOCK = "UNLOCK"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.remax.visualnovel.constant
|
||||
|
||||
/**
|
||||
* Created by HJW on 2025/7/21
|
||||
*/
|
||||
enum class StatusCode(val code:String) {
|
||||
/**
|
||||
* 登录过期
|
||||
*/
|
||||
TOKEN_EXPIRED("10050001"),
|
||||
|
||||
NO_ALBUM_PERMISSION("10010011"),
|
||||
|
||||
UNUSED_PURCHASE_TOKEN("1019"), //无效的支付凭据
|
||||
|
||||
AI_USER_NOT_EXIST("10010012"),
|
||||
|
||||
//余额不足
|
||||
INSUFFICIENT_BALANCE("INSUFFICIENT_BALANCE"),
|
||||
|
||||
/**
|
||||
* 前段自定义code
|
||||
*/
|
||||
UPLOAD_FILE_VIOLATION("112233")
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.remax.visualnovel.entity.imbean
|
||||
|
||||
import com.remax.visualnovel.entity.imbean.voice.IMVoice
|
||||
import com.netease.nimlib.sdk.v2.message.V2NIMMessage
|
||||
|
||||
/**
|
||||
* Created by HJW on 2025/8/19
|
||||
*/
|
||||
data class IMAIInMessage(
|
||||
override var message: V2NIMMessage?,
|
||||
val imVoice: IMVoice
|
||||
) : IMMessageWrapper(type = IN_TEXT_TYPE)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.remax.visualnovel.entity.imbean
|
||||
|
||||
import com.remax.visualnovel.entity.response.Character
|
||||
|
||||
/**
|
||||
* Created by HJW on 2025/8/19
|
||||
*/
|
||||
data class IMBaseInfoMessage(
|
||||
val character: Character?
|
||||
) : IMMessageWrapper(type = BASE_INFO)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.remax.visualnovel.entity.imbean
|
||||
|
||||
import com.remax.visualnovel.entity.imbean.raw.CustomCallData
|
||||
import com.netease.nimlib.sdk.v2.message.V2NIMMessage
|
||||
|
||||
/**
|
||||
* Created by HJW on 2025/8/19
|
||||
*/
|
||||
data class IMCallMessage(
|
||||
override var message: V2NIMMessage?,
|
||||
val call: CustomCallData?
|
||||
) : IMMessageWrapper(type = OUT_CALL_TYPE)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.remax.visualnovel.entity.imbean
|
||||
|
||||
import com.remax.visualnovel.entity.imbean.raw.CustomGiftData
|
||||
import com.netease.nimlib.sdk.v2.message.V2NIMMessage
|
||||
|
||||
/**
|
||||
* Created by HJW on 2025/8/19
|
||||
*/
|
||||
data class IMGiftMessage(
|
||||
override var message: V2NIMMessage?,
|
||||
val gift: CustomGiftData?
|
||||
) : IMMessageWrapper(type = OUT_GIFT_TYPE)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.remax.visualnovel.entity.imbean
|
||||
|
||||
import com.remax.visualnovel.entity.imbean.raw.CustomAlbumData
|
||||
import com.netease.nimlib.sdk.v2.message.V2NIMMessage
|
||||
|
||||
/**
|
||||
* Created by HJW on 2025/8/19
|
||||
*/
|
||||
data class IMInImageMessage(
|
||||
override var message: V2NIMMessage?,
|
||||
val albumData: CustomAlbumData?
|
||||
) : IMMessageWrapper(type = IN_IMAGE_TYPE)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.remax.visualnovel.entity.imbean
|
||||
|
||||
import com.remax.visualnovel.entity.imbean.raw.CustomLevelChangeData
|
||||
import com.netease.nimlib.sdk.v2.message.V2NIMMessage
|
||||
|
||||
/**
|
||||
* Created by HJW on 2025/8/19
|
||||
*/
|
||||
data class IMLevelMessage(
|
||||
override var message: V2NIMMessage?,
|
||||
val level: CustomLevelChangeData?
|
||||
) : IMMessageWrapper(type = HEART_BEAT_CHANGED_TYPE)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.remax.visualnovel.entity.imbean
|
||||
|
||||
import com.remax.visualnovel.manager.nim.FetchResult
|
||||
import com.remax.visualnovel.manager.nim.LoadStatus
|
||||
import com.netease.nimlib.sdk.v2.message.V2NIMMessage
|
||||
|
||||
|
||||
/**
|
||||
* Created by HJW on 2020/9/28
|
||||
*/
|
||||
open class IMMessageWrapper(
|
||||
open var message: V2NIMMessage? = null,
|
||||
var type: Int = OUT_TEXT_TYPE,
|
||||
) {
|
||||
/**
|
||||
* 表示该消息后是否要加一条AI输入中的消息
|
||||
*/
|
||||
var aiIsSending: Boolean = true
|
||||
|
||||
var fetchType: FetchResult.FetchType = FetchResult.FetchType.Init
|
||||
var loadStatus: LoadStatus = LoadStatus.Success
|
||||
|
||||
companion object {
|
||||
const val BASE_INFO = 0
|
||||
|
||||
// ai回复中
|
||||
const val INPUT_ING = 1
|
||||
|
||||
const val OUT_TEXT_TYPE = 2
|
||||
const val IN_TEXT_TYPE = 3
|
||||
|
||||
const val OUT_IMAGE_TYPE = 4
|
||||
const val IN_IMAGE_TYPE = 5
|
||||
|
||||
const val OUT_GIFT_TYPE = 6
|
||||
|
||||
const val OUT_CALL_TYPE = 7
|
||||
|
||||
/**
|
||||
* 心动等级升级/降级
|
||||
*/
|
||||
const val HEART_BEAT_CHANGED_TYPE = 8
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.remax.visualnovel.entity.imbean
|
||||
|
||||
import com.remax.visualnovel.entity.imbean.raw.CustomRawData
|
||||
import com.netease.nimlib.sdk.v2.message.V2NIMMessage
|
||||
|
||||
/**
|
||||
* Created by HJW on 2025/8/19
|
||||
*/
|
||||
data class IMOutImageMessage(
|
||||
override var message: V2NIMMessage?,
|
||||
val customRawData: CustomRawData?
|
||||
) : IMMessageWrapper(type = OUT_IMAGE_TYPE)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.remax.visualnovel.entity.imbean
|
||||
|
||||
import com.netease.nimlib.sdk.v2.conversation.model.V2NIMConversation
|
||||
import com.netease.nimlib.sdk.v2.utils.V2NIMConversationIdUtil
|
||||
|
||||
/**
|
||||
* Created by HJW on 2020/10/9
|
||||
*/
|
||||
data class RecentContactWrapper(
|
||||
var recentContact: V2NIMConversation
|
||||
) {
|
||||
val aiId: String
|
||||
get() {
|
||||
val targetId = V2NIMConversationIdUtil.conversationTargetId(recentContact.conversationId)
|
||||
|
||||
return targetId.substring(0, targetId.indexOf("@"))
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue