| <component name="ProjectCodeStyleConfiguration"> | <component name="ProjectCodeStyleConfiguration"> | ||||
| <code_scheme name="Project" version="173"> | <code_scheme name="Project" version="173"> | ||||
| <ComposeCustomCodeStyleSettings> | |||||
| <option name="USE_CUSTOM_FORMATTING_FOR_MODIFIERS" value="false" /> | |||||
| </ComposeCustomCodeStyleSettings> | |||||
| <DBN-PSQL> | <DBN-PSQL> | ||||
| <case-options enabled="true"> | <case-options enabled="true"> | ||||
| <option name="KEYWORD_CASE" value="lower" /> | <option name="KEYWORD_CASE" value="lower" /> | ||||
| </formatting-settings> | </formatting-settings> | ||||
| </DBN-SQL> | </DBN-SQL> | ||||
| <JetCodeStyleSettings> | <JetCodeStyleSettings> | ||||
| <option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="true" /> | |||||
| <option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="true" /> | |||||
| <option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="true" /> | |||||
| <option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="0" /> | |||||
| <option name="WRAP_ELVIS_EXPRESSIONS" value="0" /> | |||||
| <option name="IF_RPAREN_ON_NEW_LINE" value="false" /> | |||||
| <option name="SPACE_AROUND_RANGE" value="true" /> | |||||
| <option name="SPACE_BEFORE_TYPE_COLON" value="true" /> | |||||
| <option name="WRAP_ELVIS_EXPRESSIONS" value="2" /> | |||||
| <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | ||||
| </JetCodeStyleSettings> | </JetCodeStyleSettings> | ||||
| <DBN-PSQL> | <DBN-PSQL> | ||||
| </codeStyleSettings> | </codeStyleSettings> | ||||
| <codeStyleSettings language="kotlin"> | <codeStyleSettings language="kotlin"> | ||||
| <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | ||||
| <option name="RIGHT_MARGIN" value="120" /> | |||||
| <option name="RIGHT_MARGIN" value="130" /> | |||||
| <option name="KEEP_LINE_BREAKS" value="false" /> | <option name="KEEP_LINE_BREAKS" value="false" /> | ||||
| <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> | <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> | ||||
| <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" /> | |||||
| <option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" /> | <option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" /> | ||||
| <option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" /> | |||||
| <option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" /> | |||||
| <option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" /> | |||||
| <option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" /> | |||||
| <option name="EXTENDS_LIST_WRAP" value="0" /> | |||||
| <option name="VARIABLE_ANNOTATION_WRAP" value="2" /> | |||||
| <option name="ENUM_CONSTANTS_WRAP" value="2" /> | |||||
| <option name="WRAP_ON_TYPING" value="0" /> | |||||
| <indentOptions> | |||||
| <option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" /> | |||||
| </indentOptions> | |||||
| </codeStyleSettings> | </codeStyleSettings> | ||||
| </code_scheme> | </code_scheme> | ||||
| </component> | </component> |
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/progress_center.xml" value="0.287962962962963" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/progress_center.xml" value="0.287962962962963" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_12_white.xml" value="0.5140625" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_12_white.xml" value="0.5140625" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_toplr_24_white.xml" value="0.5061538461538462" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_toplr_24_white.xml" value="0.5061538461538462" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_switch_thumb.xml" value="0.14074074074074075" /> | |||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/spoken_autoplay_btn_text_color_.xml" value="0.5140625" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/spoken_autoplay_btn_text_color_.xml" value="0.5140625" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/switch_thumb_selector.xml" value="0.3768518518518518" /> | |||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/switch_track_selector.xml" value="0.3768518518518518" /> | |||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_base.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_base.xml" value="0.4979166666666667" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_exam_word.xml" value="0.33" /> | |||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_word2.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_word2.xml" value="0.4979166666666667" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.5" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.5" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_first.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_first.xml" value="0.4979166666666667" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_learn_center.xml" value="0.25" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_learn_center.xml" value="0.25" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_my.xml" value="0.28229166666666666" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_my.xml" value="0.28229166666666666" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/inc_learn_test_statistic.xml" value="0.35260416666666666" /> | |||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/inc_over_number.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/inc_over_number.xml" value="0.4979166666666667" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_main_learn_center_course_progress.xml" value="0.503125" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_main_learn_center_course_progress.xml" value="0.503125" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_main_learn_center_course_type_title.xml" value="0.67" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_main_learn_center_course_type_title.xml" value="0.67" /> |
| 注意include为merge的坑,viewbinding中需动态绑定 https://juejin.cn/post/6844904065655111693 | 注意include为merge的坑,viewbinding中需动态绑定 https://juejin.cn/post/6844904065655111693 | ||||
| 讨论点: | |||||
| 状态加载的判断: 加载复习状态页没问题,学前总测试状态页也没问题, | |||||
| 但如何判断课程学习完成,目前是课程学习完成后才判断进入课程学后总测试 | |||||
| 我的考虑是在进入课程详情界面时,对所有课时的学习进度进行判断,不包含课时的学后测试,当所有课时都学习完了,则进入学后总测试的状态页, | |||||
| 而在学习中,同样做引导课时前测试,学习,课时后测试, | |||||
| 课时后测试将包含 小游戏练习、再测一次、重新学习、下一步(进入学后总测试)或者下一课时(为当前课时的下一课时), | |||||
| 需要考虑的是,下一步或者下一课时的判断为循环判断,如,共10课时,现在点击学习的第5个,如果后面几个课时都学习完后,而1课时没有学习完成,下一课时将直接进入1课时的学习, | |||||
| 而下一步是在所有课时都学习完成的情况下,直接进入学后总测试的界面。 | |||||
| Kotlin的单例模式: https://developer.aliyun.com/article/642618 | Kotlin的单例模式: https://developer.aliyun.com/article/642618 |
| // } | // } | ||||
| compileOptions { | compileOptions { | ||||
| coreLibraryDesugaringEnabled true //为了使用jdk8的脱糖属性 | |||||
| sourceCompatibility JavaVersion.VERSION_1_8 | sourceCompatibility JavaVersion.VERSION_1_8 | ||||
| targetCompatibility JavaVersion.VERSION_1_8 | targetCompatibility JavaVersion.VERSION_1_8 | ||||
| } | } | ||||
| dependencies { | dependencies { | ||||
| implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs') | implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs') | ||||
| implementation 'androidx.legacy:legacy-support-v4:1.0.0' | |||||
| // implementation 'androidx.legacy:legacy-support-v4:1.0.0' | |||||
| implementation project(path: ':lib:common') | implementation project(path: ':lib:common') | ||||
| implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' | |||||
| implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' | |||||
| implementation 'androidx.appcompat:appcompat:1.2.0' | |||||
| implementation 'com.google.android.material:material:1.3.0' | |||||
| implementation 'androidx.constraintlayout:constraintlayout:2.0.4' | |||||
| implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' | |||||
| implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' | |||||
| // implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' | |||||
| // implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' | |||||
| // implementation 'androidx.appcompat:appcompat:1.2.0' | |||||
| // implementation 'com.google.android.material:material:1.3.0' | |||||
| // implementation 'androidx.constraintlayout:constraintlayout:2.0.4' | |||||
| // implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' | |||||
| // implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' | |||||
| rootProject.ext.dependencies_required.each{ k, v -> implementation v} | rootProject.ext.dependencies_required.each{ k, v -> implementation v} | ||||
| testImplementation rootProject.ext.dependencies_testImplementation.junit | testImplementation rootProject.ext.dependencies_testImplementation.junit | ||||
| //Lottie | //Lottie | ||||
| implementation customDependencies.Lottie | implementation customDependencies.Lottie | ||||
| coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | |||||
| } | } |
| android:name=".module.learn.LearnExamSpellActivity" | android:name=".module.learn.LearnExamSpellActivity" | ||||
| android:exported="true" /> | android:exported="true" /> | ||||
| <activity | <activity | ||||
| android:name=".module.learn.LearnExamWordActivity" | |||||
| android:name=".module.learn.LearnExamActivity" | |||||
| android:exported="true" /> | android:exported="true" /> | ||||
| <activity | <activity | ||||
| android:name=".module.learn.LearnSpellActivity" | android:name=".module.learn.LearnSpellActivity" |
| /** 测试通过:>80 < 90 继续加油 <80 悲伤 */ | /** 测试通过:>80 < 90 继续加油 <80 悲伤 */ | ||||
| const val TEST_SCORE_LEVEL_1 = 80 | const val TEST_SCORE_LEVEL_1 = 80 | ||||
| /** 测试通过 >= 太棒了 */ | |||||
| /** 测试通过 >= 太棒了 90 */ | |||||
| const val TEST_SCORE_LEVEL_2 = 90 | const val TEST_SCORE_LEVEL_2 = 90 | ||||
| const val ACTION_START_BEFORE_TEST = 1 | |||||
| const val ACTION_START_AFTER_TEST = 2 | |||||
| const val ACTION_START_TOTAL_TEST = 3 | |||||
| const val ACTION_START_TOTAL_AFTER_TEST = 4 | |||||
| //测试的题目类型 | |||||
| /** 选择题类型 */ | |||||
| const val TEST_QUEST_TYPE_CHOICE = 1 | |||||
| /** 口语对话测试 */ | |||||
| const val TEST_QUEST_TYPE_SPOKEN_DIALOGUE = 4 | |||||
| const val TEST_QUEST_TYPE_GAP_FILLING = 2 //填空题 | |||||
| const val TEST_QUEST_TYPE_JUDGE = 3 //判断题 | |||||
| /**总测试 */ | |||||
| const val TEST_COUNT_TOTAL = 25 | |||||
| /**章节测试*/ | |||||
| const val TEST_COUNT_LESSON = 20 | |||||
| /**词汇量测试 */ | |||||
| const val TEST_COUNT_VOCABULARY = 100 | |||||
| /** 美 */ | |||||
| const val SOUND_TYPE_US = 1 //美 | |||||
| /** 发音默认type 英 */ | |||||
| const val SOUND_TYPE_UK = 2 //英 | |||||
| /**中文*/ | |||||
| const val SOUND_TYPE_CN = 3 //中文 | |||||
| } | } |
| private val dataMap = hashMapOf<String,Any>() | private val dataMap = hashMapOf<String,Any>() | ||||
| fun <T : Any> putData(t : T){ | |||||
| dataMap.put(AppConfig.INTENT_1,t) | |||||
| /** | |||||
| * 存放数据 | |||||
| * @param key String | |||||
| * @param value T | |||||
| */ | |||||
| fun <T : Any> putData(key: String, value : T){ | |||||
| dataMap[key] = value | |||||
| } | } | ||||
| /** | |||||
| * 获取数据 | |||||
| * @param key String | |||||
| * @return T | |||||
| */ | |||||
| @Suppress("UNCHECKED_CAST") | @Suppress("UNCHECKED_CAST") | ||||
| fun <T> getData() : T{ | |||||
| return dataMap.get(AppConfig.INTENT_1) as T | |||||
| fun <T> getData(key : String) : T{ | |||||
| return dataMap[key] as T | |||||
| } | } | ||||
| /** | |||||
| * 清空集合 | |||||
| */ | |||||
| fun clear(){ | fun clear(){ | ||||
| dataMap.clear() | dataMap.clear() | ||||
| } | } |
| package com.xkl.cdl.data.bean.course | |||||
| /** | |||||
| * author suliang | |||||
| * create 2022/4/6 11:03 | |||||
| * Describe: 测试数据 | |||||
| */ | |||||
| class ExamBean { | |||||
| var id : Long = 0 //当前记录所在的id 英语: 测试题的id 作文:exam_id | |||||
| var word_id : Long = 0 //英语:词汇id 作文: knowledge_id知识点id | |||||
| var word : String = "" //测试题干 | |||||
| var correct : String = "" //正确选项 在拼写时记录为该单词的释义 | |||||
| var error1: String = "" //错误选项1 | |||||
| var error2: String = "" //错误选项2 | |||||
| var error3 : String = "" //错误选项3 | |||||
| var type = 0 //测试题类型 | |||||
| var chapterId : Long = 0 //章节id | |||||
| var lessonId : Long = 0 //课时id | |||||
| var questionIsAudio = false //问题是否是音频 | |||||
| var answersIsAudio = false //答案是否是音频 | |||||
| } |
| package com.xkl.cdl.data.manager | package com.xkl.cdl.data.manager | ||||
| import android.util.Size | |||||
| import com.suliang.common.util.file.FileUtil | import com.suliang.common.util.file.FileUtil | ||||
| import com.xkl.cdl.data.AppConstants | |||||
| import com.xkl.cdl.data.bean.course.CoursePack | import com.xkl.cdl.data.bean.course.CoursePack | ||||
| import com.xkl.cdl.data.bean.course.ExamBean | |||||
| import java.io.File | import java.io.File | ||||
| import java.math.BigDecimal | import java.math.BigDecimal | ||||
| import java.math.RoundingMode | import java.math.RoundingMode | ||||
| * Describe: 课程管理类 | * Describe: 课程管理类 | ||||
| */ | */ | ||||
| object CourseManager { | object CourseManager { | ||||
| /** 项目对应的课程包 */ | /** 项目对应的课程包 */ | ||||
| val subjectWithCoursePackMap = hashMapOf<Int, List<CoursePack>>() | val subjectWithCoursePackMap = hashMapOf<Int, List<CoursePack>>() | ||||
| /** | /** | ||||
| * 获取对应项目的课程数量 | * 获取对应项目的课程数量 | ||||
| * @param subjectId Int 项目 | * @param subjectId Int 项目 | ||||
| * @return Int 课程数量 | * @return Int 课程数量 | ||||
| */ | */ | ||||
| fun getSubjectForCourseSize(subjectId: Int): Int { | |||||
| fun getSubjectForCourseSize(subjectId : Int) : Int { | |||||
| return subjectWithCoursePackMap[subjectId]?.let { | return subjectWithCoursePackMap[subjectId]?.let { | ||||
| var count = 0 | var count = 0 | ||||
| it.forEach { coursePack -> | it.forEach { coursePack -> | ||||
| count += coursePack.childrenCourses?.size ?: 0 | |||||
| count += coursePack.childrenCourses?.size | |||||
| ?: 0 | |||||
| } | } | ||||
| count | count | ||||
| } ?: 0 | |||||
| } | |||||
| ?: 0 | |||||
| } | } | ||||
| /** | /** | ||||
| * 搜索项目下的课程包 | * 搜索项目下的课程包 | ||||
| * @param subjectId Int 项目id | * @param subjectId Int 项目id | ||||
| * @return MutableList<CoursePack>? 结果值 | * @return MutableList<CoursePack>? 结果值 | ||||
| */ | */ | ||||
| @Synchronized | @Synchronized | ||||
| fun filterSearchCoursePack(subjectId: Int, searchValue: String?): MutableList<CoursePack>? { | |||||
| fun filterSearchCoursePack(subjectId : Int, searchValue : String?) : MutableList<CoursePack>? { | |||||
| return if (searchValue.isNullOrEmpty() || searchValue.trim().isEmpty()) { | return if (searchValue.isNullOrEmpty() || searchValue.trim().isEmpty()) { | ||||
| subjectWithCoursePackMap[subjectId]?.toMutableList() | subjectWithCoursePackMap[subjectId]?.toMutableList() | ||||
| } else { | } else { | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * 根据subjectId,courseId 获取CoursePack | * 根据subjectId,courseId 获取CoursePack | ||||
| * @param subjectId Int 项目Id | * @param subjectId Int 项目Id | ||||
| * @return CoursePack? 课程包 | * @return CoursePack? 课程包 | ||||
| */ | */ | ||||
| @Synchronized | @Synchronized | ||||
| fun getCoursePackWithCoursePackId(subjectId: Int, coursePackId: Long): CoursePack? { | |||||
| fun getCoursePackWithCoursePackId(subjectId : Int, coursePackId : Long) : CoursePack? { | |||||
| return subjectWithCoursePackMap[subjectId]?.let { | return subjectWithCoursePackMap[subjectId]?.let { | ||||
| //返回很符合条件的第一个元素 | //返回很符合条件的第一个元素 | ||||
| it.firstOrNull { | it.firstOrNull { | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /*** | /*** | ||||
| * 检查课程包下课程的db数据文件是否存在,不存在,则复制过去 | * 检查课程包下课程的db数据文件是否存在,不存在,则复制过去 | ||||
| */ | */ | ||||
| entry.value.forEach { coursePack -> | entry.value.forEach { coursePack -> | ||||
| coursePack.childrenCourses.forEach { | coursePack.childrenCourses.forEach { | ||||
| val file = File( | val file = File( | ||||
| FileUtil.getSaveDirPath("db"), | |||||
| "${entry.key}/${coursePack.coursePackId}/${it.courseId}/course.db" | |||||
| ) | |||||
| FileUtil.getSaveDirPath("db"), "${entry.key}/${coursePack.coursePackId}/${it.courseId}/course.db" | |||||
| ) | |||||
| //不存在,复制,存在则不操作 | //不存在,复制,存在则不操作 | ||||
| if (!file.exists()) { | if (!file.exists()) { | ||||
| FileUtil.copyAsset(it.dbPathName, file) | FileUtil.copyAsset(it.dbPathName, file) | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * 用于显示的进度 | * 用于显示的进度 | ||||
| * @param progress Double 课程的进度 | * @param progress Double 课程的进度 | ||||
| * @return String | * @return String | ||||
| */ | */ | ||||
| @JvmStatic | @JvmStatic | ||||
| fun useToShowProgress(progress: Double): String { | |||||
| fun useToShowProgress(progress : Double) : String { | |||||
| return when { | return when { | ||||
| progress == 0.0 -> "0" | progress == 0.0 -> "0" | ||||
| progress < 0.1 -> "0.1" | progress < 0.1 -> "0.1" | ||||
| else -> "100" | else -> "100" | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * 计算预计测试时间 | |||||
| * 总测: 口语总测试与辨音课程测试不显示时长 | |||||
| * 课时测:辨音不显示时长, 作文测试没有显示时长 | |||||
| * @param courseType Int 课程类型 | |||||
| * @param testData List<ExamBean> 测试题 | |||||
| * @return String 格式化好的数据: 共%d题,预计%d分钟 | |||||
| */ | |||||
| @JvmStatic | |||||
| fun expectedTestTime(courseType : Int, testType : Int, testData : List<ExamBean>) : String { | |||||
| return when (courseType) { | |||||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> { | |||||
| "" | |||||
| } | |||||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { | |||||
| String.format("共${testData.size}题,预计%d分钟", (Math.ceil(testData.stream().mapToInt { | |||||
| it.word.length | |||||
| }.sum() * 1.6 / 60)).toInt()) | |||||
| } | |||||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { | |||||
| when(testType) { | |||||
| AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> { | |||||
| "共${testData.size}题" | |||||
| } | |||||
| else -> { | |||||
| String.format("共${testData.size}题,预计%d分钟", Math.ceil(testData.size * 0.1).toInt()) | |||||
| } | |||||
| } | |||||
| } | |||||
| AppConstants.COURSE_TYPE_ENGLISH_VOICE -> { | |||||
| "共${testData.size}题" | |||||
| } | |||||
| else -> { | |||||
| String.format("共${testData.size}题,预计%d分钟", Math.ceil(testData.size * 0.1).toInt()) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } |
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||
| import android.widget.Toast | import android.widget.Toast | ||||
| import androidx.core.database.getLongOrNull | |||||
| import com.suliang.common.util.AppGlobals | import com.suliang.common.util.AppGlobals | ||||
| import com.suliang.common.util.thread.AppExecutors | import com.suliang.common.util.thread.AppExecutors | ||||
| import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
| import com.xkl.cdl.data.bean.course.CourseDetail | import com.xkl.cdl.data.bean.course.CourseDetail | ||||
| import com.xkl.cdl.data.bean.course.ExamBean | |||||
| import com.xkl.cdl.data.bean.course.Lesson | import com.xkl.cdl.data.bean.course.Lesson | ||||
| import com.xkl.cdl.data.manager.FilePathManager | import com.xkl.cdl.data.manager.FilePathManager | ||||
| import net.sqlcipher.database.SQLiteDatabase | import net.sqlcipher.database.SQLiteDatabase | ||||
| private const val SPOKEN = "XKL_SPOKEN_COURSE_DATA_KEY" | private const val SPOKEN = "XKL_SPOKEN_COURSE_DATA_KEY" | ||||
| private const val COMPOSITION = "XKL_LOCAL_COMPOSITION_DATA_KEY" | private const val COMPOSITION = "XKL_LOCAL_COMPOSITION_DATA_KEY" | ||||
| private const val LITERACY = "XKL_LOCAL_CHINESE_COURSE_KEY" | private const val LITERACY = "XKL_LOCAL_CHINESE_COURSE_KEY" | ||||
| private var mDataBase: SQLiteDatabase? = null | |||||
| private fun open(base: DbControlBase) { | |||||
| private var mDataBase : SQLiteDatabase? = null | |||||
| private fun open(base : DbControlBase) { | |||||
| synchronized(DBCourseManager) { | synchronized(DBCourseManager) { | ||||
| if (mDataBase != null && currentBase != base) { | if (mDataBase != null && currentBase != base) { | ||||
| mDataBase?.close() | mDataBase?.close() | ||||
| AppConstants.COURSE_TYPE_CHINESE_LITERACY -> LITERACY | AppConstants.COURSE_TYPE_CHINESE_LITERACY -> LITERACY | ||||
| else -> NORMAL | else -> NORMAL | ||||
| } | } | ||||
| mDataBase = SQLiteDatabase.openDatabase(FilePathManager.getCourseDbPath(currentBase).path, | |||||
| p, | |||||
| null, | |||||
| SQLiteDatabase.OPEN_READONLY) | |||||
| mDataBase = SQLiteDatabase.openDatabase( | |||||
| FilePathManager.getCourseDbPath(currentBase).path, p, null, SQLiteDatabase.OPEN_READONLY | |||||
| ) | |||||
| } | } | ||||
| if (mDataBase == null) { | if (mDataBase == null) { | ||||
| AppExecutors.mainThread.run { | AppExecutors.mainThread.run { | ||||
| Toast.makeText(AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG) | |||||
| Toast.makeText( | |||||
| AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG | |||||
| ) | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /**获取该课程的所有课时 | /**获取该课程的所有课时 | ||||
| * @param base DbControlBase | * @param base DbControlBase | ||||
| * @param detail CourseDetail 课程信息 | * @param detail CourseDetail 课程信息 | ||||
| * @return List<Lesson> | * @return List<Lesson> | ||||
| */ | */ | ||||
| @SuppressLint("Range") | @SuppressLint("Range") | ||||
| fun queryAllLesson(base: DbControlBase, detail: CourseDetail): List<Lesson> { | |||||
| fun queryAllLesson(base : DbControlBase, detail : CourseDetail) : List<Lesson> { | |||||
| val mutableList = mutableListOf<Lesson>() | val mutableList = mutableListOf<Lesson>() | ||||
| open(base) | open(base) | ||||
| //聚合所有课时,先所有chapter_sort和word_sort排序,然后根据leesonId分组并聚合leeson_id到 | //聚合所有课时,先所有chapter_sort和word_sort排序,然后根据leesonId分组并聚合leeson_id到 | ||||
| var positionIndex = 0 | var positionIndex = 0 | ||||
| mDataBase?.rawQuery(rawQurySql, null)?.let { | mDataBase?.rawQuery(rawQurySql, null)?.let { | ||||
| while (it.moveToNext()) { | while (it.moveToNext()) { | ||||
| val chapterId: Long = it.getLong(it.getColumnIndex("chapter_id")) | |||||
| val chapterName: String = it.getString(it.getColumnIndex("chapter_title")) | |||||
| val chapterId : Long = it.getLong(it.getColumnIndex("chapter_id")) | |||||
| val chapterName : String = it.getString(it.getColumnIndex("chapter_title")) | |||||
| val lessonId = it.getLong(it.getColumnIndex("lesson_id")) | val lessonId = it.getLong(it.getColumnIndex("lesson_id")) | ||||
| val lessonName = when (base.courseType) { | val lessonName = when (base.courseType) { | ||||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("lesson_title")) | AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("lesson_title")) | ||||
| else -> it.getString(it.getColumnIndex("lesson")) | else -> it.getString(it.getColumnIndex("lesson")) | ||||
| } | } | ||||
| val wordIds = when (base.courseType) { | val wordIds = when (base.courseType) { | ||||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("relation_id")) | |||||
| .split(",") | |||||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("relation_id")).split(",") | |||||
| else -> it.getString(it.getColumnIndex("wordIds")).split(",") | else -> it.getString(it.getColumnIndex("wordIds")).split(",") | ||||
| }.map { value -> value.toLong() }.toMutableList() | }.map { value -> value.toLong() }.toMutableList() | ||||
| val lessonType: Int = when (base.courseType) { | |||||
| val lessonType : Int = when (base.courseType) { | |||||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getInt(it.getColumnIndex("type")) | AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getInt(it.getColumnIndex("type")) | ||||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> it.getInt(it.getColumnIndex("lesson_type")) | AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> it.getInt(it.getColumnIndex("lesson_type")) | ||||
| else -> AppConstants.LESSON_TYPE_WORD | else -> AppConstants.LESSON_TYPE_WORD | ||||
| } | } | ||||
| val key = "${chapterId}_${lessonId}" | val key = "${chapterId}_${lessonId}" | ||||
| val learnIndex = wordIds.indexOf(detail.lesson_learn_point.getOrElse(key, { -1 })) //学习位置,当前位置为已学 | |||||
| val learnIndex = wordIds.indexOf( | |||||
| detail.lesson_learn_point.getOrElse(key, { -1 }) | |||||
| ) //学习位置,当前位置为已学 | |||||
| // 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 | // 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 | ||||
| val learnIsOver = wordIds.size - 1 == learnIndex | val learnIsOver = wordIds.size - 1 == learnIndex | ||||
| val lesson = Lesson(base.subjectId, | |||||
| base.coursePackId, | |||||
| base.courseId, | |||||
| base.courseType, | |||||
| chapterId, | |||||
| chapterName, | |||||
| lessonId, | |||||
| lessonName).apply { | |||||
| val lesson = Lesson( | |||||
| base.subjectId, | |||||
| base.coursePackId, | |||||
| base.courseId, | |||||
| base.courseType, | |||||
| chapterId, | |||||
| chapterName, | |||||
| lessonId, | |||||
| lessonName).apply { | |||||
| lessonPositionInList = positionIndex | lessonPositionInList = positionIndex | ||||
| this.wordIds = wordIds //内容 | this.wordIds = wordIds //内容 | ||||
| totalNumber = this.wordIds.size //总数 | totalNumber = this.wordIds.size //总数 | ||||
| mutableList.last().lastLesson = true | mutableList.last().lastLesson = true | ||||
| return mutableList.toList() | return mutableList.toList() | ||||
| } | } | ||||
| /** 获取总测试的数据 */ | |||||
| fun queryTotalTest(coursePackId: Long, courseId: Long) { | |||||
| } | |||||
| /** 获取课时测试数据 */ | |||||
| fun queryLessonTest(coursePackId: Long, courseId: Long, chapterId: Long, lessonId: Long, courseType: Long) { | |||||
| /** 获取学习时的测试的数据 (学前、学后总测、课时学前、学后总测) | |||||
| * @param base DbControlBase | |||||
| * @param testType Int 测试类型 | |||||
| * @param lesson Lesson? 测试章节 仅当课时学前学后测试时才传入课时 | |||||
| * @return List<ExamBean> 返回数据 | |||||
| */ | |||||
| fun queryLearnTest(base : DbControlBase, testType : Int, lesson : Lesson? = null) : List<ExamBean> { | |||||
| open(base) | |||||
| val result = mutableListOf<ExamBean>() | |||||
| //查询数量 | |||||
| val count = when (testType) { | |||||
| AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> AppConstants.TEST_COUNT_TOTAL //学前、学后总测 | |||||
| AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> AppConstants.TEST_COUNT_LESSON //课时学前、学后 | |||||
| else -> 0 | |||||
| } | |||||
| val sql = when (base.courseType) { | |||||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE, | |||||
| AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_CHINESE_LITERACY, | |||||
| AppConstants.COURSE_TYPE_CHINESE_PINYIN, | |||||
| -> { //没有作文,作文没有学前总测 | |||||
| "SELECT exam.*, chapter_id,lesson_id,basic_explaination FROM chapter JOIN exam ON chapter.word_id = exam.word_id AND exam.type = ${AppConstants.TEST_QUEST_TYPE_CHOICE} " + when (testType) { | |||||
| AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> { //学前、学后测 | |||||
| " AND chapter_id = ${lesson!!.chapterId} AND lesson_id = ${lesson.lessonId} " | |||||
| } | |||||
| else -> "" | |||||
| } + " ORDER by random() LIMIT $count" | |||||
| } | |||||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { //拼写 不需要查询测试表,不需要选项 | |||||
| "SELECT id, word_id,word,chapter_id,lesson_id,basic_explaination FROM chapter " + when (testType) { | |||||
| AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> { //学前、学后测 | |||||
| " AND chapter_id = ${lesson!!.chapterId} AND lesson_id = ${lesson.lessonId} " | |||||
| } | |||||
| else -> " ORDER by random() LIMIT $count" | |||||
| } | |||||
| } | |||||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { //口语 | |||||
| when (testType) { | |||||
| AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> { //总测 | |||||
| "SELECT * FROM exam WHERE type = 5 ORDER by random() LIMIT $count" | |||||
| } | |||||
| AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> { | |||||
| "SELECT exam.* FROM chapter JOIN exam on chapter.word_id = exam.word_id AND exam.type = 1 AND chapter_id = ${lesson!!.chapterId} AND lesson_id = ${lesson.lessonId} ORDER by random() LIMIT $count" | |||||
| } | |||||
| else -> "" | |||||
| } | |||||
| } | |||||
| else -> "" //没有任何课程,课程都特别写入类型,用于匹配 | |||||
| } | |||||
| mDataBase?.rawQuery( | |||||
| sql, null | |||||
| )?.run { | |||||
| when (base.courseType) { | |||||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE, | |||||
| AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_CHINESE_LITERACY, | |||||
| AppConstants.COURSE_TYPE_CHINESE_PINYIN, | |||||
| -> { | |||||
| while (moveToNext()) { | |||||
| result.add(ExamBean().apply { | |||||
| id = getLong(0) | |||||
| word_id = getLong(1) | |||||
| word = getString(2) | |||||
| correct = getString(3) | |||||
| error1 = getString(4) | |||||
| error2 = getString(5) | |||||
| error3 = getString(6) | |||||
| type = AppConstants.TEST_QUEST_TYPE_CHOICE // 为1 | |||||
| chapterId = getLong(8) | |||||
| lessonId = getLong(9) | |||||
| }) | |||||
| } | |||||
| } | |||||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { | |||||
| while (moveToNext()) { | |||||
| result.add(ExamBean().apply { | |||||
| id = getLong(0) | |||||
| word_id = getLong(1) | |||||
| word = getString(2) | |||||
| chapterId = getLong(3) | |||||
| lessonId = getLong(4) | |||||
| correct = getString(5) | |||||
| type = AppConstants.TEST_QUEST_TYPE_CHOICE // 为1 | |||||
| }) | |||||
| } | |||||
| } | |||||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { | |||||
| while (moveToNext()) { | |||||
| result.add(ExamBean().apply { | |||||
| id = getLong(0) | |||||
| word_id = getLong(1) | |||||
| word = getString(2) | |||||
| correct = getString(3) | |||||
| error1 = getString(4) | |||||
| error2 = getString(5) | |||||
| error3 = getString(6) | |||||
| type = AppConstants.TEST_QUEST_TYPE_CHOICE // 为1 | |||||
| lesson?.let { //课时测试时才会有课时存在 | |||||
| chapterId = it.chapterId | |||||
| lessonId = it.lessonId | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| close() | |||||
| } | |||||
| return result | |||||
| } | } | ||||
| /** 获取指定课时(指定数据源wordIds)的随机个数测试数据 */ | /** 获取指定课时(指定数据源wordIds)的随机个数测试数据 */ | ||||
| fun queryLessonTestRandomSize(coursePackId: Long, | |||||
| courseId: Long, | |||||
| chapterId: Long, | |||||
| lessonId: Long, | |||||
| courseType: Long, | |||||
| limitSize: Int, | |||||
| wordIds: ArrayList<Long>? = null) { | |||||
| fun queryLessonTestRandomSize( | |||||
| coursePackId : Long, | |||||
| courseId : Long, | |||||
| chapterId : Long, | |||||
| lessonId : Long, | |||||
| courseType : Long, | |||||
| limitSize : Int, | |||||
| wordIds : ArrayList<Long>? = null, | |||||
| ) { | |||||
| } | } | ||||
| /** 获取课时学习内容 */ | /** 获取课时学习内容 */ | ||||
| fun queryLessonLearnData(coursePackId: Long, | |||||
| courseId: Long, | |||||
| chapterId: Long, | |||||
| lessonId: Long, | |||||
| wordIds: ArrayList<Long>) { | |||||
| fun queryLessonLearnData( | |||||
| coursePackId : Long, | |||||
| courseId : Long, | |||||
| chapterId : Long, | |||||
| lessonId : Long, | |||||
| wordIds : ArrayList<Long>, | |||||
| ) { | |||||
| } | } | ||||
| /** 获取课程今日需要复习的数据 */ | /** 获取课程今日需要复习的数据 */ | ||||
| fun queryCourseReviewDatas(coursePackId: Long, courseId: Long, wordIds: ArrayList<Long>) { | |||||
| fun queryCourseReviewDatas( | |||||
| coursePackId : Long, | |||||
| courseId : Long, | |||||
| wordIds : ArrayList<Long>, | |||||
| ) { | |||||
| } | } | ||||
| } | } |
| package com.xkl.cdl.dialog | |||||
| /** | |||||
| * author suliang | |||||
| * create 2022/4/6 9:54 | |||||
| * Describe: LearnDialog 按钮点击动作 | |||||
| */ | |||||
| enum class DialogEventAction { | |||||
| /**下一步*/ | |||||
| NEXT, | |||||
| /** 开始学习 */ | |||||
| START_LEARN, | |||||
| /** 课时学前测试 */ | |||||
| START_LESSON_BEFORE_TEST | |||||
| // TODO: 2022/4/6 可以添加相应的动作action,来处理对应的动作 | |||||
| } |
| package com.xkl.cdl.dialog | package com.xkl.cdl.dialog | ||||
| import android.view.View | import android.view.View | ||||
| import androidx.core.content.ContextCompat | |||||
| import androidx.fragment.app.FragmentManager | |||||
| import com.suliang.common.base.BaseDialogFragment | import com.suliang.common.base.BaseDialogFragment | ||||
| import com.suliang.common.extension.click | import com.suliang.common.extension.click | ||||
| import com.suliang.common.util.LogUtil | import com.suliang.common.util.LogUtil | ||||
| import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
| import com.xkl.cdl.data.AppConstants | |||||
| import com.xkl.cdl.databinding.DialogLessonLearnBinding | import com.xkl.cdl.databinding.DialogLessonLearnBinding | ||||
| /** | /** | ||||
| */ | */ | ||||
| class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | ||||
| private lateinit var dialogListener : DialogFragmentListener | |||||
| private lateinit var dialogListener: DialogFragmentListener | |||||
| override fun initFragment() { | override fun initFragment() { | ||||
| initClick() | initClick() | ||||
| initLessonBeforeTestOver() | initLessonBeforeTestOver() | ||||
| } | } | ||||
| fun initClick(){ | |||||
| dialogListener = if (parentFragment != null){ | |||||
| fun initClick() { | |||||
| dialogListener = if (parentFragment != null) { | |||||
| parentFragment as DialogFragmentListener | parentFragment as DialogFragmentListener | ||||
| }else{ | |||||
| } else { | |||||
| activity as DialogFragmentListener | activity as DialogFragmentListener | ||||
| } | } | ||||
| } | } | ||||
| fun initLessonBeforeTest(){ | |||||
| fun initLessonBeforeTest() { | |||||
| binding.tvTitle.text = "课时学前测试" | binding.tvTitle.text = "课时学前测试" | ||||
| binding.tvLessonName.text = "章节课时名称" | binding.tvLessonName.text = "章节课时名称" | ||||
| binding.tvCountTime.text = "测试题目时间" | binding.tvCountTime.text = "测试题目时间" | ||||
| binding.tvRight.click { | binding.tvRight.click { | ||||
| LogUtil.e("Dialog -- > show()") | LogUtil.e("Dialog -- > show()") | ||||
| dialogListener.rightClick() | |||||
| } | } | ||||
| binding.tvLeft.click { | binding.tvLeft.click { | ||||
| LogUtil.e("Dialog -- > hide()") | LogUtil.e("Dialog -- > hide()") | ||||
| dialogListener.leftClick() | |||||
| } | } | ||||
| } | } | ||||
| fun initLessonBeforeTestOver(){ | |||||
| fun initLessonBeforeTestOver() { | |||||
| binding.tvScore.run { | binding.tvScore.run { | ||||
| visibility = View.VISIBLE | visibility = View.VISIBLE | ||||
| text = "100分" | text = "100分" | ||||
| } | } | ||||
| } | } | ||||
| fun initLessonAfterTestOver(){ | |||||
| //未通过 | |||||
| //已通过 | |||||
| fun initLessonAfterTestOver() { | |||||
| //未通过、通过和通过已通过,均为一致,只是显示的状态不同 | |||||
| val score = if (1 + 1 == 2) 100 else 88 | |||||
| when { | |||||
| score < AppConstants.TEST_SCORE_LEVEL_1 -> { //小于80 | |||||
| binding.imgIv.setImageResource(R.mipmap.test_score_level_1) | |||||
| binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray_2)) | |||||
| } | |||||
| score >= AppConstants.TEST_SCORE_LEVEL_2 -> { //大于90 | |||||
| binding.imgIv.setImageResource(R.mipmap.test_score_level_2) | |||||
| binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_1)) | |||||
| } | |||||
| else -> { // 大于等于80 且小于90 | |||||
| binding.imgIv.setImageResource(R.mipmap.test_score_level_3) | |||||
| binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_2)) | |||||
| } | |||||
| } | |||||
| //已通过的再通过 | |||||
| binding.tvScore.run { | |||||
| visibility = View.VISIBLE | |||||
| text = "${score}分" | |||||
| } | |||||
| } | |||||
| binding.tvTip.visibility = View.VISIBLE | |||||
| binding.tvTitle.text = "恭喜你,完成了课时学前测试" | |||||
| binding.tvLessonName.visibility = View.GONE | |||||
| binding.tvCountTime.visibility = View.GONE | |||||
| binding.incStatisticsNumber.root.visibility = View.VISIBLE | |||||
| // binding.tvTip1.visibility = View.VISIBLE | |||||
| // binding.tvTip1.setText(resources.getString(R.string.test_before_test_over_tip)) | |||||
| binding.tvRight.setText(resources.getString(R.string.start_learn)) | |||||
| binding.tvRight.click { | |||||
| // TODO: 2022/4/2 开始学习 | |||||
| } | |||||
| } | |||||
| public interface DialogFragmentListener{ | |||||
| fun rightClick() | |||||
| fun leftClick() | |||||
| interface DialogFragmentListener { | |||||
| fun dialogButtonClick(action: DialogEventAction) | |||||
| } | } | ||||
| } | } |
| package com.xkl.cdl.module.learn | |||||
| import android.content.Context | |||||
| import android.content.Intent | |||||
| import androidx.appcompat.app.AppCompatActivity | |||||
| import android.os.Bundle | |||||
| import androidx.viewbinding.ViewBinding | |||||
| import com.suliang.common.AppConfig | |||||
| import com.suliang.common.base.activity.UIBaseActivity | |||||
| import com.xkl.cdl.R | |||||
| import com.xkl.cdl.data.AppConstants | |||||
| import com.xkl.cdl.databinding.ActivityLearnExamWordBinding | |||||
| import com.xkl.cdl.databinding.ActivityLearnSpellBinding | |||||
| import com.xkl.cdl.module.m_center_learn.CoursePackMainActivity | |||||
| /** | |||||
| * author suliang | |||||
| * create 2022/4/6 19:18 | |||||
| * Describe: 测试界面 总测 课时测 | |||||
| */ | |||||
| class LearnExamActivity : UIBaseActivity() { | |||||
| companion object { | |||||
| @JvmStatic | |||||
| fun newInstance(context: Context) { | |||||
| context.startActivity(Intent(context, LearnExamActivity::class.java)) | |||||
| } | |||||
| } | |||||
| /** 音频测试、单词测试布局 */ | |||||
| private var _normalBinding : ActivityLearnExamWordBinding? = null | |||||
| /**布局 */ | |||||
| private var _spellBinding : ActivityLearnSpellBinding? = null | |||||
| override fun setContentView() { | |||||
| _normalBinding = ActivityLearnExamWordBinding.inflate(layoutInflater) | |||||
| setContentView(_normalBinding!!.root) | |||||
| } | |||||
| override fun initActivity(savedInstanceState : Bundle?) { | |||||
| } | |||||
| override fun loadData() { | |||||
| _normalBinding?.let { | |||||
| it.incLearnTitle.voiceSwitch.soundWayChange.observe(this){ | |||||
| println("发音 --》 $it ") | |||||
| } | |||||
| it.incLearnTitle.voiceSwitch.setSoundWay(AppConstants.SOUND_TYPE_US) | |||||
| } | |||||
| } | |||||
| } |
| package com.xkl.cdl.module.learn | |||||
| import androidx.appcompat.app.AppCompatActivity | |||||
| import android.os.Bundle | |||||
| import com.xkl.cdl.R | |||||
| class LearnExamWordActivity : AppCompatActivity() { | |||||
| override fun onCreate(savedInstanceState: Bundle?) { | |||||
| super.onCreate(savedInstanceState) | |||||
| setContentView(R.layout.activity_learn_exam_word) | |||||
| } | |||||
| } |
| import com.suliang.common.extension.replaceFragment | import com.suliang.common.extension.replaceFragment | ||||
| import com.suliang.common.util.LogUtil | import com.suliang.common.util.LogUtil | ||||
| import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
| import com.xkl.cdl.data.AppConstants | |||||
| import com.xkl.cdl.data.manager.db.DbControlBase | import com.xkl.cdl.data.manager.db.DbControlBase | ||||
| import com.xkl.cdl.databinding.FragmentCourseMainBinding | import com.xkl.cdl.databinding.FragmentCourseMainBinding | ||||
| import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | ||||
| //学后总测 | //学后总测 | ||||
| // replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) | // replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) | ||||
| //学习目录 | //学习目录 | ||||
| replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance()) | |||||
| vm.courseDetail.run { | |||||
| if (st_before == AppConstants.NOT_DOING) { //学前总测未做 | |||||
| replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_BEFORE_TOTAL)) | |||||
| } else if (courseLearnProgress != 100.0) { //学习未完成 | |||||
| replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance()) | |||||
| } else { //学习完成,学后总测试界面 | |||||
| replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 供其他地方手动调用切换界面 | |||||
| * @param position Int 1目录页 2学后总测 | |||||
| */ | |||||
| fun changeFragment(position: Int){ | |||||
| when(position){ | |||||
| 1 -> replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance()) | |||||
| 2 -> replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) | |||||
| } | |||||
| } | } | ||||
| package com.xkl.cdl.module.m_center_learn.coursechildren | package com.xkl.cdl.module.m_center_learn.coursechildren | ||||
| import android.provider.ContactsContract | |||||
| import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||
| import com.suliang.common.base.activity.ToastEvent | |||||
| import com.suliang.common.base.viewmodel.BaseViewModel | import com.suliang.common.base.viewmodel.BaseViewModel | ||||
| import com.suliang.common.extension.diskIo2DiskIo | import com.suliang.common.extension.diskIo2DiskIo | ||||
| import com.suliang.common.extension.diskIo2Main | |||||
| import com.xkl.cdl.data.bean.course.Course | import com.xkl.cdl.data.bean.course.Course | ||||
| import com.xkl.cdl.data.bean.course.CourseDetail | import com.xkl.cdl.data.bean.course.CourseDetail | ||||
| import com.xkl.cdl.data.bean.course.ExamBean | |||||
| import com.xkl.cdl.data.bean.course.Lesson | import com.xkl.cdl.data.bean.course.Lesson | ||||
| import com.xkl.cdl.data.manager.db.DBCourseManager | |||||
| import com.xkl.cdl.data.manager.db.DbControlBase | import com.xkl.cdl.data.manager.db.DbControlBase | ||||
| import com.xkl.cdl.data.repository.DataRepository | import com.xkl.cdl.data.repository.DataRepository | ||||
| import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | ||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers | |||||
| import io.reactivex.rxjava3.core.Observable | import io.reactivex.rxjava3.core.Observable | ||||
| import io.reactivex.rxjava3.core.Observer | import io.reactivex.rxjava3.core.Observer | ||||
| import io.reactivex.rxjava3.disposables.Disposable | import io.reactivex.rxjava3.disposables.Disposable | ||||
| import java.util.* | import java.util.* | ||||
| import kotlin.collections.ArrayList | |||||
| import kotlin.collections.HashMap | |||||
| /** | /** | ||||
| * author suliang | * author suliang | ||||
| * create 2022/3/28 15:26 | * create 2022/3/28 15:26 | ||||
| * Describe: | * Describe: | ||||
| * @property courseIndex 该课程在课程包的课程集合中的位置 | * @property courseIndex 该课程在课程包的课程集合中的位置 | ||||
| * | |||||
| */ | */ | ||||
| class CourseMainFragmentViewModel(val courseIndex: Int) : BaseViewModel() { | |||||
| class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||||
| //课程包主页的ViewModel | //课程包主页的ViewModel | ||||
| lateinit var coursePackMainActivityVM: CoursePackMainActivityViewModel | |||||
| lateinit var dbControlBase: DbControlBase | |||||
| lateinit var coursePackMainActivityVM : CoursePackMainActivityViewModel | |||||
| lateinit var dbControlBase : DbControlBase | |||||
| //当前操作课程 | //当前操作课程 | ||||
| lateinit var course: Course | |||||
| lateinit var course : Course | |||||
| /** 课程的收藏本: 目前仅作用在口语课程会使用到该数据 */ | /** 课程的收藏本: 目前仅作用在口语课程会使用到该数据 */ | ||||
| lateinit var collectList: HashMap<String, Long> | |||||
| lateinit var collectList : HashMap<String, Long> | |||||
| /** 复习的数据 */ | /** 复习的数据 */ | ||||
| lateinit var reviewDataList: MutableList<String> | |||||
| lateinit var reviewDataList : MutableList<String> | |||||
| /** 课程所有课时 */ | /** 课程所有课时 */ | ||||
| lateinit var allLesson: List<Lesson> | |||||
| lateinit var allLesson : List<Lesson> | |||||
| //课程的详细信息 | //课程的详细信息 | ||||
| lateinit var courseDetail: CourseDetail | |||||
| lateinit var courseDetail : CourseDetail | |||||
| //获取课程的统计信息 | //获取课程的统计信息 | ||||
| //课程收藏本: 主要用于口语对话学习的数据 | //课程收藏本: 主要用于口语对话学习的数据 | ||||
| //获取课程的复习数据 | //获取课程的复习数据 | ||||
| fun loadMain() : MutableLiveData<Boolean> { | fun loadMain() : MutableLiveData<Boolean> { | ||||
| showHideLoading(true) | showHideLoading(true) | ||||
| val mutableLiveData = MutableLiveData<Boolean>() | val mutableLiveData = MutableLiveData<Boolean>() | ||||
| Observable.concat( | |||||
| DataRepository.getCourseStatistics().flatMap { | |||||
| courseDetail = it | |||||
| Observable.concat(DataRepository.getCourseStatistics().flatMap { | |||||
| courseDetail = it | |||||
| return@flatMap DataRepository.getCourseAllLesson(dbControlBase, it) | return@flatMap DataRepository.getCourseAllLesson(dbControlBase, it) | ||||
| }, DataRepository.getReviewData() | |||||
| , DataRepository.getCourseCollect()) | |||||
| .compose(diskIo2DiskIo()) | |||||
| .subscribe(object :Observer<Any>{ | |||||
| override fun onSubscribe(d: Disposable?) { | |||||
| }, DataRepository.getReviewData(), DataRepository.getCourseCollect()).compose(diskIo2DiskIo()) | |||||
| .subscribe(object : Observer<Any> { | |||||
| override fun onSubscribe(d : Disposable?) { | |||||
| } | } | ||||
| override fun onNext(t: Any?) { | |||||
| override fun onNext(t : Any?) { | |||||
| t?.let { it -> | t?.let { it -> | ||||
| if (it is List<*>){ | |||||
| if (it is List<*>) { | |||||
| allLesson = it as List<Lesson> | allLesson = it as List<Lesson> | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| override fun onError(e: Throwable?) { | |||||
| override fun onError(e : Throwable?) { | |||||
| e?.printStackTrace() | e?.printStackTrace() | ||||
| showHideLoading(false) | |||||
| showToast(ToastEvent(content = "数据加载异常")) | |||||
| } | } | ||||
| override fun onComplete() { | override fun onComplete() { | ||||
| showHideLoading(false) | showHideLoading(false) | ||||
| mutableLiveData.postValue(true) | mutableLiveData.postValue(true) | ||||
| } | } | ||||
| }) | }) | ||||
| return mutableLiveData | return mutableLiveData | ||||
| } | } | ||||
| /** | |||||
| * 获取课程的测试数据 | |||||
| * @param testType Int 测试类型 | |||||
| * @param lesson Lesson? 课时章节 | |||||
| */ | |||||
| fun loadTest(testType : Int, lesson : Lesson? = null) : MutableLiveData<List<ExamBean>> { | |||||
| val result = MutableLiveData<List<ExamBean>>() | |||||
| // showHideLoading(true) | |||||
| Observable.create<List<ExamBean>> { | |||||
| it.onNext(DBCourseManager.queryLearnTest(dbControlBase, testType, lesson)) | |||||
| it.onComplete() | |||||
| }.compose(diskIo2Main()).subscribe({ | |||||
| // showHideLoading(false) | |||||
| result.value = it | |||||
| }, { | |||||
| it.printStackTrace() | |||||
| // showHideLoading(false) | |||||
| }) | |||||
| return result | |||||
| } | |||||
| } | } |
| import com.suliang.common.extension.setHtml | import com.suliang.common.extension.setHtml | ||||
| import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
| import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
| import com.xkl.cdl.data.bean.course.ExamBean | |||||
| import com.xkl.cdl.data.manager.CourseManager | |||||
| import com.xkl.cdl.databinding.FragmentCourseTotalTestBinding | import com.xkl.cdl.databinding.FragmentCourseTotalTestBinding | ||||
| import com.xkl.cdl.module.learn.LearnExamActivity | |||||
| /** | /** | ||||
| * 课程总测试: 学前总,学后总 | * 课程总测试: 学前总,学后总 | ||||
| * @property param1 String? | |||||
| * @property param2 String? | |||||
| * @property totalTestType 测试类型 | |||||
| */ | */ | ||||
| class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, CourseMainFragmentViewModel>() { | class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, CourseMainFragmentViewModel>() { | ||||
| override fun initViewModel(): CourseMainFragmentViewModel { | override fun initViewModel(): CourseMainFragmentViewModel { | ||||
| return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java] | return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java] | ||||
| } | } | ||||
| override fun initFirst() { | |||||
| super.initFirst() | |||||
| //由于使用的是与父类相同的loadingEvent,所以将此evnet移除 | |||||
| vm.loadingEvent.removeObservers(this) | |||||
| } | |||||
| //传递过来的总测类型: 学前,学后 | //传递过来的总测类型: 学前,学后 | ||||
| private var totalTestType = 0 | private var totalTestType = 0 | ||||
| //测试的数据 | |||||
| private var testData : List<ExamBean> = emptyList() | |||||
| override fun initFragment() { | override fun initFragment() { | ||||
| totalTestType = requireArguments().getInt(AppConfig.INTENT_1) | totalTestType = requireArguments().getInt(AppConfig.INTENT_1) | ||||
| } | |||||
| override fun loadData() { | |||||
| vm.loadTest(totalTestType).observe(this){ | |||||
| testData = it | |||||
| initView() | |||||
| } | |||||
| } | |||||
| private fun initView(){ | |||||
| binding.tvCountTip.text = CourseManager.expectedTestTime(vm.course.courseType,totalTestType,testData!!) | |||||
| when (totalTestType) { | when (totalTestType) { | ||||
| AppConstants.TEST_TYPE_BEFORE_TOTAL -> { | AppConstants.TEST_TYPE_BEFORE_TOTAL -> { | ||||
| //学前总测 | //学前总测 | ||||
| binding.run { | binding.run { | ||||
| tvTitle.setText(R.string.test_total_before_title) | tvTitle.setText(R.string.test_total_before_title) | ||||
| tvCountTip.text = "共25题,预计3分钟" | |||||
| button1.run { | |||||
| setText(R.string.start_learn) | |||||
| click { | |||||
| continueLearn(it) | |||||
| } | |||||
| } | |||||
| //按钮初始 | //按钮初始 | ||||
| button2.run { | button2.run { | ||||
| setText(R.string.start_test) | setText(R.string.start_test) | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| AppConstants.TEST_TYPE_AFTER_TOTAL -> { | AppConstants.TEST_TYPE_AFTER_TOTAL -> { | ||||
| //学后总测 | //学后总测 | ||||
| binding.run { | binding.run { | ||||
| when { | when { | ||||
| it == -1.0 -> { //未测 | it == -1.0 -> { //未测 | ||||
| tvMainTip.setText(R.string.test_total_after_tip_1) | tvMainTip.setText(R.string.test_total_after_tip_1) | ||||
| tvCountTip.text = "共25题,预计3分钟" | |||||
| button2.run { | button2.run { | ||||
| setText(R.string.test_type_after_total) | setText(R.string.test_type_after_total) | ||||
| click { view -> | click { view -> | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| override fun loadData() { | |||||
| } | |||||
| /**继续学习*/ | /**继续学习*/ | ||||
| private fun continueLearn(view: View) { | private fun continueLearn(view: View) { | ||||
| showToast("继续学习") | |||||
| (requireParentFragment() as CourseMainFragment).changeFragment(1) | |||||
| } | } | ||||
| /** 开始测试 */ | /** 开始测试 */ | ||||
| private fun startTest(view: View) { | private fun startTest(view: View) { | ||||
| showToast("开始测试") | |||||
| LearnExamActivity.newInstance(requireContext()) | |||||
| } | } | ||||
| } | } |
| import java.util.concurrent.TimeUnit | import java.util.concurrent.TimeUnit | ||||
| @SuppressLint("CustomSplashScreen") | @SuppressLint("CustomSplashScreen") | ||||
| class SplashActivity : BaseActivity<ActivitySplashBinding>(), LearnDialog.DialogFragmentListener { | |||||
| class SplashActivity : BaseActivity<ActivitySplashBinding>(){ | |||||
| override fun onCreate(savedInstanceState: Bundle?) { | override fun onCreate(savedInstanceState: Bundle?) { | ||||
| if (!isTaskRoot) { | if (!isTaskRoot) { | ||||
| // e.printStackTrace() | // e.printStackTrace() | ||||
| // } | // } | ||||
| learnDialog = LearnDialog().apply { | |||||
| show(supportFragmentManager,javaClass.name) | |||||
| } | |||||
| LogUtil.e("Dialog -- > ${learnDialog.hashCode()}") | |||||
| if (true) return | |||||
| // learnDialog = LearnDialog().apply { | |||||
| // show(supportFragmentManager,javaClass.name) | |||||
| // } | |||||
| // LogUtil.e("Dialog -- > ${learnDialog.hashCode()}") | |||||
| // if (true) return | |||||
| showHideLoading(true) | showHideLoading(true) | ||||
| } | } | ||||
| lateinit var learnDialog :LearnDialog | |||||
| lateinit var learnDialog1: LearnDialog | |||||
| override fun rightClick() { | |||||
| if (!this::learnDialog1.isInitialized) { | |||||
| learnDialog1 = LearnDialog() | |||||
| } | |||||
| LogUtil.e("Dialog 1 -- > ${learnDialog1.hashCode()}") | |||||
| learnDialog1.show(supportFragmentManager,javaClass.name) | |||||
| } | |||||
| // lateinit var learnDialog :LearnDialog | |||||
| // lateinit var learnDialog1: LearnDialog | |||||
| override fun leftClick() { | |||||
| learnDialog1.dismiss() | |||||
| } | |||||
| // override fun rightClick() { | |||||
| // if (!this::learnDialog1.isInitialized) { | |||||
| // learnDialog1 = LearnDialog() | |||||
| // } | |||||
| // LogUtil.e("Dialog 1 -- > ${learnDialog1.hashCode()}") | |||||
| // learnDialog1.show(supportFragmentManager,javaClass.name) | |||||
| // } | |||||
| // | |||||
| // override fun leftClick() { | |||||
| // learnDialog1.dismiss() | |||||
| // } | |||||
| } | } |
| package com.xkl.cdl.widget | |||||
| import android.content.Context | |||||
| import android.graphics.Bitmap | |||||
| import android.graphics.Canvas | |||||
| import android.graphics.Paint | |||||
| import android.util.AttributeSet | |||||
| import android.view.MotionEvent | |||||
| import android.view.View | |||||
| import androidx.core.content.res.ResourcesCompat | |||||
| import androidx.lifecycle.MutableLiveData | |||||
| import com.suliang.common.util.os.ScreenUtil | |||||
| import com.xkl.cdl.R | |||||
| import com.xkl.cdl.data.AppConstants | |||||
| /** | |||||
| * author suliang | |||||
| * create 2022/4/7 16:12 | |||||
| * Describe: 自定义英美发音switch | |||||
| */ | |||||
| class VoiceSwitch @JvmOverloads constructor(context : Context, attr : AttributeSet? = null, defStyle : Int = 0) : | |||||
| View(context, attr, defStyle) { | |||||
| //背景图片和滑块 | |||||
| private val strack : Bitmap by lazy { | |||||
| val drawable = ResourcesCompat.getDrawable(resources, R.drawable.shape_switch_track, null)!! | |||||
| val createBitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) | |||||
| val canvas = Canvas(createBitmap) | |||||
| drawable.setBounds(0,0,drawable.intrinsicWidth,drawable.intrinsicHeight) | |||||
| drawable.draw(canvas) | |||||
| createBitmap | |||||
| // BitmapFactory.decodeResource(resources, R.drawable.shape_switch_track) | |||||
| } | |||||
| private val thumb : Bitmap by lazy { | |||||
| val drawable = ResourcesCompat.getDrawable(resources, R.drawable.shape_switch_thumb, null)!! | |||||
| val createBitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) | |||||
| val canvas = Canvas(createBitmap) | |||||
| drawable.setBounds(0,0,drawable.intrinsicWidth,drawable.intrinsicHeight) | |||||
| drawable.draw(canvas) | |||||
| createBitmap | |||||
| } | |||||
| //默认英音 | |||||
| private var soundWay = AppConstants.SOUND_TYPE_UK | |||||
| private val uk = "英音" | |||||
| private val us = "美音" | |||||
| val soundWayChange = MutableLiveData<Int>() | |||||
| private var w : Int = 0 | |||||
| private var h : Int = 0 | |||||
| private val paint : Paint = Paint().apply { | |||||
| isAntiAlias = true | |||||
| textSize = ScreenUtil.dp2px(12f).toFloat() | |||||
| color = ResourcesCompat.getColor(resources,R.color.main_text_color,null) | |||||
| textAlign = Paint.Align.CENTER | |||||
| style = Paint.Style.FILL | |||||
| } | |||||
| override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int) { | |||||
| w = strack.width + paddingStart + paddingRight | |||||
| h = strack.height | |||||
| setMeasuredDimension(w,h) | |||||
| } | |||||
| override fun onDraw(canvas : Canvas) { | |||||
| // super.onDraw(canvas) | |||||
| //背景滑块 | |||||
| // val drawable = ResourcesCompat.getDrawable(resources, R.drawable.shape_switch_track, null)!! | |||||
| // drawable.setBounds(0,0,drawable.intrinsicWidth,drawable.intrinsicHeight) | |||||
| // drawable.draw(canvas) | |||||
| canvas.drawBitmap(strack,paddingStart.toFloat(),0f,paint) | |||||
| when(soundWay){ | |||||
| AppConstants.SOUND_TYPE_UK -> { | |||||
| canvas.drawBitmap(thumb,0f,0f,paint) //左边 | |||||
| canvas.drawText(uk, thumb.width/2f + paddingStart , h/2 + ((paint.fontMetrics.bottom - paint.fontMetrics.top)/2) - paint.fontMetrics.bottom,paint) | |||||
| } | |||||
| AppConstants.SOUND_TYPE_US -> { | |||||
| canvas.drawBitmap(thumb,strack.width - thumb.width.toFloat(), 0f,paint) //右边 | |||||
| canvas.drawText(us,(w - thumb.width - paddingRight) + thumb.width/2f,h/2 + ((paint.fontMetrics.bottom - paint.fontMetrics.top)/2) - paint.fontMetrics.bottom,paint) | |||||
| } | |||||
| } | |||||
| } | |||||
| override fun onTouchEvent(event : MotionEvent) : Boolean { | |||||
| when(event.action){ | |||||
| MotionEvent.ACTION_UP -> { | |||||
| soundWay = if (soundWay == AppConstants.SOUND_TYPE_UK) AppConstants.SOUND_TYPE_US else AppConstants.SOUND_TYPE_UK | |||||
| soundWayChange.value = soundWay | |||||
| invalidate() | |||||
| } | |||||
| } | |||||
| return true | |||||
| } | |||||
| /** 设置默认发音 */ | |||||
| fun setSoundWay(way : Int){ | |||||
| soundWay = way | |||||
| invalidate() | |||||
| } | |||||
| } |
| <?xml version="1.0" encoding="utf-8"?><!--滑块图片--> | |||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" | |||||
| android:shape="rectangle"> | |||||
| <size android:height="24dp" android:width="40dp" /> | |||||
| <corners android:radius="12dp" /> | |||||
| <solid android:color="@color/white" /> | |||||
| <stroke | |||||
| android:width="1dp" | |||||
| android:color="@color/gray_1" /> | |||||
| </shape> |
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <!--背景图片:滑动条--> | |||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" | |||||
| android:shape="rectangle"> | |||||
| <size android:height="24dp" android:width="50dp" /> | |||||
| <corners android:radius="12dp" /> | |||||
| <solid android:color="@color/gray_1" /> | |||||
| </shape> |
| xmlns:tools="http://schemas.android.com/tools" | xmlns:tools="http://schemas.android.com/tools" | ||||
| android:layout_width="match_parent" | android:layout_width="match_parent" | ||||
| android:layout_height="match_parent" | android:layout_height="match_parent" | ||||
| tools:context=".module.learn.LearnExamWordActivity"> | |||||
| android:background="@color/white" | |||||
| tools:context=".module.learn.LearnExamActivity"> | |||||
| <include | |||||
| android:id="@+id/inc_learn_title" | |||||
| layout="@layout/inc_learn_title" /> | |||||
| </androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> |
| app:layout_constraintTop_toTopOf="@+id/button_2" | app:layout_constraintTop_toTopOf="@+id/button_2" | ||||
| app:strokeColor="@color/theme_color" | app:strokeColor="@color/theme_color" | ||||
| app:strokeWidth="@dimen/line_height" | app:strokeWidth="@dimen/line_height" | ||||
| android:visibility="gone" | |||||
| tools:visibility="visible"/> | |||||
| /> | |||||
| <Button | <Button | ||||
| android:id="@+id/button_2" | android:id="@+id/button_2" |
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <layout xmlns:android="http://schemas.android.com/apk/res/android" | |||||
| xmlns:tools="http://schemas.android.com/tools" | |||||
| xmlns:app="http://schemas.android.com/apk/res-auto"> | |||||
| <data> | |||||
| <variable | |||||
| name="examType" | |||||
| type="Long" /> | |||||
| <variable | |||||
| name="totalNum" | |||||
| type="Long" /> | |||||
| <variable | |||||
| name="progressNum" | |||||
| type="Integer" /> | |||||
| <!--时间 : 毫秒--> | |||||
| <variable | |||||
| name="time" | |||||
| type="Long" /> | |||||
| <variable | |||||
| name="correctNum" | |||||
| type="Long" /> | |||||
| <variable | |||||
| name="errorNum" | |||||
| type="Long" /> | |||||
| </data> | |||||
| <androidx.constraintlayout.widget.ConstraintLayout | |||||
| android:layout_width="match_parent" | |||||
| android:layout_height="wrap_content" | |||||
| tools:context=".ui.mainlearncenter.com_fragment.TestingStatisticFragment"> | |||||
| <!--测试数量进度--> | |||||
| <ProgressBar | |||||
| android:id="@+id/progress_test_number" | |||||
| style="?android:attr/progressBarStyleHorizontal" | |||||
| android:layout_width="match_parent" | |||||
| android:layout_height="@dimen/dp_2" | |||||
| android:indeterminateOnly="false" | |||||
| app:layout_constraintTop_toTopOf="parent" | |||||
| android:progressDrawable="@drawable/progress_statistics" | |||||
| android:max="@{(int)totalNum}" | |||||
| android:progress="@{progressNum}"/> | |||||
| <!--错误数量--> | |||||
| <TextView | |||||
| android:id="@+id/tv_error_number" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="@dimen/dp_34" | |||||
| android:textSize="@dimen/dp_12" | |||||
| android:textColor="@color/color_6" | |||||
| android:drawablePadding="@dimen/dp_2" | |||||
| app:layout_constraintRight_toRightOf="parent" | |||||
| app:layout_constraintTop_toBottomOf="@+id/progress_test_number" | |||||
| android:drawableLeft="@mipmap/test_error_icon" | |||||
| android:text="@{@string/error_number_format(errorNum)}" | |||||
| android:layout_marginRight="@dimen/dp_16" | |||||
| android:gravity="center"/> | |||||
| <!--正确数量--> | |||||
| <TextView | |||||
| android:id="@+id/tv_correct_number" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="@dimen/dp_34" | |||||
| android:textSize="@dimen/dp_12" | |||||
| android:textColor="@color/color_6" | |||||
| android:drawableLeft="@mipmap/test_right_icon" | |||||
| android:drawablePadding="@dimen/dp_2" | |||||
| app:layout_constraintRight_toLeftOf="@+id/tv_error_number" | |||||
| app:layout_constraintTop_toBottomOf="@+id/progress_test_number" | |||||
| android:text="@{@string/correct_number_foramt(correctNum)}" | |||||
| android:layout_marginRight="@dimen/dp_16" | |||||
| android:gravity="center" /> | |||||
| <!--测试类型--> | |||||
| <TextView | |||||
| android:id="@+id/tv_test_type_name" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="@dimen/dp_34" | |||||
| android:textSize="@dimen/dp_12" | |||||
| android:textColor="@color/color_6" | |||||
| android:drawablePadding="@dimen/dp_2" | |||||
| app:layout_constraintLeft_toLeftOf="parent" | |||||
| app:layout_constraintTop_toBottomOf="@+id/progress_test_number" | |||||
| tools:text="学后总测试" | |||||
| android:gravity="center" | |||||
| android:layout_marginLeft="@dimen/dp_16" | |||||
| android:layout_marginRight="@dimen/dp_16" | |||||
| android:singleLine="true" | |||||
| android:maxEms="6" | |||||
| android:ellipsize="end" | |||||
| app:learnTestExamType="@{examType}" | |||||
| /> | |||||
| <!--测试时间--> | |||||
| <TextView | |||||
| android:id="@+id/tv_test_time" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="@dimen/dp_34" | |||||
| android:textSize="@dimen/dp_12" | |||||
| android:textColor="@color/color_6" | |||||
| tools:text="00:00:00" | |||||
| app:statisticsTotalTime="@{time}" | |||||
| android:gravity="center" | |||||
| app:layout_constraintLeft_toRightOf="@+id/tv_test_type_name" | |||||
| app:layout_constraintTop_toBottomOf="@+id/progress_test_number" | |||||
| android:layout_marginLeft="@dimen/dp_16"/> | |||||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||||
| </layout> |
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||||
| xmlns:tools="http://schemas.android.com/tools" | |||||
| android:layout_width="0dp" | |||||
| android:layout_height="@dimen/title_bar_height" | |||||
| app:layout_constraintEnd_toEndOf="parent" | |||||
| app:layout_constraintStart_toStartOf="parent" | |||||
| app:layout_constraintTop_toTopOf="parent" | |||||
| tools:showIn="@layout/activity_learn_exam_word"> | |||||
| <ImageView | |||||
| android:id="@+id/img_back" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="match_parent" | |||||
| android:layout_centerVertical="true" | |||||
| android:paddingStart="@dimen/global_spacing" | |||||
| android:scaleType="centerInside" | |||||
| android:src="@drawable/ic_back" | |||||
| app:layout_constraintBottom_toBottomOf="parent" | |||||
| app:layout_constraintStart_toStartOf="parent" | |||||
| app:layout_constraintTop_toTopOf="parent" /> | |||||
| <TextView | |||||
| android:id="@+id/tv_title" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="wrap_content" | |||||
| android:layout_marginStart="66dp" | |||||
| android:ellipsize="end" | |||||
| android:maxLines="1" | |||||
| android:text="小学英语单词课程小学英语单词课程小学英语单词课程" | |||||
| android:textColor="@color/main_text_color" | |||||
| android:textSize="@dimen/bigSize" | |||||
| android:textStyle="bold" | |||||
| app:layout_constrainedWidth="true" | |||||
| app:layout_constraintBottom_toBottomOf="parent" | |||||
| app:layout_constraintEnd_toStartOf="@+id/tv_num_progress" | |||||
| app:layout_constraintHorizontal_chainStyle="packed" | |||||
| app:layout_constraintStart_toStartOf="parent" | |||||
| app:layout_constraintTop_toTopOf="parent" | |||||
| app:layout_goneMarginEnd="66dp" /> | |||||
| <TextView | |||||
| android:id="@+id/tv_num_progress" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="wrap_content" | |||||
| android:layout_marginStart="4dp" | |||||
| android:layout_marginEnd="66dp" | |||||
| android:text="90/100" | |||||
| android:textColor="@color/main_text_color" | |||||
| android:textSize="@dimen/bigSize" | |||||
| android:textStyle="bold" | |||||
| android:visibility="visible" | |||||
| app:layout_constrainedWidth="true" | |||||
| app:layout_constraintBottom_toBottomOf="parent" | |||||
| app:layout_constraintEnd_toEndOf="parent" | |||||
| app:layout_constraintStart_toEndOf="@id/tv_title" | |||||
| app:layout_constraintTop_toTopOf="parent" /> | |||||
| <com.xkl.cdl.widget.VoiceSwitch | |||||
| android:id="@+id/voice_switch" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="wrap_content" | |||||
| android:paddingEnd="@dimen/global_spacing" | |||||
| android:visibility="visible" | |||||
| app:layout_constraintBottom_toBottomOf="parent" | |||||
| app:layout_constraintEnd_toEndOf="parent" | |||||
| app:layout_constraintTop_toTopOf="parent" /> | |||||
| </androidx.constraintlayout.widget.ConstraintLayout> |
| <attr name="progressNumber" format="integer"/> <!--弧形进度--> | <attr name="progressNumber" format="integer"/> <!--弧形进度--> | ||||
| <attr name="backgroundColor" format="color"/> <!--背景颜色--> | <attr name="backgroundColor" format="color"/> <!--背景颜色--> | ||||
| </declare-styleable> | </declare-styleable> | ||||
| </resources> | </resources> |
| <color name="gray_3">#FAFAFA</color> | <color name="gray_3">#FAFAFA</color> | ||||
| <color name="green_1">#40A540</color> | <color name="green_1">#40A540</color> | ||||
| <color name="red_1">#FFF26255</color> | <color name="red_1">#FFF26255</color> | ||||
| <color name="red_2">#F7874F</color> | |||||
| <color name="num0">#E50213</color> | <color name="num0">#E50213</color> |
| targetSdk rootProject.ext.android.target_sdk_version | targetSdk rootProject.ext.android.target_sdk_version | ||||
| versionCode 1 | versionCode 1 | ||||
| versionName "1.0" | versionName "1.0" | ||||
| // multiDexEnabled true | |||||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
| consumerProguardFiles "consumer-rules.pro" | consumerProguardFiles "consumer-rules.pro" | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| compileOptions { | compileOptions { | ||||
| coreLibraryDesugaringEnabled true | |||||
| sourceCompatibility JavaVersion.VERSION_1_8 | sourceCompatibility JavaVersion.VERSION_1_8 | ||||
| targetCompatibility JavaVersion.VERSION_1_8 | targetCompatibility JavaVersion.VERSION_1_8 | ||||
| } | } | ||||
| dependencies { | dependencies { | ||||
| // implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs') | // implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs') | ||||
| implementation 'androidx.legacy:legacy-support-v4:1.0.0' | |||||
| // implementation 'androidx.legacy:legacy-support-v4:1.0.0' | |||||
| rootProject.ext.dependencies_required.each{ k, v -> implementation v} | rootProject.ext.dependencies_required.each{ k, v -> implementation v} | ||||
| testImplementation rootProject.ext.dependencies_testImplementation.junit | testImplementation rootProject.ext.dependencies_testImplementation.junit | ||||
| rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v} | rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v} | ||||
| //RxJava RxAndroid | //RxJava RxAndroid | ||||
| api customDependencies.RxJava | api customDependencies.RxJava | ||||
| api customDependencies.RxAndroid | api customDependencies.RxAndroid | ||||
| coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | |||||
| package com.suliang.common.base | package com.suliang.common.base | ||||
| import android.R.attr | |||||
| import android.graphics.Color | import android.graphics.Color | ||||
| import android.os.Bundle | import android.os.Bundle | ||||
| import android.view.LayoutInflater | import android.view.LayoutInflater | ||||
| import android.graphics.drawable.ColorDrawable | import android.graphics.drawable.ColorDrawable | ||||
| import android.util.DisplayMetrics | import android.util.DisplayMetrics | ||||
| import androidx.fragment.app.FragmentManager | |||||
| import androidx.fragment.app.FragmentTransaction | |||||
| import android.R.attr.tag | |||||
| import androidx.fragment.app.Fragment | |||||
| import java.lang.IllegalStateException | |||||
| /** | /** | ||||
| dialog!!.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) | dialog!!.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) | ||||
| } | } | ||||
| } | } | ||||
| abstract fun initFragment() | abstract fun initFragment() | ||||
| /** 是否布局背景透明 */ | /** 是否布局背景透明 */ | ||||
| open fun isTransparent():Boolean { | open fun isTransparent():Boolean { | ||||
| return true | return true | ||||
| } | } | ||||
| override fun show(manager : FragmentManager, tag : String?) { | |||||
| //避免重复添加的异常 java.lang.IllegalStateException: Fragment already added | |||||
| val fragment = manager.findFragmentByTag(tag) | |||||
| if (fragment != null) { | |||||
| val fragmentTransaction = manager.beginTransaction() | |||||
| fragmentTransaction.remove(fragment) | |||||
| fragmentTransaction.commitAllowingStateLoss() | |||||
| } | |||||
| //避免状态丢失的异常 java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState | |||||
| try { | |||||
| super.show(manager, tag) | |||||
| } catch (e : IllegalStateException) { | |||||
| e.printStackTrace() | |||||
| } | |||||
| } | |||||
| } | } |
| companion object{ | companion object{ | ||||
| @JvmStatic | @JvmStatic | ||||
| fun newInstance(msg : String): LoadingDialog { | |||||
| fun newInstance(msg : String?): LoadingDialog { | |||||
| return LoadingDialog().apply { | return LoadingDialog().apply { | ||||
| arguments?.putString(AppConfig.INTENT_1,msg) | |||||
| if (!msg.isNullOrEmpty()) { | |||||
| arguments?.putString(AppConfig.INTENT_1, msg) | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| interface ViewBehavior { | interface ViewBehavior { | ||||
| /** 是否显示视图 */ | /** 是否显示视图 */ | ||||
| fun showHideLoading(isShow: Boolean) | fun showHideLoading(isShow: Boolean) | ||||
| /** 显示toast */ | /** 显示toast */ | ||||
| fun showToast(msg: ToastEvent) | fun showToast(msg: ToastEvent) | ||||
| package com.suliang.common.base.activity | package com.suliang.common.base.activity | ||||
| import android.content.Intent | |||||
| import android.graphics.Color | |||||
| import android.os.Bundle | |||||
| import android.widget.Toast | |||||
| import androidx.annotation.StringRes | |||||
| import androidx.viewbinding.ViewBinding | import androidx.viewbinding.ViewBinding | ||||
| import com.suliang.common.base.LoadingDialog | |||||
| import com.suliang.common.extension.initBinding | import com.suliang.common.extension.initBinding | ||||
| import com.suliang.common.util.ActivityStackManager | |||||
| import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly | |||||
| import com.suliang.common.base.ViewBehavior | |||||
| import com.suliang.common.util.LogUtil | |||||
| /** | /** | ||||
| * 基类Activity | * 基类Activity | ||||
| */ | */ | ||||
| abstract class BaseActivity<VB : ViewBinding> : LifecycleLogActivity(), ViewBehavior { | |||||
| private var _binding: VB? = null | |||||
| val binding: VB | |||||
| abstract class BaseActivity<VB : ViewBinding> : UIBaseActivity() { | |||||
| private var _binding : VB? = null | |||||
| val binding : VB | |||||
| get() = _binding!! | get() = _binding!! | ||||
| // val binding : VB by lazy { | |||||
| // initViewBinding() | |||||
| // } | |||||
| override fun onCreate(savedInstanceState: Bundle?) { | |||||
| super.onCreate(savedInstanceState) | |||||
| //入栈 | |||||
| ActivityStackManager.addActivity(this) | |||||
| initFirst() | |||||
| initActivity(savedInstanceState) | |||||
| loadData() | |||||
| } | |||||
| /*** | |||||
| * 实例化binding, | |||||
| */ | |||||
| protected open fun initFirst() { | |||||
| setLayoutBefore() | |||||
| override fun setContentView() { | |||||
| _binding = initBinding(layoutInflater) | _binding = initBinding(layoutInflater) | ||||
| setContentView(binding.root) | setContentView(binding.root) | ||||
| initStatusBar() | |||||
| setLayoutAfter() | |||||
| } | } | ||||
| /** | |||||
| * 状态栏初始化方法 | |||||
| */ | |||||
| open fun initStatusBar() { | |||||
| statusBarOnly { | |||||
| fitWindow = true | |||||
| color = Color.WHITE | |||||
| light = true | |||||
| } | |||||
| } | |||||
| override fun onDestroy() { | override fun onDestroy() { | ||||
| super.onDestroy() | super.onDestroy() | ||||
| ActivityStackManager.removeActivity(this) | |||||
| _binding = null | _binding = null | ||||
| } | } | ||||
| /** 布局前操作 : 空实现 */ | |||||
| open fun setLayoutBefore() { | |||||
| } | |||||
| /** | |||||
| * 布局后操作:空实现 | |||||
| */ | |||||
| open fun setLayoutAfter() { | |||||
| } | |||||
| /** | |||||
| * onCreate中给与app中用户的具体实现 | |||||
| * binding 与 ViewModel都已实现好 | |||||
| * @param savedInstanceState Bundle? | |||||
| */ | |||||
| abstract fun initActivity(savedInstanceState: Bundle?) | |||||
| /** onCreate()最后实现的数据加载调用 */ | |||||
| abstract fun loadData() | |||||
| private val loadingDialog by lazy { | |||||
| LoadingDialog() | |||||
| } | |||||
| /** toast提示 */ | |||||
| override fun showToast(event: ToastEvent) { | |||||
| val showTime = if (event.showLong) Toast.LENGTH_LONG else Toast.LENGTH_LONG | |||||
| event.content?.let { | |||||
| Toast.makeText(this, it, showTime).show() | |||||
| } ?: let { | |||||
| Toast.makeText(this, event.contentResId!!, showTime).show() | |||||
| } | |||||
| } | |||||
| protected fun showToast(text: String, showLong: Boolean = false) { | |||||
| showToast(ToastEvent(content = text, showLong = showLong)) | |||||
| } | |||||
| protected fun showToast(@StringRes resId: Int, showLong: Boolean = false) { | |||||
| showToast(ToastEvent(contentResId = resId, showLong = showLong)) | |||||
| } | |||||
| override fun showHideLoading(isShow: Boolean) { | |||||
| if (isShow) { | |||||
| loadingDialog.show(supportFragmentManager, javaClass.name) | |||||
| } else if (loadingDialog.activity != null) { | |||||
| loadingDialog.dismiss() | |||||
| } | |||||
| } | |||||
| /** 单独封装一个方法,用于进行无参数的activity的跳转 */ | |||||
| override fun startActivity(clazz: Class<*>) { | |||||
| startActivity(Intent(this, clazz)) | |||||
| } | |||||
| //覆写该方法,避免多次跳转失败的问题 | |||||
| override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) { | |||||
| if (checkDoubleClick(intent)) { | |||||
| super.startActivityForResult(intent, requestCode, options) | |||||
| } else { | |||||
| LogUtil.e("多次跳转无效操作--> $intent") | |||||
| } | |||||
| } | |||||
| // private var mActivityJumpTag : String = "" // activity跳转tag | |||||
| private var clickTime = 0L //点击时间 | |||||
| //避免多次点击跳转到同一个activity 和 避免短时间内多次点击跳转到不同activity | |||||
| private fun checkDoubleClick(intent: Intent): Boolean { | |||||
| val currentTime = System.currentTimeMillis() | |||||
| var result = true | |||||
| var tag: String = "" | |||||
| if (intent.component != null) { //显示跳转 | |||||
| tag = intent.component!!.className | |||||
| } else if (intent.action != null) { //隐式跳转 | |||||
| tag = intent.action!! | |||||
| } else { | |||||
| return true | |||||
| } | |||||
| // result = tag == mActivityJumpTag && currentTime - clickTime >= 500 | |||||
| result = currentTime - clickTime >= 500 | |||||
| // mActivityJumpTag = tag | |||||
| clickTime = currentTime | |||||
| return result | |||||
| } | |||||
| // 反射实现 ViewBinding | |||||
| // @SuppressWarnings("unchecked") | |||||
| // fun initBinding(inflater: LayoutInflater): VB { | |||||
| // val vbClass = | |||||
| // (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>() | |||||
| // val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java) | |||||
| // return inflate.invoke(null, inflater) as VB | |||||
| // } | |||||
| // https://juejin.cn/post/6926007534188773384#heading-1 | |||||
| // private fun initViewBinding(): VB{ | |||||
| // return reflexViewBinding(this::class.java,layoutInflater) | |||||
| // } | |||||
| // private fun <V : ViewBinding?> reflexViewBinding(aClass: Class<*>, from: LayoutInflater?): V { | |||||
| // return try { | |||||
| // val genericSuperclass: Type = aClass.genericSuperclass | |||||
| // if (genericSuperclass is ParameterizedType) { | |||||
| // val actualTypeArguments: Array<Type> = | |||||
| // (Objects.requireNonNull(genericSuperclass) as ParameterizedType).getActualTypeArguments() | |||||
| // for (actualTypeArgument in actualTypeArguments) { | |||||
| // var tClass: Class<*> | |||||
| // tClass = try { | |||||
| // actualTypeArgument as Class<*> | |||||
| // } catch (e: Exception) { | |||||
| // continue | |||||
| // } | |||||
| // if (ViewBinding::class.java.isAssignableFrom(tClass)) { | |||||
| // val inflate: Method = tClass.getMethod("inflate", LayoutInflater::class.java) | |||||
| // return inflate.invoke(null, from) as V | |||||
| // } | |||||
| // } | |||||
| // } | |||||
| // reflexViewBinding(aClass.superclass, from) | |||||
| // } catch (e: Exception) { | |||||
| // e.printStackTrace() | |||||
| // throw RuntimeException(e.message, e) | |||||
| // } | |||||
| // } | |||||
| } | } |
| import androidx.viewbinding.ViewBinding | import androidx.viewbinding.ViewBinding | ||||
| import com.suliang.common.base.viewmodel.BaseViewModel | import com.suliang.common.base.viewmodel.BaseViewModel | ||||
| import com.suliang.common.util.LogUtil | |||||
| /** | /** | ||||
| * Activity DataBinding 与 ViewModel基类,封装DataBinding和ViewModel | * Activity DataBinding 与 ViewModel基类,封装DataBinding和ViewModel | ||||
| vm.pageEvent.observe(this) { startActivity(it) } | vm.pageEvent.observe(this) { startActivity(it) } | ||||
| vm.toastEvent.observe(this){ showToast(it) } | vm.toastEvent.observe(this){ showToast(it) } | ||||
| vm.loadingEvent.observe(this) { showHideLoading(it)} | |||||
| vm.loadingEvent.observe(this) { | |||||
| LogUtil.e("监听到 activity vm 的 loadingEvent isShow $it") | |||||
| showHideLoading(it)} | |||||
| } | } | ||||
| package com.suliang.common.base.activity | |||||
| import android.content.Intent | |||||
| import android.graphics.Color | |||||
| import android.os.Bundle | |||||
| import android.widget.Toast | |||||
| import androidx.annotation.StringRes | |||||
| import com.suliang.common.base.LoadingDialog | |||||
| import com.suliang.common.base.ViewBehavior | |||||
| import com.suliang.common.util.ActivityStackManager | |||||
| import com.suliang.common.util.LogUtil | |||||
| import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly | |||||
| /** | |||||
| * author suliang | |||||
| * create 2022/4/7 11:45 | |||||
| * Describe: | |||||
| */ | |||||
| abstract class UIBaseActivity : LifecycleLogActivity(), ViewBehavior { | |||||
| override fun onCreate(savedInstanceState : Bundle?) { | |||||
| super.onCreate(savedInstanceState) | |||||
| //入栈 | |||||
| ActivityStackManager.addActivity(this) | |||||
| initFirst() | |||||
| initActivity(savedInstanceState) | |||||
| loadData() | |||||
| } | |||||
| /*** | |||||
| * 实例化binding, | |||||
| */ | |||||
| protected open fun initFirst() { | |||||
| setLayoutBefore() | |||||
| initContentView() | |||||
| initStatusBar() | |||||
| setLayoutAfter() | |||||
| } | |||||
| private fun initContentView() { | |||||
| setContentView() | |||||
| } | |||||
| abstract fun setContentView() | |||||
| /** | |||||
| * 状态栏初始化方法 | |||||
| */ | |||||
| open fun initStatusBar() { | |||||
| statusBarOnly { | |||||
| fitWindow = true | |||||
| color = Color.WHITE | |||||
| light = true | |||||
| } | |||||
| } | |||||
| override fun onDestroy() { | |||||
| super.onDestroy() | |||||
| ActivityStackManager.removeActivity(this) | |||||
| } | |||||
| /** 布局前操作 : 空实现 */ | |||||
| open fun setLayoutBefore() {} | |||||
| /** | |||||
| * 布局后操作:空实现 | |||||
| */ | |||||
| open fun setLayoutAfter() {} | |||||
| /** | |||||
| * onCreate中给与app中用户的具体实现 | |||||
| * binding 与 ViewModel都已实现好 | |||||
| * @param savedInstanceState Bundle? | |||||
| */ | |||||
| abstract fun initActivity(savedInstanceState : Bundle?) | |||||
| /** onCreate()最后实现的数据加载调用 */ | |||||
| abstract fun loadData() | |||||
| private var loadingDialog : LoadingDialog? = null | |||||
| private var dialogList = arrayListOf<String>() | |||||
| /** toast提示 */ | |||||
| override fun showToast(event : ToastEvent) { | |||||
| val showTime = if (event.showLong) Toast.LENGTH_LONG else Toast.LENGTH_LONG | |||||
| event.content?.let { | |||||
| Toast.makeText(this, it, showTime).show() | |||||
| } | |||||
| ?: let { | |||||
| Toast.makeText(this, event.contentResId!!, showTime).show() | |||||
| } | |||||
| } | |||||
| protected fun showToast(text : String, showLong : Boolean = false) { | |||||
| showToast(ToastEvent(content = text, showLong = showLong)) | |||||
| } | |||||
| protected fun showToast(@StringRes resId : Int, showLong : Boolean = false) { | |||||
| showToast(ToastEvent(contentResId = resId, showLong = showLong)) | |||||
| } | |||||
| override fun showHideLoading(isShow : Boolean) { | |||||
| val findFragment = supportFragmentManager.findFragmentByTag("loading") | |||||
| if (isShow) { //显示 | |||||
| findFragment?.let { | |||||
| if (findFragment is LoadingDialog) { | |||||
| findFragment.dialog?.show() | |||||
| } | |||||
| } | |||||
| ?: let { | |||||
| val loadingDialog = LoadingDialog.newInstance("") | |||||
| loadingDialog.show(supportFragmentManager, "loading") | |||||
| } | |||||
| } else { //关闭 | |||||
| findFragment?.let { | |||||
| if (it is LoadingDialog) { | |||||
| it.dismissAllowingStateLoss() | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /** 单独封装一个方法,用于进行无参数的activity的跳转 */ | |||||
| override fun startActivity(clazz : Class<*>) { | |||||
| startActivity(Intent(this, clazz)) | |||||
| } | |||||
| //覆写该方法,避免多次跳转失败的问题 | |||||
| override fun startActivityForResult(intent : Intent, requestCode : Int, options : Bundle?) { | |||||
| if (checkDoubleClick(intent)) { | |||||
| super.startActivityForResult(intent, requestCode, options) | |||||
| } else { | |||||
| LogUtil.e("多次跳转无效操作--> $intent") | |||||
| } | |||||
| } | |||||
| private var clickTime = 0L //点击时间 | |||||
| //避免多次点击跳转到同一个activity 和 避免短时间内多次点击跳转到不同activity | |||||
| private fun checkDoubleClick(intent : Intent) : Boolean { | |||||
| val currentTime = System.currentTimeMillis() | |||||
| var result = true | |||||
| var tag : String = "" | |||||
| if (intent.component != null) { //显示跳转 | |||||
| tag = intent.component!!.className | |||||
| } else if (intent.action != null) { //隐式跳转 | |||||
| tag = intent.action!! | |||||
| } else { | |||||
| return true | |||||
| } | |||||
| result = currentTime - clickTime >= 500 | |||||
| clickTime = currentTime | |||||
| return result | |||||
| } | |||||
| } | |||||
| // 反射实现 ViewBinding | |||||
| // @SuppressWarnings("unchecked") | |||||
| // fun initBinding(inflater: LayoutInflater): VB { | |||||
| // val vbClass = | |||||
| // (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>() | |||||
| // val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java) | |||||
| // return inflate.invoke(null, inflater) as VB | |||||
| // } | |||||
| // https://juejin.cn/post/6926007534188773384#heading-1 | |||||
| // private fun initViewBinding(): VB{ | |||||
| // return reflexViewBinding(this::class.java,layoutInflater) | |||||
| // } | |||||
| // private fun <V : ViewBinding?> reflexViewBinding(aClass: Class<*>, from: LayoutInflater?): V { | |||||
| // return try { | |||||
| // val genericSuperclass: Type = aClass.genericSuperclass | |||||
| // if (genericSuperclass is ParameterizedType) { | |||||
| // val actualTypeArguments: Array<Type> = | |||||
| // (Objects.requireNonNull(genericSuperclass) as ParameterizedType).getActualTypeArguments() | |||||
| // for (actualTypeArgument in actualTypeArguments) { | |||||
| // var tClass: Class<*> | |||||
| // tClass = try { | |||||
| // actualTypeArgument as Class<*> | |||||
| // } catch (e: Exception) { | |||||
| // continue | |||||
| // } | |||||
| // if (ViewBinding::class.java.isAssignableFrom(tClass)) { | |||||
| // val inflate: Method = tClass.getMethod("inflate", LayoutInflater::class.java) | |||||
| // return inflate.invoke(null, from) as V | |||||
| // } | |||||
| // } | |||||
| // } | |||||
| // reflexViewBinding(aClass.superclass, from) | |||||
| // } catch (e: Exception) { | |||||
| // e.printStackTrace() | |||||
| // throw RuntimeException(e.message, e) | |||||
| // } | |||||
| // } | |||||
| import androidx.viewbinding.ViewBinding | import androidx.viewbinding.ViewBinding | ||||
| import com.suliang.common.base.LoadingDialog | import com.suliang.common.base.LoadingDialog | ||||
| import com.suliang.common.base.ViewBehavior | import com.suliang.common.base.ViewBehavior | ||||
| import com.suliang.common.base.activity.BaseActivity | |||||
| import com.suliang.common.base.activity.ToastEvent | import com.suliang.common.base.activity.ToastEvent | ||||
| import com.suliang.common.extension.initBinding | import com.suliang.common.extension.initBinding | ||||
| import com.suliang.common.util.LogUtil | |||||
| /** | /** | ||||
| * Fragment ViewBinding基类,封装ViewDataBinding 自带懒加载 | * Fragment ViewBinding基类,封装ViewDataBinding 自带懒加载 | ||||
| * 界面显示出来后再加载数据 | * 界面显示出来后再加载数据 | ||||
| */ | */ | ||||
| abstract fun loadData() | abstract fun loadData() | ||||
| private val loadingDialog by lazy { | |||||
| LoadingDialog() | |||||
| } | |||||
| /** toast提示 */ | /** toast提示 */ | ||||
| override fun showToast(event: ToastEvent) { | override fun showToast(event: ToastEvent) { | ||||
| showToast(ToastEvent(contentResId = resId, showLong = showLong)) | showToast(ToastEvent(contentResId = resId, showLong = showLong)) | ||||
| } | } | ||||
| /** 调用父类的 加载框 */ | |||||
| override fun showHideLoading(isShow: Boolean) { | override fun showHideLoading(isShow: Boolean) { | ||||
| if (isShow) { | |||||
| loadingDialog.show(childFragmentManager, javaClass.name) | |||||
| } else if (loadingDialog.activity != null){ | |||||
| loadingDialog.dismiss() | |||||
| synchronized(this){ | |||||
| // LogUtil.e(" BaseFragment showHideLoding -> $isShow") | |||||
| (activity as BaseActivity<*>).showHideLoading(isShow) | |||||
| } | } | ||||
| } | } | ||||
| import androidx.lifecycle.ViewModel | import androidx.lifecycle.ViewModel | ||||
| import androidx.viewbinding.ViewBinding | import androidx.viewbinding.ViewBinding | ||||
| import com.suliang.common.base.viewmodel.BaseViewModel | import com.suliang.common.base.viewmodel.BaseViewModel | ||||
| import com.suliang.common.util.LogUtil | |||||
| /** | /** | ||||
| * Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel 自带懒加载 | * Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel 自带懒加载 | ||||
| * 如果vm 初始的为已存在的(即父fragmnet或父Activity)vm,则重写initFirst,避免出现loading显示异常的问题 | |||||
| * @property VB : ViewDataBinding | * @property VB : ViewDataBinding | ||||
| * @property VM: ViewModel | * @property VM: ViewModel | ||||
| */ | */ | ||||
| vm = initViewModel() | vm = initViewModel() | ||||
| vm.pageEvent.observe(this) { startActivity(it) } | vm.pageEvent.observe(this) { startActivity(it) } | ||||
| vm.toastEvent.observe(this){ showToast(it) } | vm.toastEvent.observe(this){ showToast(it) } | ||||
| vm.loadingEvent.observe(this) { showHideLoading(it)} | |||||
| vm.loadingEvent.observe(this) { | |||||
| LogUtil.e("监听到 fragment vm 的 loadingEvent isShow $it") | |||||
| showHideLoading(it) | |||||
| } | |||||
| } | } | ||||
| abstract fun initViewModel(): VM | abstract fun initViewModel(): VM |
| super.onCleared() | super.onCleared() | ||||
| LogUtil.i("${javaClass.name} onCleared() ") | LogUtil.i("${javaClass.name} onCleared() ") | ||||
| } | } | ||||
| var loadingEvent = MutableLiveData<Boolean>() | |||||
| private set | |||||
| var toastEvent = MutableLiveData<ToastEvent>() | |||||
| private set | |||||
| var pageEvent = MutableLiveData<Class<*>>() | |||||
| private set | |||||
| val loadingEvent = MutableLiveData<Boolean>() | |||||
| val toastEvent = MutableLiveData<ToastEvent>() | |||||
| val pageEvent = MutableLiveData<Class<*>>() | |||||
| override fun showHideLoading(isShow: Boolean) { | override fun showHideLoading(isShow: Boolean) { | ||||
| loadingEvent.postValue(isShow) | loadingEvent.postValue(isShow) | ||||
| } | } | ||||
| override fun showToast(msg: ToastEvent) { | override fun showToast(msg: ToastEvent) { | ||||
| toastEvent.postValue(msg) | toastEvent.postValue(msg) | ||||
| } | } | ||||
| override fun startActivity(clazz: Class<*>) { | override fun startActivity(clazz: Class<*>) { | ||||
| pageEvent.postValue(clazz) | pageEvent.postValue(clazz) | ||||
| } | } |
| upstream!!.subscribeOn(Schedulers.from(AppExecutors.diskIO)) | upstream!!.subscribeOn(Schedulers.from(AppExecutors.diskIO)) | ||||
| .observeOn(Schedulers.from(AppExecutors.diskIO)) | .observeOn(Schedulers.from(AppExecutors.diskIO)) | ||||
| } | } | ||||
| } | |||||
| fun <T> diskIo2Main(): ObservableTransformer<T, T> { | |||||
| return ObservableTransformer { upstream -> | |||||
| upstream!!.subscribeOn(Schedulers.from(AppExecutors.diskIO)) | |||||
| .observeOn(Schedulers.from(AppExecutors.mainThread)) | |||||
| } | |||||
| } | } |