| @@ -1,5 +1,8 @@ | |||
| <component name="ProjectCodeStyleConfiguration"> | |||
| <code_scheme name="Project" version="173"> | |||
| <ComposeCustomCodeStyleSettings> | |||
| <option name="USE_CUSTOM_FORMATTING_FOR_MODIFIERS" value="false" /> | |||
| </ComposeCustomCodeStyleSettings> | |||
| <DBN-PSQL> | |||
| <case-options enabled="true"> | |||
| <option name="KEYWORD_CASE" value="lower" /> | |||
| @@ -25,12 +28,9 @@ | |||
| </formatting-settings> | |||
| </DBN-SQL> | |||
| <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" /> | |||
| </JetCodeStyleSettings> | |||
| <DBN-PSQL> | |||
| @@ -172,15 +172,17 @@ | |||
| </codeStyleSettings> | |||
| <codeStyleSettings language="kotlin"> | |||
| <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="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="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> | |||
| </code_scheme> | |||
| </component> | |||
| @@ -25,11 +25,15 @@ | |||
| <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_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/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/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_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_main.xml" value="0.5" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> | |||
| @@ -46,6 +50,7 @@ | |||
| <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_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/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" /> | |||
| @@ -37,6 +37,17 @@ image包: 实现了图片加载的封装 | |||
| 注意include为merge的坑,viewbinding中需动态绑定 https://juejin.cn/post/6844904065655111693 | |||
| 讨论点: | |||
| 状态加载的判断: 加载复习状态页没问题,学前总测试状态页也没问题, | |||
| 但如何判断课程学习完成,目前是课程学习完成后才判断进入课程学后总测试 | |||
| 我的考虑是在进入课程详情界面时,对所有课时的学习进度进行判断,不包含课时的学后测试,当所有课时都学习完了,则进入学后总测试的状态页, | |||
| 而在学习中,同样做引导课时前测试,学习,课时后测试, | |||
| 课时后测试将包含 小游戏练习、再测一次、重新学习、下一步(进入学后总测试)或者下一课时(为当前课时的下一课时), | |||
| 需要考虑的是,下一步或者下一课时的判断为循环判断,如,共10课时,现在点击学习的第5个,如果后面几个课时都学习完后,而1课时没有学习完成,下一课时将直接进入1课时的学习, | |||
| 而下一步是在所有课时都学习完成的情况下,直接进入学后总测试的界面。 | |||
| Kotlin的单例模式: https://developer.aliyun.com/article/642618 | |||
| @@ -50,6 +50,7 @@ android { | |||
| // } | |||
| compileOptions { | |||
| coreLibraryDesugaringEnabled true //为了使用jdk8的脱糖属性 | |||
| sourceCompatibility JavaVersion.VERSION_1_8 | |||
| targetCompatibility JavaVersion.VERSION_1_8 | |||
| } | |||
| @@ -69,15 +70,15 @@ android { | |||
| dependencies { | |||
| 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 '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} | |||
| testImplementation rootProject.ext.dependencies_testImplementation.junit | |||
| @@ -94,4 +95,6 @@ dependencies { | |||
| //Lottie | |||
| implementation customDependencies.Lottie | |||
| coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | |||
| } | |||
| @@ -25,7 +25,7 @@ | |||
| android:name=".module.learn.LearnExamSpellActivity" | |||
| android:exported="true" /> | |||
| <activity | |||
| android:name=".module.learn.LearnExamWordActivity" | |||
| android:name=".module.learn.LearnExamActivity" | |||
| android:exported="true" /> | |||
| <activity | |||
| android:name=".module.learn.LearnSpellActivity" | |||
| @@ -113,6 +113,34 @@ object AppConstants { | |||
| /** 测试通过:>80 < 90 继续加油 <80 悲伤 */ | |||
| const val TEST_SCORE_LEVEL_1 = 80 | |||
| /** 测试通过 >= 太棒了 */ | |||
| /** 测试通过 >= 太棒了 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 //中文 | |||
| } | |||
| @@ -17,15 +17,28 @@ class DataTransferHolder private constructor(){ | |||
| 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") | |||
| 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(){ | |||
| dataMap.clear() | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| 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 //答案是否是音频 | |||
| } | |||
| @@ -1,7 +1,10 @@ | |||
| package com.xkl.cdl.data.manager | |||
| import android.util.Size | |||
| 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.ExamBean | |||
| import java.io.File | |||
| import java.math.BigDecimal | |||
| import java.math.RoundingMode | |||
| @@ -13,25 +16,27 @@ import java.text.DecimalFormat | |||
| * Describe: 课程管理类 | |||
| */ | |||
| object CourseManager { | |||
| /** 项目对应的课程包 */ | |||
| val subjectWithCoursePackMap = hashMapOf<Int, List<CoursePack>>() | |||
| /** | |||
| * 获取对应项目的课程数量 | |||
| * @param subjectId Int 项目 | |||
| * @return Int 课程数量 | |||
| */ | |||
| fun getSubjectForCourseSize(subjectId: Int): Int { | |||
| fun getSubjectForCourseSize(subjectId : Int) : Int { | |||
| return subjectWithCoursePackMap[subjectId]?.let { | |||
| var count = 0 | |||
| it.forEach { coursePack -> | |||
| count += coursePack.childrenCourses?.size ?: 0 | |||
| count += coursePack.childrenCourses?.size | |||
| ?: 0 | |||
| } | |||
| count | |||
| } ?: 0 | |||
| } | |||
| ?: 0 | |||
| } | |||
| /** | |||
| * 搜索项目下的课程包 | |||
| * @param subjectId Int 项目id | |||
| @@ -39,7 +44,7 @@ object CourseManager { | |||
| * @return MutableList<CoursePack>? 结果值 | |||
| */ | |||
| @Synchronized | |||
| fun filterSearchCoursePack(subjectId: Int, searchValue: String?): MutableList<CoursePack>? { | |||
| fun filterSearchCoursePack(subjectId : Int, searchValue : String?) : MutableList<CoursePack>? { | |||
| return if (searchValue.isNullOrEmpty() || searchValue.trim().isEmpty()) { | |||
| subjectWithCoursePackMap[subjectId]?.toMutableList() | |||
| } else { | |||
| @@ -54,7 +59,7 @@ object CourseManager { | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| * 根据subjectId,courseId 获取CoursePack | |||
| * @param subjectId Int 项目Id | |||
| @@ -62,7 +67,7 @@ object CourseManager { | |||
| * @return CoursePack? 课程包 | |||
| */ | |||
| @Synchronized | |||
| fun getCoursePackWithCoursePackId(subjectId: Int, coursePackId: Long): CoursePack? { | |||
| fun getCoursePackWithCoursePackId(subjectId : Int, coursePackId : Long) : CoursePack? { | |||
| return subjectWithCoursePackMap[subjectId]?.let { | |||
| //返回很符合条件的第一个元素 | |||
| it.firstOrNull { | |||
| @@ -70,7 +75,7 @@ object CourseManager { | |||
| } | |||
| } | |||
| } | |||
| /*** | |||
| * 检查课程包下课程的db数据文件是否存在,不存在,则复制过去 | |||
| */ | |||
| @@ -79,9 +84,8 @@ object CourseManager { | |||
| entry.value.forEach { coursePack -> | |||
| coursePack.childrenCourses.forEach { | |||
| 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()) { | |||
| FileUtil.copyAsset(it.dbPathName, file) | |||
| @@ -90,14 +94,14 @@ object CourseManager { | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| * 用于显示的进度 | |||
| * @param progress Double 课程的进度 | |||
| * @return String | |||
| */ | |||
| @JvmStatic | |||
| fun useToShowProgress(progress: Double): String { | |||
| fun useToShowProgress(progress : Double) : String { | |||
| return when { | |||
| progress == 0.0 -> "0" | |||
| progress < 0.1 -> "0.1" | |||
| @@ -108,5 +112,42 @@ object CourseManager { | |||
| 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()) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -2,10 +2,12 @@ package com.xkl.cdl.data.manager.db | |||
| import android.annotation.SuppressLint | |||
| import android.widget.Toast | |||
| import androidx.core.database.getLongOrNull | |||
| import com.suliang.common.util.AppGlobals | |||
| import com.suliang.common.util.thread.AppExecutors | |||
| import com.xkl.cdl.data.AppConstants | |||
| 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.manager.FilePathManager | |||
| import net.sqlcipher.database.SQLiteDatabase | |||
| @@ -21,9 +23,9 @@ object DBCourseManager { | |||
| private const val SPOKEN = "XKL_SPOKEN_COURSE_DATA_KEY" | |||
| private const val COMPOSITION = "XKL_LOCAL_COMPOSITION_DATA_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) { | |||
| if (mDataBase != null && currentBase != base) { | |||
| mDataBase?.close() | |||
| @@ -37,27 +39,27 @@ object DBCourseManager { | |||
| AppConstants.COURSE_TYPE_CHINESE_LITERACY -> LITERACY | |||
| 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) { | |||
| AppExecutors.mainThread.run { | |||
| Toast.makeText(AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG) | |||
| Toast.makeText( | |||
| AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| /**获取该课程的所有课时 | |||
| * @param base DbControlBase | |||
| * @param detail CourseDetail 课程信息 | |||
| * @return List<Lesson> | |||
| */ | |||
| @SuppressLint("Range") | |||
| fun queryAllLesson(base: DbControlBase, detail: CourseDetail): List<Lesson> { | |||
| fun queryAllLesson(base : DbControlBase, detail : CourseDetail) : List<Lesson> { | |||
| val mutableList = mutableListOf<Lesson>() | |||
| open(base) | |||
| //聚合所有课时,先所有chapter_sort和word_sort排序,然后根据leesonId分组并聚合leeson_id到 | |||
| @@ -75,38 +77,40 @@ object DBCourseManager { | |||
| var positionIndex = 0 | |||
| mDataBase?.rawQuery(rawQurySql, null)?.let { | |||
| 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 lessonName = when (base.courseType) { | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("lesson_title")) | |||
| else -> it.getString(it.getColumnIndex("lesson")) | |||
| } | |||
| 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(",") | |||
| }.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_ENGLISH_SPOKEN -> it.getInt(it.getColumnIndex("lesson_type")) | |||
| else -> AppConstants.LESSON_TYPE_WORD | |||
| } | |||
| 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 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 | |||
| this.wordIds = wordIds //内容 | |||
| totalNumber = this.wordIds.size //总数 | |||
| @@ -126,41 +130,149 @@ object DBCourseManager { | |||
| mutableList.last().lastLesson = true | |||
| 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)的随机个数测试数据 */ | |||
| 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>, | |||
| ) { | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| 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,来处理对应的动作 | |||
| } | |||
| @@ -1,10 +1,13 @@ | |||
| package com.xkl.cdl.dialog | |||
| import android.view.View | |||
| import androidx.core.content.ContextCompat | |||
| import androidx.fragment.app.FragmentManager | |||
| import com.suliang.common.base.BaseDialogFragment | |||
| import com.suliang.common.extension.click | |||
| import com.suliang.common.util.LogUtil | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.databinding.DialogLessonLearnBinding | |||
| /** | |||
| @@ -14,7 +17,7 @@ import com.xkl.cdl.databinding.DialogLessonLearnBinding | |||
| */ | |||
| class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
| private lateinit var dialogListener : DialogFragmentListener | |||
| private lateinit var dialogListener: DialogFragmentListener | |||
| override fun initFragment() { | |||
| initClick() | |||
| @@ -22,15 +25,15 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
| initLessonBeforeTestOver() | |||
| } | |||
| fun initClick(){ | |||
| dialogListener = if (parentFragment != null){ | |||
| fun initClick() { | |||
| dialogListener = if (parentFragment != null) { | |||
| parentFragment as DialogFragmentListener | |||
| }else{ | |||
| } else { | |||
| activity as DialogFragmentListener | |||
| } | |||
| } | |||
| fun initLessonBeforeTest(){ | |||
| fun initLessonBeforeTest() { | |||
| binding.tvTitle.text = "课时学前测试" | |||
| binding.tvLessonName.text = "章节课时名称" | |||
| binding.tvCountTime.text = "测试题目时间" | |||
| @@ -40,16 +43,16 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
| binding.tvRight.click { | |||
| LogUtil.e("Dialog -- > show()") | |||
| dialogListener.rightClick() | |||
| } | |||
| binding.tvLeft.click { | |||
| LogUtil.e("Dialog -- > hide()") | |||
| dialogListener.leftClick() | |||
| } | |||
| } | |||
| fun initLessonBeforeTestOver(){ | |||
| fun initLessonBeforeTestOver() { | |||
| binding.tvScore.run { | |||
| visibility = View.VISIBLE | |||
| text = "100分" | |||
| @@ -68,20 +71,53 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
| } | |||
| } | |||
| 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) | |||
| } | |||
| } | |||
| @@ -0,0 +1,61 @@ | |||
| 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) | |||
| } | |||
| } | |||
| } | |||
| @@ -1,12 +0,0 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @@ -8,6 +8,7 @@ import com.suliang.common.base.fragment.BaseFragmentVM | |||
| import com.suliang.common.extension.replaceFragment | |||
| import com.suliang.common.util.LogUtil | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.manager.db.DbControlBase | |||
| import com.xkl.cdl.databinding.FragmentCourseMainBinding | |||
| import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | |||
| @@ -64,7 +65,27 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
| //学后总测 | |||
| // 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)) | |||
| } | |||
| } | |||
| @@ -1,51 +1,52 @@ | |||
| package com.xkl.cdl.module.m_center_learn.coursechildren | |||
| import android.provider.ContactsContract | |||
| import androidx.lifecycle.MutableLiveData | |||
| import com.suliang.common.base.activity.ToastEvent | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| 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.CourseDetail | |||
| import com.xkl.cdl.data.bean.course.ExamBean | |||
| 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.repository.DataRepository | |||
| 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.Observer | |||
| import io.reactivex.rxjava3.disposables.Disposable | |||
| import java.util.* | |||
| import kotlin.collections.ArrayList | |||
| import kotlin.collections.HashMap | |||
| /** | |||
| * author suliang | |||
| * create 2022/3/28 15:26 | |||
| * Describe: | |||
| * @property courseIndex 该课程在课程包的课程集合中的位置 | |||
| * | |||
| */ | |||
| class CourseMainFragmentViewModel(val courseIndex: Int) : BaseViewModel() { | |||
| class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| //课程包主页的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 | |||
| //获取课程的统计信息 | |||
| //课程收藏本: 主要用于口语对话学习的数据 | |||
| //获取课程的复习数据 | |||
| @@ -53,38 +54,59 @@ class CourseMainFragmentViewModel(val courseIndex: Int) : BaseViewModel() { | |||
| fun loadMain() : MutableLiveData<Boolean> { | |||
| showHideLoading(true) | |||
| val mutableLiveData = MutableLiveData<Boolean>() | |||
| Observable.concat( | |||
| DataRepository.getCourseStatistics().flatMap { | |||
| courseDetail = it | |||
| Observable.concat(DataRepository.getCourseStatistics().flatMap { | |||
| courseDetail = 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 -> | |||
| if (it is List<*>){ | |||
| if (it is List<*>) { | |||
| allLesson = it as List<Lesson> | |||
| } | |||
| } | |||
| } | |||
| override fun onError(e: Throwable?) { | |||
| override fun onError(e : Throwable?) { | |||
| e?.printStackTrace() | |||
| showHideLoading(false) | |||
| showToast(ToastEvent(content = "数据加载异常")) | |||
| } | |||
| override fun onComplete() { | |||
| showHideLoading(false) | |||
| mutableLiveData.postValue(true) | |||
| } | |||
| }) | |||
| 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 | |||
| } | |||
| } | |||
| @@ -9,12 +9,14 @@ import com.suliang.common.extension.click | |||
| import com.suliang.common.extension.setHtml | |||
| import com.xkl.cdl.R | |||
| 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.module.learn.LearnExamActivity | |||
| /** | |||
| * 课程总测试: 学前总,学后总 | |||
| * @property param1 String? | |||
| * @property param2 String? | |||
| * @property totalTestType 测试类型 | |||
| */ | |||
| class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, CourseMainFragmentViewModel>() { | |||
| @@ -30,18 +32,42 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
| override fun initViewModel(): CourseMainFragmentViewModel { | |||
| return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java] | |||
| } | |||
| override fun initFirst() { | |||
| super.initFirst() | |||
| //由于使用的是与父类相同的loadingEvent,所以将此evnet移除 | |||
| vm.loadingEvent.removeObservers(this) | |||
| } | |||
| //传递过来的总测类型: 学前,学后 | |||
| private var totalTestType = 0 | |||
| //测试的数据 | |||
| private var testData : List<ExamBean> = emptyList() | |||
| override fun initFragment() { | |||
| 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) { | |||
| AppConstants.TEST_TYPE_BEFORE_TOTAL -> { | |||
| //学前总测 | |||
| binding.run { | |||
| tvTitle.setText(R.string.test_total_before_title) | |||
| tvCountTip.text = "共25题,预计3分钟" | |||
| button1.run { | |||
| setText(R.string.start_learn) | |||
| click { | |||
| continueLearn(it) | |||
| } | |||
| } | |||
| //按钮初始 | |||
| button2.run { | |||
| setText(R.string.start_test) | |||
| @@ -51,7 +77,6 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
| } | |||
| } | |||
| } | |||
| AppConstants.TEST_TYPE_AFTER_TOTAL -> { | |||
| //学后总测 | |||
| binding.run { | |||
| @@ -64,7 +89,6 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
| when { | |||
| it == -1.0 -> { //未测 | |||
| tvMainTip.setText(R.string.test_total_after_tip_1) | |||
| tvCountTip.text = "共25题,预计3分钟" | |||
| button2.run { | |||
| setText(R.string.test_type_after_total) | |||
| click { view -> | |||
| @@ -115,19 +139,17 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
| } | |||
| } | |||
| } | |||
| override fun loadData() { | |||
| } | |||
| /**继续学习*/ | |||
| private fun continueLearn(view: View) { | |||
| showToast("继续学习") | |||
| (requireParentFragment() as CourseMainFragment).changeFragment(1) | |||
| } | |||
| /** 开始测试 */ | |||
| private fun startTest(view: View) { | |||
| showToast("开始测试") | |||
| LearnExamActivity.newInstance(requireContext()) | |||
| } | |||
| } | |||
| @@ -19,7 +19,7 @@ import java.io.File | |||
| import java.util.concurrent.TimeUnit | |||
| @SuppressLint("CustomSplashScreen") | |||
| class SplashActivity : BaseActivity<ActivitySplashBinding>(), LearnDialog.DialogFragmentListener { | |||
| class SplashActivity : BaseActivity<ActivitySplashBinding>(){ | |||
| override fun onCreate(savedInstanceState: Bundle?) { | |||
| if (!isTaskRoot) { | |||
| @@ -45,11 +45,11 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(), LearnDialog.Dialog | |||
| // 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) | |||
| @@ -72,20 +72,20 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(), LearnDialog.Dialog | |||
| } | |||
| 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() | |||
| // } | |||
| } | |||
| @@ -0,0 +1,100 @@ | |||
| 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() | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?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> | |||
| @@ -0,0 +1,8 @@ | |||
| <?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> | |||
| @@ -4,6 +4,12 @@ | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:layout_width="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> | |||
| @@ -100,8 +100,7 @@ | |||
| app:layout_constraintTop_toTopOf="@+id/button_2" | |||
| app:strokeColor="@color/theme_color" | |||
| app:strokeWidth="@dimen/line_height" | |||
| android:visibility="gone" | |||
| tools:visibility="visible"/> | |||
| /> | |||
| <Button | |||
| android:id="@+id/button_2" | |||
| @@ -0,0 +1,112 @@ | |||
| <?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> | |||
| @@ -0,0 +1,70 @@ | |||
| <?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> | |||
| @@ -8,5 +8,4 @@ | |||
| <attr name="progressNumber" format="integer"/> <!--弧形进度--> | |||
| <attr name="backgroundColor" format="color"/> <!--背景颜色--> | |||
| </declare-styleable> | |||
| </resources> | |||
| @@ -26,6 +26,7 @@ | |||
| <color name="gray_3">#FAFAFA</color> | |||
| <color name="green_1">#40A540</color> | |||
| <color name="red_1">#FFF26255</color> | |||
| <color name="red_2">#F7874F</color> | |||
| <color name="num0">#E50213</color> | |||
| @@ -12,7 +12,7 @@ android { | |||
| targetSdk rootProject.ext.android.target_sdk_version | |||
| versionCode 1 | |||
| versionName "1.0" | |||
| // multiDexEnabled true | |||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
| consumerProguardFiles "consumer-rules.pro" | |||
| } | |||
| @@ -30,6 +30,7 @@ android { | |||
| } | |||
| } | |||
| compileOptions { | |||
| coreLibraryDesugaringEnabled true | |||
| sourceCompatibility JavaVersion.VERSION_1_8 | |||
| targetCompatibility JavaVersion.VERSION_1_8 | |||
| } | |||
| @@ -44,7 +45,7 @@ android { | |||
| dependencies { | |||
| // 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} | |||
| testImplementation rootProject.ext.dependencies_testImplementation.junit | |||
| rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v} | |||
| @@ -63,7 +64,7 @@ dependencies { | |||
| //RxJava RxAndroid | |||
| api customDependencies.RxJava | |||
| api customDependencies.RxAndroid | |||
| coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | |||
| @@ -1,5 +1,6 @@ | |||
| package com.suliang.common.base | |||
| import android.R.attr | |||
| import android.graphics.Color | |||
| import android.os.Bundle | |||
| import android.view.LayoutInflater | |||
| @@ -11,8 +12,11 @@ import com.suliang.common.extension.initBinding | |||
| import android.graphics.drawable.ColorDrawable | |||
| 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 | |||
| /** | |||
| @@ -49,12 +53,30 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment() { | |||
| dialog!!.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) | |||
| } | |||
| } | |||
| abstract fun initFragment() | |||
| /** 是否布局背景透明 */ | |||
| open fun isTransparent():Boolean { | |||
| 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() | |||
| } | |||
| } | |||
| } | |||
| @@ -13,9 +13,11 @@ class LoadingDialog : BaseDialogFragment<LoadingFragmentBinding>() { | |||
| companion object{ | |||
| @JvmStatic | |||
| fun newInstance(msg : String): LoadingDialog { | |||
| fun newInstance(msg : String?): LoadingDialog { | |||
| return LoadingDialog().apply { | |||
| arguments?.putString(AppConfig.INTENT_1,msg) | |||
| if (!msg.isNullOrEmpty()) { | |||
| arguments?.putString(AppConfig.INTENT_1, msg) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -10,7 +10,7 @@ import com.suliang.common.base.activity.ToastEvent | |||
| interface ViewBehavior { | |||
| /** 是否显示视图 */ | |||
| fun showHideLoading(isShow: Boolean) | |||
| /** 显示toast */ | |||
| fun showToast(msg: ToastEvent) | |||
| @@ -1,199 +1,26 @@ | |||
| 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 com.suliang.common.base.LoadingDialog | |||
| 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 | |||
| */ | |||
| 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!! | |||
| // 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) | |||
| setContentView(binding.root) | |||
| initStatusBar() | |||
| setLayoutAfter() | |||
| } | |||
| /** | |||
| * 状态栏初始化方法 | |||
| */ | |||
| open fun initStatusBar() { | |||
| statusBarOnly { | |||
| fitWindow = true | |||
| color = Color.WHITE | |||
| light = true | |||
| } | |||
| } | |||
| override fun onDestroy() { | |||
| super.onDestroy() | |||
| ActivityStackManager.removeActivity(this) | |||
| _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) | |||
| // } | |||
| // } | |||
| } | |||
| @@ -2,6 +2,7 @@ package com.suliang.common.base.activity | |||
| import androidx.viewbinding.ViewBinding | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| import com.suliang.common.util.LogUtil | |||
| /** | |||
| * Activity DataBinding 与 ViewModel基类,封装DataBinding和ViewModel | |||
| @@ -26,7 +27,9 @@ abstract class BaseActivityVM<VB : ViewBinding, VM : BaseViewModel> : BaseActivi | |||
| vm.pageEvent.observe(this) { startActivity(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)} | |||
| } | |||
| @@ -0,0 +1,196 @@ | |||
| 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) | |||
| // } | |||
| // } | |||
| @@ -12,8 +12,10 @@ import androidx.fragment.app.Fragment | |||
| import androidx.viewbinding.ViewBinding | |||
| import com.suliang.common.base.LoadingDialog | |||
| import com.suliang.common.base.ViewBehavior | |||
| import com.suliang.common.base.activity.BaseActivity | |||
| import com.suliang.common.base.activity.ToastEvent | |||
| import com.suliang.common.extension.initBinding | |||
| import com.suliang.common.util.LogUtil | |||
| /** | |||
| * Fragment ViewBinding基类,封装ViewDataBinding 自带懒加载 | |||
| @@ -82,12 +84,7 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment(),ViewBehavior { | |||
| * 界面显示出来后再加载数据 | |||
| */ | |||
| abstract fun loadData() | |||
| private val loadingDialog by lazy { | |||
| LoadingDialog() | |||
| } | |||
| /** toast提示 */ | |||
| override fun showToast(event: ToastEvent) { | |||
| @@ -107,11 +104,11 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment(),ViewBehavior { | |||
| showToast(ToastEvent(contentResId = resId, showLong = showLong)) | |||
| } | |||
| /** 调用父类的 加载框 */ | |||
| 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) | |||
| } | |||
| } | |||
| @@ -4,9 +4,11 @@ import androidx.databinding.ViewDataBinding | |||
| import androidx.lifecycle.ViewModel | |||
| import androidx.viewbinding.ViewBinding | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| import com.suliang.common.util.LogUtil | |||
| /** | |||
| * Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel 自带懒加载 | |||
| * 如果vm 初始的为已存在的(即父fragmnet或父Activity)vm,则重写initFirst,避免出现loading显示异常的问题 | |||
| * @property VB : ViewDataBinding | |||
| * @property VM: ViewModel | |||
| */ | |||
| @@ -17,7 +19,10 @@ abstract class BaseFragmentVM<VB : ViewBinding, VM : BaseViewModel> : BaseFragme | |||
| vm = initViewModel() | |||
| vm.pageEvent.observe(this) { startActivity(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 | |||
| @@ -48,24 +48,18 @@ open class BaseViewModel : ViewModel(), ViewModelLifecycle, ViewBehavior { | |||
| super.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) { | |||
| loadingEvent.postValue(isShow) | |||
| } | |||
| override fun showToast(msg: ToastEvent) { | |||
| toastEvent.postValue(msg) | |||
| } | |||
| override fun startActivity(clazz: Class<*>) { | |||
| pageEvent.postValue(clazz) | |||
| } | |||
| @@ -15,4 +15,11 @@ fun <T> diskIo2DiskIo(): ObservableTransformer<T, T> { | |||
| upstream!!.subscribeOn(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)) | |||
| } | |||
| } | |||