项目初步框架代码
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