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