项目初步框架代码
This commit is contained in:
parent
b535a7667d
commit
bb87a3d138
|
|
@ -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,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,283 @@
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
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,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,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,33 @@
|
||||||
|
package com.remax.visualnovel.api.service
|
||||||
|
|
||||||
|
|
||||||
|
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,34 @@
|
||||||
|
package com.remax.visualnovel.api.service
|
||||||
|
|
||||||
|
import com.remax.visualnovel.BuildConfig
|
||||||
|
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,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,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,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,47 @@
|
||||||
|
package com.remax.visualnovel.app.di
|
||||||
|
|
||||||
|
|
||||||
|
import com.remax.visualnovel.api.factory.ServiceFactory
|
||||||
|
import com.remax.visualnovel.api.service.BookService
|
||||||
|
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.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>()
|
||||||
|
|
||||||
|
|
||||||
|
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,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,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,60 @@
|
||||||
|
package com.remax.visualnovel.configs
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
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 val proxies = listOf<ApplicationProxy>(CommonApplicationProxy)
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var appInitializersProvider: AppInitializersProvider
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
MultiDex.install(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,24 @@
|
||||||
|
package com.remax.visualnovel.entity.imbean.raw
|
||||||
|
|
||||||
|
import com.remax.visualnovel.entity.model.base.BasePhoto
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/21
|
||||||
|
*/
|
||||||
|
|
||||||
|
data class CustomAlbumData(
|
||||||
|
var url: String,
|
||||||
|
var width: Int,
|
||||||
|
var height: Int,
|
||||||
|
var unlockPrice: Long?,
|
||||||
|
val albumId: Long?
|
||||||
|
) : BasePhoto() {
|
||||||
|
|
||||||
|
val type = CustomRawData.IMAGE
|
||||||
|
|
||||||
|
var messageServerId: String? = null
|
||||||
|
|
||||||
|
override fun paramId(): Long {
|
||||||
|
return albumId ?: url.hashCode().toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.remax.visualnovel.entity.imbean.raw
|
||||||
|
|
||||||
|
import com.remax.visualnovel.R
|
||||||
|
import com.remax.visualnovel.app.base.app.CommonApplicationProxy
|
||||||
|
import com.remax.visualnovel.widget.imagepicker.utils.PDateUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/21
|
||||||
|
*/
|
||||||
|
|
||||||
|
data class CustomCallData(
|
||||||
|
val type: String,
|
||||||
|
val callType: String,
|
||||||
|
// 通话总时长
|
||||||
|
val duration: Long,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val CALL_CANCEL = "CALL_CANCEL"
|
||||||
|
const val CALL_END = "CALL_END"
|
||||||
|
}
|
||||||
|
|
||||||
|
val callTxt: String?
|
||||||
|
get() = when (callType) {
|
||||||
|
CALL_CANCEL -> {
|
||||||
|
CommonApplicationProxy.application.getString(R.string.call_canceled)
|
||||||
|
}
|
||||||
|
|
||||||
|
CALL_END -> {
|
||||||
|
"${CommonApplicationProxy.application.getString(R.string.call_duration)} ${
|
||||||
|
PDateUtil.formatTime(
|
||||||
|
CommonApplicationProxy.application,
|
||||||
|
duration
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.remax.visualnovel.entity.imbean.raw
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/23
|
||||||
|
*/
|
||||||
|
data class CustomGiftData(
|
||||||
|
val giftId: Int,
|
||||||
|
val giftName: String,
|
||||||
|
val giftIcon: String,
|
||||||
|
val giftNum: Int,
|
||||||
|
val title: String,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.remax.visualnovel.entity.imbean.raw
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/27
|
||||||
|
*/
|
||||||
|
data class CustomLevelChangeData(
|
||||||
|
val type: String,
|
||||||
|
val title: String,
|
||||||
|
val heartbeatLevel: String,
|
||||||
|
val heartbeatLevelName: String,
|
||||||
|
val heartbeatLevelNum: Int,
|
||||||
|
val heartbeatVal: Double,
|
||||||
|
) {
|
||||||
|
val isLevelUp: Boolean
|
||||||
|
get() = type == CustomRawData.HEARTBEAT_LEVEL_UP
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.remax.visualnovel.entity.imbean.raw
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/21
|
||||||
|
*/
|
||||||
|
|
||||||
|
data class CustomRawData(
|
||||||
|
val type: String,
|
||||||
|
val url: String,
|
||||||
|
val width: Int,
|
||||||
|
val height: Int
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
// 发送图片
|
||||||
|
const val IMAGE = "IMAGE"
|
||||||
|
|
||||||
|
// IM发送礼物
|
||||||
|
const val GIFT = "IM_SEND_GIFT"
|
||||||
|
|
||||||
|
//心动等级升级
|
||||||
|
const val HEARTBEAT_LEVEL_UP = "HEARTBEAT_LEVEL_UP"
|
||||||
|
|
||||||
|
//心动等级降级
|
||||||
|
const val HEARTBEAT_LEVEL_DOWN = "HEARTBEAT_LEVEL_DOWN"
|
||||||
|
|
||||||
|
// IM通话结束
|
||||||
|
const val CALL = "CALL"
|
||||||
|
|
||||||
|
// IM通话中分数变化
|
||||||
|
const val VOICE_CHAT_EMOTION_SCORE = "VOICE_CHAT_EMOTION_SCORE"
|
||||||
|
|
||||||
|
//余额不足 关闭语音电话
|
||||||
|
const val INSUFFICIENT_BALANCE = "INSUFFICIENT_BALANCE"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.remax.visualnovel.entity.imbean.raw
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/27
|
||||||
|
*/
|
||||||
|
data class CustomScoreData(
|
||||||
|
val score: Double
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.remax.visualnovel.entity.model
|
||||||
|
|
||||||
|
import com.remax.visualnovel.widget.imageviewer.adapter.ItemType
|
||||||
|
import com.remax.visualnovel.widget.imageviewer.core.Photo
|
||||||
|
|
||||||
|
|
||||||
|
data class MyImgData(
|
||||||
|
val viewerId: Long,
|
||||||
|
val url: String?,
|
||||||
|
val subsampling: Boolean = false
|
||||||
|
) : Photo {
|
||||||
|
override fun id(): Long = viewerId
|
||||||
|
override fun itemType(): Int {
|
||||||
|
return when {
|
||||||
|
subsampling -> ItemType.SUBSAMPLING
|
||||||
|
else -> ItemType.PHOTO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.remax.visualnovel.entity.model.base
|
||||||
|
|
||||||
|
import com.remax.visualnovel.widget.imageviewer.adapter.ItemType
|
||||||
|
import com.remax.visualnovel.widget.imageviewer.core.Photo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/7/21
|
||||||
|
*/
|
||||||
|
abstract class BasePhoto : Photo {
|
||||||
|
|
||||||
|
abstract fun paramId(): Long
|
||||||
|
|
||||||
|
override fun id(): Long {
|
||||||
|
return paramId()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun itemType(): Int {
|
||||||
|
return ItemType.PHOTO
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.remax.visualnovel.entity.request
|
||||||
|
|
||||||
|
import com.remax.visualnovel.constant.AppConstant
|
||||||
|
import com.remax.visualnovel.utils.AppUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2023/10/19
|
||||||
|
*/
|
||||||
|
data class PlatformAccountVerifyDTO(
|
||||||
|
/**
|
||||||
|
* 三方账号验证用
|
||||||
|
*/
|
||||||
|
val thirdToken: String? = null,
|
||||||
|
val thirdType: String? = null,
|
||||||
|
val appClient: String = AppConstant.APP_CLIENT,
|
||||||
|
val deviceCode: String = AppUtils.getAndroidID(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* common
|
||||||
|
*/
|
||||||
|
val authCode: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CompleteUserInfoInput(
|
||||||
|
var nickname: String? = null,
|
||||||
|
var sex: Int? = null,
|
||||||
|
var birthDay: Long? = null,
|
||||||
|
var headImage: String? = null,
|
||||||
|
var exUserId: String? = null,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.remax.visualnovel.entity.model.base.BasePhoto
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/7/21
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class AppearanceImage(
|
||||||
|
var imageUrl: String? = null,
|
||||||
|
var imageWidth: Int? = null,
|
||||||
|
var imageHeight: Int? = null,
|
||||||
|
var status: String = PENDING,
|
||||||
|
var select: Boolean = false,
|
||||||
|
var isPlaying: Boolean = false,
|
||||||
|
var unlockPrice: Long = 0,
|
||||||
|
var tempId: Long = 0,
|
||||||
|
) : BasePhoto(), Parcelable {
|
||||||
|
|
||||||
|
override fun paramId(): Long = tempId
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val NSFW = "NSFW"
|
||||||
|
const val COMPLETED = "COMPLETED"
|
||||||
|
const val FAILED = "FAILED"
|
||||||
|
const val PENDING = "PENDING"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/14
|
||||||
|
*/
|
||||||
|
data class Book(
|
||||||
|
val aiId: String,
|
||||||
|
val birthday: Long,
|
||||||
|
val characterName: String,
|
||||||
|
val headImg: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.remax.visualnovel.extension.calculateAge
|
||||||
|
import com.remax.visualnovel.extension.getNimAccountId
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/7/17
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class Character(
|
||||||
|
var aiId: String? = null,
|
||||||
|
var userId: String? = null,
|
||||||
|
var sex: Int? = null, // 0,男;1,女;2,自定义
|
||||||
|
var permission: Int? = null, //权限 1: 公开 2:私密
|
||||||
|
var nickname: String? = null,
|
||||||
|
var idCard: String? = null,
|
||||||
|
var headImg: String? = null,
|
||||||
|
var birthday: Long? = null,
|
||||||
|
var showBirthday: Long? = null,
|
||||||
|
var roleCode: String? = null,
|
||||||
|
var roleName: String? = null,
|
||||||
|
var characterCode: String? = null,
|
||||||
|
var characterName: String? = null,
|
||||||
|
var tagCode: String? = null,
|
||||||
|
var tagName: String? = null,
|
||||||
|
var introduction: String? = null, // 人物简介 >= 10 字符 <= 300 字符
|
||||||
|
var imageUrl: String? = null,
|
||||||
|
var homeImageUrl: String? = null, // 主页头图
|
||||||
|
var imageWidth: Int? = null,
|
||||||
|
var imageHeight: Int? = null,
|
||||||
|
var aiUserExt: CharacterExt? = null,
|
||||||
|
var liked: Boolean? = null, //是否点过赞
|
||||||
|
|
||||||
|
var likedNum: Long? = null,
|
||||||
|
var chatNum: Long? = null,
|
||||||
|
var conversationNum: Long? = null,
|
||||||
|
var coinNum: Long? = null,
|
||||||
|
|
||||||
|
//在IM中用
|
||||||
|
val dialoguePrologue: String? = null,
|
||||||
|
val dialoguePitch: String? = null,
|
||||||
|
val dialogueSpeechRate: String? = null,
|
||||||
|
val voiceType: String? = null,
|
||||||
|
var backgroundImg: String? = null,
|
||||||
|
var isDefaultBackground: Boolean? = null,
|
||||||
|
var isMember: Boolean? = null,
|
||||||
|
var isHaveChatted: Boolean? = null, //是否聊过天
|
||||||
|
var isDelChatted: Boolean? = null, //是否删除过会话
|
||||||
|
var isAutoPlayVoice: Int? = null, //自动播放语音开关 1:开 0:关
|
||||||
|
//var aiUserHeartbeatRelation: HeartbeatRelation? = null,
|
||||||
|
//var chatBubble: ChatBubble? = null,
|
||||||
|
|
||||||
|
//排行榜使用
|
||||||
|
var rankNo: Int? = null,
|
||||||
|
var heartbeatValTotal: Double? = null,
|
||||||
|
var giftCoinNum: Int? = null,
|
||||||
|
|
||||||
|
//首页滑动卡片使用
|
||||||
|
var isLimit: Boolean? = null,
|
||||||
|
var likedCount: Long? = null,
|
||||||
|
var heartbeatVal: Double? = null,
|
||||||
|
var character: String? = null,
|
||||||
|
var role: String? = null,
|
||||||
|
var tag: String? = null,
|
||||||
|
var isSecret: Boolean? = null,
|
||||||
|
//var albumList: List<Album>? = null,
|
||||||
|
|
||||||
|
) : Parcelable {
|
||||||
|
companion object {
|
||||||
|
const val UM_FREE = 1
|
||||||
|
const val UM_PAID = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
val age: Int
|
||||||
|
get() = (birthday ?: 0L).calculateAge()
|
||||||
|
|
||||||
|
val nimAccountId: String
|
||||||
|
get() = aiId.getNimAccountId(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class CharacterExt(
|
||||||
|
var profile: String? = null, //人物设定 >= 10 字符 <= 4000 字符
|
||||||
|
var userProfile: String? = null, //人物设定 >= 10 字符 <= 4000 字符
|
||||||
|
var dialogueStyle: String? = null, // 对话风格 >= 10 字符 <= 300 字符
|
||||||
|
var userDialogueStyle: String? = null, // 对话风格 >= 10 字符 <= 300 字符
|
||||||
|
var dialoguePrologue: String? = null, // 开场白 >= 10 字符 <= 150 字符
|
||||||
|
var dialogueTimbreCode: String? = null, // 对话音色Code
|
||||||
|
var dialoguePitch: String? = null, // 对话-音高
|
||||||
|
var dialogueSpeechRate: String? = null, // 对话-语速
|
||||||
|
var imageStyleCode: String? = null, //形象风格code
|
||||||
|
var imageDesc: String? = null, //形象描述 >= 10 字符 <= 500 字符
|
||||||
|
var imageReferenceUrl: String? = null, // 形象参考
|
||||||
|
) : Parcelable
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
import com.remax.visualnovel.entity.model.base.BasePhoto
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/8/18
|
||||||
|
*/
|
||||||
|
data class ChatBackground(
|
||||||
|
val backgroundId: Int?,
|
||||||
|
val imgUrl: String,
|
||||||
|
var isDefault: Boolean,
|
||||||
|
var select: Boolean = false,
|
||||||
|
var isSelected: Boolean? = null,
|
||||||
|
) : BasePhoto() {
|
||||||
|
override fun paramId(): Long {
|
||||||
|
return imgUrl.hashCode().toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2023/10/19
|
||||||
|
*/
|
||||||
|
data class PlatformAccountVerify(
|
||||||
|
val authType: String,
|
||||||
|
val optType: String,
|
||||||
|
val authCode: String,
|
||||||
|
val token: String?,
|
||||||
|
) {
|
||||||
|
val isLogin
|
||||||
|
get() = !token.isNullOrBlank()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val OPT_LOGIN = "L"
|
||||||
|
const val OPT_REGISTER = "R"
|
||||||
|
|
||||||
|
const val AUTH_VC = "VC"
|
||||||
|
const val AUTH_PD = "PD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.remax.visualnovel.entity.response
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/7/11
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class User(
|
||||||
|
val userId: String,
|
||||||
|
val idCard: String,
|
||||||
|
var birthday: Long?,
|
||||||
|
var nickname: String?,
|
||||||
|
var headImage: String?,
|
||||||
|
var sex: Int?,
|
||||||
|
val cpUserInfo: Boolean?,
|
||||||
|
val isMember: Boolean?,
|
||||||
|
val thirdEmail: String?,
|
||||||
|
val thirdNickname: String?,
|
||||||
|
val thirdType: String?,
|
||||||
|
// 可创建AI数量
|
||||||
|
var canCreateAiCount: Int,
|
||||||
|
var createdAiCount: Int,
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.remax.visualnovel.entity.response.base
|
||||||
|
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2025/7/21
|
||||||
|
*/
|
||||||
|
open class BaseVoice {
|
||||||
|
|
||||||
|
open fun id(): String = ""
|
||||||
|
open fun url(): String = ""
|
||||||
|
open fun filePathName(): String = ""
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
var isPlaying: Boolean = false
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
var isLoading: Boolean = false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package com.remax.visualnovel.entity.response.base
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import com.remax.visualnovel.app.base.app.CommonApplicationProxy
|
||||||
|
import com.remax.visualnovel.extension.toast
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2022/10/27
|
||||||
|
*/
|
||||||
|
open class Response<T>(
|
||||||
|
@SerializedName(value = "content")
|
||||||
|
val data: T? = null,
|
||||||
|
open val errorCode: String = "",
|
||||||
|
open val errorMsg: String = "",
|
||||||
|
val status: String = successCode
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val successCode = "OK"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* zip打包的错误error封装
|
||||||
|
* new
|
||||||
|
*/
|
||||||
|
inline fun <reified T> createZipFailResponse(vararg data: Response<*>): ApiFailedResponse<T> {
|
||||||
|
val failedResponse = ApiFailedResponse<T>()
|
||||||
|
for (t in data) {
|
||||||
|
if (!t.isApiSuccess) {
|
||||||
|
failedResponse.errorCode = t.errorCode
|
||||||
|
failedResponse.errorMsg = t.errorMsg
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return failedResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isOk: Boolean
|
||||||
|
get() = status == successCode
|
||||||
|
|
||||||
|
val isApiSuccess: Boolean
|
||||||
|
get() =
|
||||||
|
this is ApiSuccessResponse || this is ApiEmptyResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将返回结果分为成功和失败2个高阶函数
|
||||||
|
*
|
||||||
|
* 使用inline修饰,使2个参数可以调用外部函数return
|
||||||
|
*/
|
||||||
|
inline fun transformResult(apiSuccessCallback: ((T?) -> Unit) = {}, apiFailedCallback: ((Response<T>) -> Unit) = {}): Response<T> {
|
||||||
|
if (isApiSuccess) {
|
||||||
|
apiSuccessCallback.invoke(data)
|
||||||
|
} else {
|
||||||
|
apiFailedCallback.invoke(this)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> Response<T>.parseData(listenerBuilder: (ResultBuilder<T>.() -> Unit), showToast: Boolean = false) {
|
||||||
|
val listener = ResultBuilder<T>().also(listenerBuilder)
|
||||||
|
when (this) {
|
||||||
|
is ApiSuccessResponse -> listener.onSuccess(this.response)
|
||||||
|
is ApiEmptyResponse -> listener.onSuccess(null)
|
||||||
|
is ApiFailedResponse -> {
|
||||||
|
listener.onFailed(this.errorCode, this.errorMsg)
|
||||||
|
listener.onFailedWithData(this.data)
|
||||||
|
if (showToast) {
|
||||||
|
CommonApplicationProxy.application.toast(errorMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listener.onComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResultBuilder<T> {
|
||||||
|
var onSuccess: (data: T?) -> Unit = {}
|
||||||
|
var onFailed: (errorCode: String, errorMsg: String) -> Unit = { _, _ ->
|
||||||
|
|
||||||
|
}
|
||||||
|
var onFailedWithData: (errorData: T?) -> Unit = {}
|
||||||
|
var onComplete: () -> Unit = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ApiSuccessResponse<T>(val response: T? = null) : Response<T>(data = response)
|
||||||
|
|
||||||
|
class ApiEmptyResponse<T> : Response<T>()
|
||||||
|
|
||||||
|
data class ApiFailedResponse<T>(override var errorCode: String = "", override var errorMsg: String = "", val errorData: T? = null) :
|
||||||
|
Response<T>(data = errorData, errorCode = errorCode, errorMsg = errorMsg)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.remax.visualnovel.event.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2023/10/18
|
||||||
|
*/
|
||||||
|
data class OnLoginEvent(val status: Int) {
|
||||||
|
|
||||||
|
fun isLogin(): Boolean = status == LOGIN
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LOGIN = 1 //登录成功
|
||||||
|
const val LOGOUT = 2 //登出成功
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.remax.visualnovel.event.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2023/10/25
|
||||||
|
*/
|
||||||
|
data class OnPlayVoiceEvent(
|
||||||
|
val isStart: Boolean,
|
||||||
|
val id: String = ""
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.remax.visualnovel.event.model.tab
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2023/4/18
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
enum class MainTab(val index: Int, val checkLogin: Boolean = true) : Parcelable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主页的子fragment
|
||||||
|
*/
|
||||||
|
|
||||||
|
TAB_BOOKS(0, false),
|
||||||
|
TAB_MANGAS(1, false),
|
||||||
|
TAB_ACTORS(2, false),
|
||||||
|
TAB_HISTORY(3, false),
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
enum class ContactTab(val index: Int) : Parcelable {
|
||||||
|
MESSAGE(0), FRIEND(1)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.remax.visualnovel.event.model.tab
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2023/10/23
|
||||||
|
*
|
||||||
|
* jumpItem: 主页 tab切换
|
||||||
|
*/
|
||||||
|
data class OnTabChangedEvent(val jumpItem: MainTab, val contactTab: ContactTab? = null)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.remax.visualnovel.event.modular
|
||||||
|
|
||||||
|
import com.remax.visualnovel.event.model.OnPlayVoiceEvent
|
||||||
|
import com.remax.visualnovel.event.model.tab.OnTabChangedEvent
|
||||||
|
import com.pengxr.modular.eventbus.facade.annotation.EventGroup
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by HJW on 2023/5/22
|
||||||
|
* UI操作的事件
|
||||||
|
*/
|
||||||
|
@EventGroup(moduleName = "UI", autoClear = true)
|
||||||
|
interface UIEvents {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首页底部tab 双击时回到顶部操作
|
||||||
|
*/
|
||||||
|
fun mainScrollToTop()
|
||||||
|
|
||||||
|
fun onNetworkConnect()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主页tab切换
|
||||||
|
*/
|
||||||
|
fun onHomeTabChanged(): OnTabChangedEvent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放语音开始/结束
|
||||||
|
* @return Integer
|
||||||
|
*/
|
||||||
|
fun onSkillVoiceEvent(): OnPlayVoiceEvent
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue