| @@ -46,7 +46,7 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_exam.xml" value="0.25" /> | |||
| <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_spell.xml" value="0.47690217391304346" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_word.xml" value="0.5" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_word.xml" value="0.6435024322446143" /> | |||
| <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" /> | |||
| @@ -102,6 +102,7 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_statistics.xml" value="0.44166666666666665" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play.xml" value="0.503125" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_pause.xml" value="0.503125" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_stop.xml" value="0.4973958333333333" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_right.xml" value="0.4425925925925926" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_spell.xml" value="0.5061538461538462" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_spoken.xml" value="0.5061538461538462" /> | |||
| @@ -121,7 +121,7 @@ class AdapterHistoricalRoute(val courseType : Int) : BaseRVAdapter<LearnWord>() | |||
| if (currentSelectPosition == position) return@click | |||
| //点击改变选中值与显示 | |||
| previousSelectPosition = currentSelectPosition | |||
| previousIsShowLine = !(previousSelectPosition == itemCount-1 && lastIsLearnOver) | |||
| previousIsShowLine = previousSelectPosition == itemCount-1 && !lastIsLearnOver | |||
| currentSelectPosition = position | |||
| currentIsShowLine = true | |||
| notifyItemChanged(previousSelectPosition) | |||
| @@ -42,29 +42,6 @@ class AdapterLesson(vm: CourseMainFragmentViewModel) : BaseRVAdapterVM<Lesson, C | |||
| executePendingBindings() | |||
| initColor(position, lesson) | |||
| when(lesson.lessonType){ | |||
| AppConstants.LESSON_TYPE_COMPOSITION_VIDEO -> { // 作文视频 | |||
| } | |||
| AppConstants.LESSON_TYPE_COMPOSITION_EXAM -> { //作文章节测试 | |||
| } | |||
| AppConstants.LESSON_TYPE_COMPOSITION_READING -> { //作文课堂练习 | |||
| } | |||
| AppConstants.LESSON_TYPE_COMPOSITION_TASK -> { //作文课外练习 | |||
| } | |||
| AppConstants.LESSON_TYPE_DIALOGUE -> { //口语对话 | |||
| } | |||
| else -> { } // 其他课时类型为布局的正常显示 | |||
| } | |||
| //事件 | |||
| layoutContent.click { | |||
| //选中项非当前项,则需要改变选中颜色,直接通知更新,调用对应位置的notfy | |||
| @@ -111,7 +88,18 @@ class AdapterLesson(vm: CourseMainFragmentViewModel) : BaseRVAdapterVM<Lesson, C | |||
| } | |||
| } else { //未选中:根据学习情况判断 | |||
| layoutContent.setBackgroundColor(translationColor) //未选中背景透明 | |||
| if (lesson.learnIsOver) { //学习完成 | |||
| //根据课时类型判断当前课时是否完全完成 | |||
| val lessonLearnOver = when(lesson.lessonType){ | |||
| AppConstants.LESSON_TYPE_WORD -> lesson.learnIsOver && lesson.afterTestScore != AppConstants.NOT_DOING | |||
| AppConstants.LESSON_TYPE_COMPOSITION_VIDEO -> lesson.learnIsOver // 作文视频 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_EXAM -> lesson.learnIsOver //作文章节测试 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_READING -> lesson.learnIsOver //作文课堂练习 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_TASK -> lesson.learnIsOver //作文课外练习 | |||
| AppConstants.LESSON_TYPE_DIALOGUE -> lesson.learnIsOver //口语对话 | |||
| else -> false // 其他课时类型为布局的正常显示 | |||
| } | |||
| if (lessonLearnOver) { //学习完成 且学后测试完成 | |||
| tvLessonName.run { | |||
| setTextColor(normalColorLearnOver) //课时名称颜色 | |||
| paint.isFakeBoldText = false //加粗 | |||
| @@ -294,8 +294,8 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
| private fun playItem(letter : Char) { | |||
| if (letter.isLetter()) { | |||
| when (defaultSoundWay) { | |||
| AppConstants.SOUND_TYPE_UK -> MPManager.playAsset("common_voice_uk/${letter}_uk.mp3") | |||
| AppConstants.SOUND_TYPE_US -> MPManager.playAsset("common_voice_us/${letter}_us.mp3") | |||
| AppConstants.SOUND_TYPE_UK -> MPManager.playAsset("common_voice_uk/${letter.lowercase()}_uk.mp3") | |||
| AppConstants.SOUND_TYPE_US -> MPManager.playAsset("common_voice_us/${letter.lowercase()}_us.mp3") | |||
| } | |||
| } else { | |||
| MPManager.playAsset("common_voice/konge.mp3") | |||
| @@ -153,9 +153,11 @@ object AppConstants { | |||
| const val DIALOG_TYPE_EXAM_START = 1 | |||
| const val DIALOG_TYPE_EXAM_OVER = 2 | |||
| /** 对话框类型: 学习结束弹窗类型 */ | |||
| const val DIALOG_TYPE_LEARN_OVER = 3 | |||
| const val DIALOG_TYPE_LEARNING_OVER = 3 | |||
| /**对话框类型,item学习完成*/ | |||
| const val DIALOG_TYPE_LESSON_ITEM_OVER = 4 | |||
| const val DIALOG_TYPE_LESSON_ITEM_CLICK_ALL_OVER = 4 | |||
| /**课时列表item点击: 课时未做学后测试*/ | |||
| const val DIALOG_TYPE_LESSON_ITEM_CLICK_NOT_DOING_AFTER_TEST = 5 | |||
| /**--- 总线动作 --------------------------------- */ | |||
| /**action key 改变界面 到目录页 */ | |||
| @@ -7,6 +7,7 @@ import com.xkl.cdl.data.bean.course.Lesson | |||
| * author suliang | |||
| * create 2022/4/2 14:55 | |||
| * Describe: 课时学习时的传参 | |||
| * @param lesson 课时学习是为课时数据,其他特殊时候,如自动播放,只是用以包装课程的基本信息如果项目id、课程包类型、课程类型等 | |||
| */ | |||
| class LearnData(val lesson: Lesson) { | |||
| @@ -15,4 +16,9 @@ class LearnData(val lesson: Lesson) { | |||
| /**学习数据*/ | |||
| var learnWordList : List<LearnWord> = mutableListOf() | |||
| /** 是否自动播放 ,如果是自动播放,则lesson为手动虚拟创建的 */ | |||
| var isAutoPlay = false | |||
| //自动播放次数 | |||
| var autoPlayTime = 0 | |||
| } | |||
| @@ -107,7 +107,7 @@ object DBCourseManager { | |||
| val learnIndex = wordIds.indexOf(detail.lesson_learn_point.getOrElse(key, { -1 })) //学习位置,当前位置为已学 | |||
| // 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 | |||
| // 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 todo 分课程类型设置进度点 | |||
| val learnIsOver = wordIds.size - 1 == learnIndex | |||
| val lesson = Lesson(base.subjectId, | |||
| @@ -132,6 +132,15 @@ object DBCourseManager { | |||
| } | |||
| mutableList.add(lesson) | |||
| positionIndex += 1 | |||
| // TODO: 2022/5/9 对课时数量进行限制,课时太多,开发人员不好进行测试 | |||
| val needBreak = when(base.coursePackType){ | |||
| AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION -> positionIndex == 11 //保留十个课时 | |||
| AppConstants.COURSEPACK_TYPE_ENGLISH_SPOKEN -> positionIndex == 7 //保留六个课时 | |||
| else -> positionIndex == 3 //保留三个课时 | |||
| } | |||
| if (needBreak){ | |||
| break | |||
| } | |||
| } | |||
| it.close() | |||
| } | |||
| @@ -169,10 +178,10 @@ object DBCourseManager { | |||
| 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} " | |||
| " WHERE chapter_id = ${lesson!!.chapterId} AND lesson_id = ${lesson.lessonId} " | |||
| } | |||
| else -> " ORDER by random() LIMIT $count" | |||
| } | |||
| else -> "" | |||
| } + " ORDER by random() LIMIT $count" | |||
| } | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { //口语 | |||
| when (testType) { | |||
| @@ -509,5 +518,68 @@ object DBCourseManager { | |||
| return result | |||
| } | |||
| /** | |||
| * 根据wordId查询指定课程指定章节课时的指定单词数据,按照wordId顺序进行排序 | |||
| * @param dbcb DbControlBase | |||
| * @param chapterId 章节id | |||
| * @param lessonId 课时id | |||
| * @param wordIds String 字符串样式的单词id 1,2,3,4,5 注意不要在最后添加逗号 | |||
| * @param isNeedOriginSort 是否保持wordIds排序,默认false,通过word_sort排序 true:即wordIds不为word——sort的排序,需要保持顺序 | |||
| * @return List<LearnWord> | |||
| */ | |||
| @SuppressLint("Range") | |||
| fun queryLearnWord(dbcb : DbControlBase, chapterId:Long, lessonId:Long, wordIds:String, isNeedOriginSort:Boolean = false):List<LearnWord>{ | |||
| val result = mutableListOf<LearnWord>() | |||
| open(dbcb) | |||
| val sql = when{ | |||
| !isNeedOriginSort -> "SELECT * FROM chapter WHERE chapter_id = $chapterId and lesson_id = $lessonId AND word_id in ($wordIds) ORDER by word_sort ASC" | |||
| else -> { | |||
| val sortSqlBuilder = StringBuilder() //排序value | |||
| wordIds.split(",").forEachIndexed { index, value -> | |||
| sortSqlBuilder.append(" when word_id = $value then $index ") | |||
| } | |||
| "SELECT * FROM chapter WHERE chapter_id = $chapterId and lesson_id = $lessonId AND word_id in ($wordIds) ORDER by CASE $sortSqlBuilder END" | |||
| } | |||
| } | |||
| mDataBase?.rawQuery(sql, null)?.run { | |||
| while (moveToNext()) { | |||
| //学习单词实体 | |||
| result.add(LearnWord(dbcb.subjectId, | |||
| dbcb.coursePackId, | |||
| dbcb.courseId, | |||
| dbcb.coursePackType, | |||
| dbcb.courseType, | |||
| chapterId, | |||
| lessonId, | |||
| getLong(getColumnIndex("word_id")), | |||
| true, | |||
| AppConstants.LESSON_TYPE_WORD).apply { | |||
| word = getString(getColumnIndex("word")) | |||
| basic_explanation = getString(getColumnIndex("basic_explaination")) | |||
| extend_explanation = getString(getColumnIndex("all_explaination")) | |||
| phrase = getString(getColumnIndex("phrase")) | |||
| example = getString(getColumnIndex("example")) | |||
| reference = getString(getColumnIndex("reference")) | |||
| when (dbcb.courseType) { | |||
| AppConstants.COURSE_TYPE_CHINESE_LITERACY -> { | |||
| literacyIspolyphone = getInt(getColumnIndex("polyphone")) > 0 | |||
| phonetic_cn = getString(getColumnIndex("phonetic")) | |||
| } | |||
| AppConstants.COURSE_TYPE_CHINESE_PINYIN -> { | |||
| phonetic_cn = getString(getColumnIndex("phonetic")) | |||
| } | |||
| else -> { | |||
| phonetic_uk = getString(getColumnIndex("phonetic_uk")) | |||
| phonetic_us = getString(getColumnIndex("phonetic_us")) | |||
| } | |||
| } | |||
| }) | |||
| } | |||
| close() | |||
| } | |||
| return result | |||
| } | |||
| } | |||
| @@ -3,6 +3,7 @@ package com.xkl.cdl.data.repository | |||
| import android.util.LruCache | |||
| import androidx.lifecycle.MutableLiveData | |||
| import com.suliang.common.extension.diskIo2Main | |||
| import com.suliang.common.util.thread.AppExecutors | |||
| import com.xkl.cdl.data.manager.db.DBCourseManager | |||
| import com.xkl.cdl.data.manager.db.DbControlBase | |||
| import io.reactivex.rxjava3.core.Observable | |||
| @@ -39,18 +40,15 @@ object PhotoCache { | |||
| fun get(dbControlBase : DbControlBase, wordId : Long) { | |||
| //先查对应的文件路径 : 项目id_课程包id_课程id_单词id_发音方式 | |||
| val defaultKey = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}" | |||
| Observable.fromCallable { | |||
| AppExecutors.diskIO.run { | |||
| //不为空,直接取值 | |||
| if (lruCache.get(defaultKey) == null) { | |||
| val value = DBCourseManager.queryPhoto(dbControlBase, wordId) | |||
| lruCache.put(defaultKey, value) //保存路径 | |||
| if (::photoLiveData.isInitialized){ | |||
| photoLiveData.postValue(lruCache.get(defaultKey)?:let { | |||
| DBCourseManager.queryPhoto(dbControlBase, wordId)?.apply { | |||
| lruCache.put(defaultKey, this) //保存路径 | |||
| } | |||
| }) | |||
| } | |||
| return@fromCallable lruCache.get(defaultKey) | |||
| }.compose(diskIo2Main()).subscribe { | |||
| if (this::photoLiveData.isInitialized) | |||
| photoLiveData.value = it | |||
| } | |||
| } | |||
| } | |||
| @@ -69,7 +69,7 @@ class CommonDialog private constructor() : BaseDialogFragment<DialogCommonBindin | |||
| } | |||
| }?:let { | |||
| binding.tvLeft.visibility = View.GONE | |||
| binding.vSpace.visibility = View.GONE | |||
| binding.vSplit.visibility = View.GONE | |||
| } | |||
| rightColor?.let { | |||
| binding.tvRight.setTextColor(ContextCompat.getColor(requireContext(),it)) | |||
| @@ -67,9 +67,11 @@ class LearnDialog private constructor() : BaseDialogFragment<DialogLessonLearnBi | |||
| AppConstants.TEST_TYPE_AFTER_TOTAL -> initCourseAfterTestOver() | |||
| } | |||
| //学习结束弹窗 | |||
| AppConstants.DIALOG_TYPE_LEARN_OVER -> initLessonLearningOver() | |||
| AppConstants.DIALOG_TYPE_LEARNING_OVER -> initLessonLearningOver(false) | |||
| /**课时列表点击课时完全完成*/ | |||
| AppConstants.DIALOG_TYPE_LESSON_ITEM_OVER -> initLessonItemClickLessonOver() | |||
| AppConstants.DIALOG_TYPE_LESSON_ITEM_CLICK_ALL_OVER -> initLessonItemClickLessonOver() | |||
| /** 课时列表item点击 */ | |||
| AppConstants.DIALOG_TYPE_LESSON_ITEM_CLICK_NOT_DOING_AFTER_TEST -> initLessonLearningOver(true) | |||
| } | |||
| } | |||
| @@ -181,8 +183,9 @@ class LearnDialog private constructor() : BaseDialogFragment<DialogLessonLearnBi | |||
| /** | |||
| * 课时学习中的学习完成 | |||
| * @param isShowCloseImg 是否需要显示关闭按钮 | |||
| */ | |||
| private fun initLessonLearningOver(){ | |||
| private fun initLessonLearningOver(isShowCloseImg:Boolean){ | |||
| initNumber() | |||
| binding.run { | |||
| @@ -200,6 +203,10 @@ class LearnDialog private constructor() : BaseDialogFragment<DialogLessonLearnBi | |||
| } | |||
| binding.tvLeft.click { onDialogListener(AppConstants.DIALOG_LESSON_RELEARN, this) } | |||
| binding.tvRight.click { onDialogListener(AppConstants.DIALOG_START_TEST, this) } | |||
| if (isShowCloseImg){ | |||
| binding.ivClose.visibility = View.VISIBLE | |||
| binding.ivClose.click { dismissAllowingStateLoss() } | |||
| } | |||
| } | |||
| @@ -3,7 +3,14 @@ package com.xkl.cdl.module | |||
| import android.app.Application | |||
| import com.suliang.common.util.LogUtil | |||
| import com.tencent.mmkv.MMKV | |||
| import io.reactivex.rxjava3.exceptions.UndeliverableException | |||
| import io.reactivex.rxjava3.functions.Consumer | |||
| import io.reactivex.rxjava3.plugins.RxJavaPlugins | |||
| import net.sqlcipher.database.SQLiteDatabase | |||
| import java.io.IOException | |||
| import java.lang.IllegalArgumentException | |||
| import java.lang.IllegalStateException | |||
| import java.lang.NullPointerException | |||
| import java.util.* | |||
| /** | |||
| @@ -18,11 +25,38 @@ class XKLApplication : Application() { | |||
| SQLiteDatabase.loadLibs(this) | |||
| // ImageLoader.mStrategy = GlideLoaderStrategy() | |||
| LogUtil.e(UUID.randomUUID().toString().replace("-","")) | |||
| //初始MMKV存储 | |||
| val rootDir = MMKV.initialize(this) | |||
| LogUtil.e(rootDir) | |||
| setRxJavaErrorHandler() | |||
| } | |||
| /*** | |||
| * 避免 调用多次onError。正常来说第一次onError会走正常的Observer处理,其他会走ErrorHandler。通过此方法捕捉多次的error | |||
| */ | |||
| private fun setRxJavaErrorHandler(){ | |||
| RxJavaPlugins.setErrorHandler(Consumer { e -> | |||
| e.printStackTrace() | |||
| LogUtil.e( "RxJavaErrorHandler --> \n $e") | |||
| if (e is UndeliverableException) { | |||
| return@Consumer | |||
| } else if (e is IOException) { | |||
| // fine, irrelevant network problem or API that throws on cancellation | |||
| return@Consumer | |||
| } else if (e is InterruptedException) { | |||
| // fine, some blocking code was interrupted by a dispose call | |||
| return@Consumer | |||
| } else if (e is NullPointerException || e is IllegalArgumentException) { | |||
| // that's likely a bug in the application | |||
| Thread.currentThread().uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), e) | |||
| return@Consumer | |||
| } else if (e is IllegalStateException) { | |||
| // that's a bug in RxJava or in a custom operator | |||
| Thread.currentThread().uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), e) | |||
| return@Consumer | |||
| } | |||
| LogUtil.e( "RxJavaErrorHandler --> unknown exception = \n $e") | |||
| }) | |||
| } | |||
| } | |||
| @@ -6,6 +6,7 @@ import androidx.lifecycle.LifecycleOwner | |||
| import androidx.lifecycle.MutableLiveData | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| import com.suliang.common.util.LogUtil | |||
| import com.xkl.cdl.data.bean.intentdata.LearnData | |||
| import java.util.* | |||
| import kotlin.concurrent.timerTask | |||
| @@ -37,7 +38,7 @@ abstract class LearnBaseViewModel : BaseViewModel() { | |||
| val validTime : MutableLiveData<Long> = MutableLiveData(0) | |||
| /**记录当前倒计时已运行时间 默认18秒 */ | |||
| private var currentValidSurplusTime = validMaxTime | |||
| var currentValidSurplusTime = validMaxTime | |||
| /** 开始计时 */ | |||
| @@ -45,19 +46,24 @@ abstract class LearnBaseViewModel : BaseViewModel() { | |||
| timeTask = timerTask { | |||
| totalUseTime.postValue(totalUseTime.value?.plus(200)) | |||
| //如果需要记录有效时间 | |||
| if (isRunValidTime) { | |||
| validTime.postValue(validTime.value!!.plus(200)) | |||
| currentValidSurplusTime -= 200 | |||
| isRunValidTime = currentValidSurplusTime > 0 | |||
| } | |||
| initRunValidTime() | |||
| } | |||
| totalTimer.schedule(timeTask, 200, 200) | |||
| LogUtil.e("开始总计时") | |||
| } | |||
| /** 有效时间记录规则 */ | |||
| open fun initRunValidTime(){ | |||
| if (isRunValidTime) { | |||
| validTime.postValue(validTime.value!!.plus(200)) | |||
| currentValidSurplusTime -= 200 | |||
| isRunValidTime = currentValidSurplusTime > 0 | |||
| } | |||
| } | |||
| /** 停止计时 */ | |||
| fun stopTotalCountTing() { | |||
| LogUtil.e("停止总计时") | |||
| // LogUtil.e("停止总计时") | |||
| timeTask?.cancel() | |||
| timeTask = null | |||
| } | |||
| @@ -207,14 +207,14 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
| /** 停止获取下一题 */ | |||
| fun pauseToNext() { | |||
| LogUtil.e("停止获取下一题") | |||
| // LogUtil.e("停止获取下一题") | |||
| isErrorPauseToNext = true | |||
| mHandler.removeCallbacks(toNextRunable) | |||
| } | |||
| /** 继续获取下一题 */ | |||
| fun continueToNext() { | |||
| LogUtil.e("获取下一题") | |||
| // LogUtil.e("获取下一题") | |||
| isErrorPauseToNext = false | |||
| isGetNextIng = true | |||
| mHandler.post(toNextRunable) | |||
| @@ -264,13 +264,13 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
| currentMaxTime = if (intentData.courseType == AppConstants.COURSE_TYPE_ENGLISH_VOICE) 0 else Math.max( | |||
| AppConstants.TEST_MIN_TIME, | |||
| (nextExam.word.length + nextExam.correct.length) * 200) | |||
| LogUtil.e("------currentMaxTime = $currentMaxTime") | |||
| // LogUtil.e("------currentMaxTime = $currentMaxTime") | |||
| currentSuplusTime.value = currentMaxTime | |||
| } | |||
| } | |||
| currentExamBean.value = nextExam | |||
| //开始倒计时 | |||
| LogUtil.e("开始单题倒计时") | |||
| // LogUtil.e("开始单题倒计时") | |||
| mHandler.postDelayed(currentCountingTimeRunnable, currentCountingIntervalTime) | |||
| } | |||
| } | |||
| @@ -340,7 +340,7 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
| fun chooseResult(position : Int) { | |||
| //移除计时 | |||
| mHandler.removeCallbacks(currentCountingTimeRunnable) | |||
| LogUtil.e("结果移除单题倒计时") | |||
| // LogUtil.e("结果移除单题倒计时") | |||
| //创建单题的测试记录 | |||
| currentExamRecord = ExamRecord.newBuilder().apply { | |||
| questionId = currentExamBean.value!!.word_id | |||
| @@ -395,7 +395,7 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
| fun spellOver(selectedValue : String, errorSize : Int) { | |||
| //移除计时 | |||
| mHandler.removeCallbacks(currentCountingTimeRunnable) | |||
| LogUtil.e("结果移除单题倒计时") | |||
| // LogUtil.e("结果移除单题倒计时") | |||
| //创建测试记录 | |||
| currentExamRecord = ExamRecord.newBuilder().apply { | |||
| questionId = currentExamBean.value!!.word_id | |||
| @@ -1,6 +1,7 @@ | |||
| package com.xkl.cdl.module.learn | |||
| import android.annotation.SuppressLint | |||
| import android.graphics.Color | |||
| import android.os.Bundle | |||
| import android.text.SpannableStringBuilder | |||
| import android.view.MotionEvent | |||
| @@ -17,6 +18,7 @@ import com.jeremyliao.liveeventbus.LiveEventBus | |||
| import com.suliang.common.base.activity.BaseActivityVM | |||
| import com.suliang.common.extension.click | |||
| import com.suliang.common.util.DateUtil | |||
| import com.suliang.common.util.DrawableUti | |||
| import com.suliang.common.util.LogUtil | |||
| import com.suliang.common.util.image.ImageLoader | |||
| import com.suliang.common.util.media.EMediaState | |||
| @@ -91,6 +93,14 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| /** 横幅 自动播放 与 复习时使用 */ | |||
| private fun initBanner() { | |||
| // TODO: 2022/4/25 初始化横幅,默认隐藏 | |||
| if (vm.learnData.isAutoPlay) { | |||
| binding.tvBanner.apply { | |||
| visibility = View.VISIBLE | |||
| setText(R.string.auto_playing) | |||
| setBackgroundColor(Color.parseColor("#1A5082E6")) | |||
| setTextColor(ContextCompat.getColor(this@LearnWordActivity, R.color.theme_color)) | |||
| } | |||
| } | |||
| } | |||
| /** 标题初始 */ | |||
| @@ -122,9 +132,9 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| private fun initHistoricalRoute() { | |||
| adapterHistorical = AdapterHistoricalRoute(vm.learnData.lesson.courseType).apply { | |||
| onItemClick = { v : View, position : Int, item : LearnWord -> | |||
| //历史轨迹的点击,改变当前选项 | |||
| vm.currentIsHistoricalItemClick = true | |||
| vm.currentLearnWord.value = item | |||
| if (!vm.learnData.isAutoPlay) { //自动播放历史轨迹点击无效 | |||
| vm.clickHistoricalItem(item) | |||
| } | |||
| } | |||
| } | |||
| binding.rvHistoricalRoute.apply { | |||
| @@ -177,8 +187,11 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| } | |||
| AppConstants.COURSE_TYPE_ENGLISH_VOICE -> { | |||
| bindingWord = IncLearnWordBinding.inflate(layoutInflater, binding.containerLayout, true) | |||
| bindingWord.ivVoice.click { readWord() } | |||
| bindingWord.incWord.tvWord.click { readWord() } | |||
| //自动播放点击无效 | |||
| if (!vm.learnData.isAutoPlay) { | |||
| bindingWord.ivVoice.click { readWord() } | |||
| bindingWord.incWord.tvWord.click { readWord() } | |||
| } | |||
| } | |||
| else -> { | |||
| bindingWord = IncLearnWordBinding.inflate(layoutInflater, binding.containerLayout, true) | |||
| @@ -186,10 +199,28 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| bindingWord.incWord.tvWord.click { readWord() } | |||
| } | |||
| } | |||
| //自动播放初始可见 | |||
| if (vm.learnData.isAutoPlay) { | |||
| View.VISIBLE.let { | |||
| bindingWord.run { | |||
| imgWord.visibility = it | |||
| incWord.tvWord.visibility = it | |||
| tvPhonetic.visibility = it | |||
| tvExplain.visibility = it | |||
| tvExpandExplain.visibility = it | |||
| } | |||
| incWorcDetailBinding.root.visibility = it | |||
| } | |||
| } | |||
| } | |||
| //初始化按钮 | |||
| private fun initControlButton() { | |||
| //自动播放隐藏操作按钮 | |||
| if (vm.learnData.isAutoPlay){ | |||
| binding.incControlButton.root.visibility= View.GONE | |||
| return | |||
| } | |||
| //其他课程默认按钮全部隐藏 | |||
| binding.incControlButton.run { | |||
| tvLeft.visibility = View.INVISIBLE | |||
| @@ -222,19 +253,74 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| //最底部 | |||
| private fun initBottom(){ | |||
| binding.tvPlay.visibility = View.GONE | |||
| binding.tvPlayStop.visibility = View.GONE | |||
| //有效时间 | |||
| vm.validTime.observe(this){ | |||
| binding.tvValidTime.text = "本次学习 ${DateUtil.formatGMT(it,DateUtil.FORMAT_2)}" | |||
| } | |||
| if (vm.learnData.isAutoPlay){ | |||
| binding.tvPlay.run { | |||
| visibility = View.VISIBLE | |||
| tag = 1 | |||
| setText(R.string.auto_play_paused) | |||
| } | |||
| binding.tvPlayStop.run { | |||
| visibility = View.VISIBLE | |||
| setText(R.string.auto_play_stop) | |||
| } | |||
| binding.tvPlay.click { | |||
| when(binding.tvPlay.tag as Int){ | |||
| 1 -> { //暂停播放 | |||
| vm.isAutoPlaying = false | |||
| binding.tvPlay.apply { | |||
| setText(R.string.auto_play_continue) | |||
| setIconResource(R.drawable.ic_play) | |||
| tag = 2 | |||
| } | |||
| //点击暂停后 | |||
| binding.tvBanner.apply { | |||
| setText(R.string.auto_play_banner_paused) | |||
| setBackgroundColor(Color.parseColor("#1AF26255")) | |||
| setTextColor(ContextCompat.getColor(this@LearnWordActivity,R.color.red_1)) | |||
| } | |||
| } | |||
| 2 -> { //继续播放 | |||
| vm.isAutoPlaying = true | |||
| binding.tvPlay.apply { | |||
| setText(R.string.auto_play_paused) | |||
| setIconResource(R.drawable.ic_play_pause) | |||
| tag = 1 | |||
| LiveEventBus.get<Int>("auto_play").post(0) | |||
| } | |||
| //点击继续后 | |||
| binding.tvBanner.apply { | |||
| setText(R.string.auto_playing) | |||
| setBackgroundColor(Color.parseColor("#1A5082E6")) | |||
| setTextColor(ContextCompat.getColor(this@LearnWordActivity, R.color.theme_color)) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| //停止播放 | |||
| binding.tvPlayStop.click {onBackPressed()} | |||
| } | |||
| } | |||
| /** 发音监听 */ | |||
| private val impListener = object : IMPListener { | |||
| override fun onMpState(state : EMediaState) { | |||
| when (state) { | |||
| EMediaState.RUNNING -> bindingWord.ivVoice.playAnimation() | |||
| EMediaState.COMPLETE, EMediaState.ERROR -> bindingWord.ivVoice.cancelAnimation() | |||
| when{ | |||
| vm.learnData.isAutoPlay -> when (state) { | |||
| EMediaState.COMPLETE, EMediaState.ERROR -> { | |||
| //播放次数+1 | |||
| vm.currentPlayTime ++ | |||
| //继续播放,或获取下一条 | |||
| LiveEventBus.get<Int>("auto_play").postDelay(0,700) | |||
| } | |||
| } | |||
| else -> when (state) { | |||
| EMediaState.RUNNING -> bindingWord.ivVoice.playAnimation() | |||
| EMediaState.COMPLETE, EMediaState.ERROR -> bindingWord.ivVoice.cancelAnimation() | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -242,9 +328,30 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| override fun loadData() { | |||
| vm.loadNext() | |||
| //如果自动播放,监听信息 | |||
| if (vm.learnData.isAutoPlay){ | |||
| LiveEventBus.get<Int>("auto_play").observe(this){ | |||
| if (!vm.isAutoPlaying) return@observe | |||
| when(vm.currentPlayTime){ | |||
| vm.learnData.autoPlayTime -> vm.loadNext() | |||
| else -> readWord() | |||
| } | |||
| } | |||
| } | |||
| //发音数据 | |||
| AudioCache.initAudioLiveData().observe(this) { | |||
| //自动播放的单词音频数据 | |||
| if (vm.learnData.isAutoPlay){ | |||
| it?.run { | |||
| MPManager.play(it,listener = impListener) | |||
| }?:let { | |||
| showToast("未找到发音文件") | |||
| //获取下一条 | |||
| vm.loadNext() | |||
| } | |||
| return@observe | |||
| } | |||
| it?.run { | |||
| if (vm.learnData.lesson.courseType == AppConstants.COURSE_TYPE_ENGLISH_VOICE && bindingWord.ivVoice.visibility == View.VISIBLE) MPManager.play( | |||
| it, listener = impListener) | |||
| @@ -255,7 +362,10 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| if (vm.isNeedLoadPhoto) { | |||
| //图片数据 | |||
| PhotoCache.initPhotoLiveData().observe(this) { | |||
| it?.run { ImageLoader.loadImage(bindingWord.imgWord, it) } ?: let { bindingWord.imgWord.visibility = View.GONE } | |||
| it?.run { | |||
| if(vm.learnData.isAutoPlay)bindingWord.imgWord.visibility = View.VISIBLE | |||
| ImageLoader.loadImage(bindingWord.imgWord, it) | |||
| } ?: let { bindingWord.imgWord.visibility = View.GONE } | |||
| } | |||
| } | |||
| @@ -266,6 +376,10 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| adapterHistorical.addData(it) | |||
| binding.rvHistoricalRoute.scrollToPosition(adapterHistorical.itemCount - 1) | |||
| } | |||
| if (vm.learnData.isAutoPlay){ | |||
| initAutoPlayWord(it) | |||
| return@observe | |||
| } | |||
| //界面初始 | |||
| when (vm.learnData.lesson.courseType) { | |||
| AppConstants.COURSE_TYPE_ENGLISH_VOICE -> initVoice(it) | |||
| @@ -285,6 +399,42 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| } | |||
| } | |||
| /**新数据到来,自动播放界面显示*/ | |||
| @SuppressLint("SetTextI18n") | |||
| private fun initAutoPlayWord(learnWord : LearnWord) { | |||
| //发音 | |||
| readWord() | |||
| //图片 | |||
| if (vm.isNeedLoadPhoto) { | |||
| bindingWord.imgWord.visibility = View.INVISIBLE | |||
| PhotoCache.get(vm.dbControlBase, learnWord.wordId) | |||
| } | |||
| //单词内容 | |||
| bindingWord.incWord.tvWord.apply { | |||
| //识字需单独处理 | |||
| text = if (vm.learnData.lesson.courseType == AppConstants.COURSE_TYPE_CHINESE_LITERACY) ViewUtil.literacyToHtmlWord( | |||
| learnWord.word, learnWord.showColor) else learnWord.word | |||
| setTextColor(ContextCompat.getColor(this@LearnWordActivity, learnWord.showColor)) //单词显示颜色 | |||
| } | |||
| //音标 | |||
| if (vm.learnData.lesson.subjectId == AppConstants.SUBJECT_ENGLISH) { | |||
| val generatePhonetic = ViewUtil.generatePhonetic(bindingWord.tvPhonetic, learnWord.phonetic_uk, learnWord.phonetic_us) | |||
| bindingWord.tvPhonetic.text = generatePhonetic | |||
| } else { | |||
| bindingWord.tvPhonetic.text = learnWord.phonetic_cn | |||
| } | |||
| //基本释义 | |||
| bindingWord.tvExplain.text = learnWord.basic_explanation | |||
| //扩展释义 | |||
| initFirstVisible(bindingWord.tvExpandExplain, learnWord.extend_explanation) | |||
| if (bindingWord.tvExpandExplain.text.isNotEmpty()) { | |||
| bindingWord.tvExpandExplain.text = "扩展释义:${bindingWord.tvExpandExplain.text}" | |||
| bindingWord.tvExpandExplain.visibility = View.VISIBLE | |||
| } | |||
| //详情 | |||
| incWorcDetailBinding.initValue(learnWord.phrase, learnWord.example, learnWord.reference) | |||
| } | |||
| /**新数据到来,进行初始化*/ | |||
| @SuppressLint("SetTextI18n") | |||
| @@ -423,6 +573,7 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| private fun clickNext() { | |||
| //拼写,需要处理历史轨迹 和在下一条的时候保存当前学习数据 | |||
| if (vm.learnData.lesson.courseType == AppConstants.COURSE_TYPE_ENGLISH_SPELL){ | |||
| adapterHistorical.currentLearnOver() | |||
| when{ | |||
| //正确 | |||
| vm.currentSpellIsCorrect -> { | |||
| @@ -440,8 +591,8 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| } | |||
| } | |||
| //当前数据为历史轨迹点击 且 最后一条学习未完成 | |||
| if (vm.currentIsHistoricalItemClick && !adapterHistorical.lastIsLearnOver) { | |||
| //当前数据为历史轨迹点击 且 最后一条学习未完成 且不为最后一条 | |||
| if (!adapterHistorical.currentIsLastSelect && vm.currentIsHistoricalItemClick && !adapterHistorical.lastIsLearnOver) { | |||
| skipToHistoricalLastItem() | |||
| } else { | |||
| vm.loadNext() | |||
| @@ -586,7 +737,13 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| override fun dispatchTouchEvent(ev : MotionEvent?) : Boolean { | |||
| //执行有效计时 | |||
| vm.executeLearnValidTime() | |||
| when{ | |||
| //自动播放只有在非播放时触摸有效 | |||
| vm.learnData.isAutoPlay -> when{ | |||
| !vm.isAutoPlaying -> vm.executeLearnValidTime() | |||
| } | |||
| else -> vm.executeLearnValidTime() | |||
| } | |||
| return super.dispatchTouchEvent(ev) | |||
| } | |||
| @@ -595,30 +752,61 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| when { | |||
| vm.isAllOver -> finish() | |||
| else -> when { | |||
| vm.isHasLearned -> { | |||
| CommonDialog.newInstance( | |||
| CommonDialogBean(titleText = R.string.quit_learn_title, contentText = R.string.quit_learn_content, | |||
| leftText = R.string.quit, rightText = R.string.cancel)).apply { | |||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||
| dialog.dismissAllowingStateLoss() | |||
| when { | |||
| // TODO: 2022/4/26 取消,恢复计时 | |||
| isRightClick -> "" | |||
| // TODO: 2022/4/26 保存数据 | |||
| else -> vm.saveData() | |||
| } | |||
| } | |||
| }.show(supportFragmentManager, "learn_back_dialog") | |||
| } | |||
| vm.learnData.isAutoPlay -> autoPlayBackDialog() | |||
| vm.isHasLearned -> learnBackDialog() | |||
| else -> finish() | |||
| } | |||
| } | |||
| } | |||
| /** 学习时的返回弹窗 */ | |||
| private fun learnBackDialog() { | |||
| vm.showOrDismissBackDialogForTime(true) | |||
| CommonDialog.newInstance( | |||
| CommonDialogBean(titleText = R.string.quit_learn_title, contentText = R.string.quit_learn_content, leftText = R.string.quit, rightText = R.string.cancel)).apply { | |||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||
| dialog.dismissAllowingStateLoss() | |||
| when { | |||
| isRightClick -> vm.showOrDismissBackDialogForTime(false) | |||
| else -> vm.saveData() | |||
| } | |||
| } | |||
| }.show(supportFragmentManager, "learn_back_dialog") | |||
| } | |||
| /** 自动播放时的返回弹窗 */ | |||
| private fun autoPlayBackDialog() { | |||
| vm.showOrDismissBackDialogForTime(true) | |||
| CommonDialog.newInstance( | |||
| CommonDialogBean(titleText = R.string.quit_auto_play_title, leftText = R.string.cancel, rightText = R.string.quit)).apply { | |||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||
| dialog.dismissAllowingStateLoss() | |||
| when { | |||
| isRightClick -> vm.saveData() | |||
| else -> { | |||
| vm.showOrDismissBackDialogForTime(false) | |||
| LiveEventBus.get<Int>("auto_play").post(0) | |||
| } | |||
| } | |||
| } | |||
| }.show(supportFragmentManager, "auto_play_back_dialog") | |||
| } | |||
| /** 学习完成 */ | |||
| private fun showLearnOverDialog() { | |||
| //自动播放完成弹窗 | |||
| if (vm.learnData.isAutoPlay){ | |||
| val drawable = DrawableUti.changeSvgSizeAndColor(resources, R.drawable.ic_right, R.color.theme_color,3) | |||
| CommonDialog.newInstance(CommonDialogBean(titleText = R.string.quit_auto_play_title_over, | |||
| rightText = R.string.sure),drawable).apply { | |||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||
| dialog.dismissAllowingStateLoss() | |||
| finish() | |||
| } | |||
| }.show(supportFragmentManager, "auto_play_back_dialog") | |||
| return | |||
| } | |||
| vm.loadAfterTest().observe(this) { showTimeCount -> | |||
| LearnDialog.newInstance(LearnDialogBean(AppConstants.DIALOG_TYPE_LEARN_OVER).apply { | |||
| LearnDialog.newInstance(LearnDialogBean(AppConstants.DIALOG_TYPE_LEARNING_OVER).apply { | |||
| correctNumber = vm.learnData.lesson.correctNumber | |||
| errorNumber = vm.learnData.lesson.errorNumber | |||
| this.showTimeCount = showTimeCount | |||
| @@ -7,6 +7,7 @@ import com.jeremyliao.liveeventbus.LiveEventBus | |||
| import com.suliang.common.extension.createRandomNewChar | |||
| import com.suliang.common.extension.diskIo2Main | |||
| import com.suliang.common.util.DateUtil | |||
| import com.suliang.common.util.LogUtil | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.DataTransferHolder | |||
| import com.xkl.cdl.data.bean.LearnWord | |||
| @@ -32,6 +33,7 @@ class LearnWordViewModel : LearnBaseViewModel() { | |||
| //记录是否是历史轨迹的item点击 | |||
| var currentIsHistoricalItemClick : Boolean = false | |||
| private set | |||
| //默认发音 | |||
| var defaultSoundWay : Int = 0 | |||
| @@ -72,12 +74,42 @@ class LearnWordViewModel : LearnBaseViewModel() { | |||
| //数据上传完成监听: true 学习完成的上传 false可能是半途的返回事件,直接退出 | |||
| val saveDataLiveData = MutableLiveData<Boolean>() | |||
| //是否显示返回弹窗 | |||
| private var isShowBackDialog = false | |||
| //自动播放模式:是否在自动播放中,默认是 | |||
| var isAutoPlaying = true | |||
| //当前单词的自动播放次数 | |||
| var currentPlayTime = 0 | |||
| /** | |||
| * 定义有效时间 | |||
| * 学习和自动播放有一点不一样哟 | |||
| */ | |||
| override fun initRunValidTime() { | |||
| //自动播放单独定义有效时间规则,播放时时间和总时间一直,停止时播放时,通过触摸判断 | |||
| if (learnData.isAutoPlay){ | |||
| if (isRunValidTime){ | |||
| validTime.postValue(validTime.value!!.plus(200)) | |||
| if (!isAutoPlaying){ //停止播放了 | |||
| currentValidSurplusTime -= 200 | |||
| isRunValidTime = currentValidSurplusTime > 0 | |||
| } | |||
| } | |||
| }else { | |||
| super.initRunValidTime() | |||
| } | |||
| } | |||
| /** 获取数据 */ | |||
| fun loadNext() { | |||
| //修改标记 | |||
| currentIsHistoricalItemClick = false | |||
| //新数据自动播放次数归0 | |||
| if (learnData.isAutoPlay){ | |||
| currentPlayTime = 0 | |||
| } | |||
| val word = learnRuleUtil.loadNext | |||
| word?.let { | |||
| @@ -140,8 +172,29 @@ class LearnWordViewModel : LearnBaseViewModel() { | |||
| /** 设置已学位置 */ | |||
| private fun setLearnPoint(currentIsLastSelect : Boolean, isCycleFirst : Boolean) { | |||
| if (currentIsLastSelect && isCycleFirst) { | |||
| currentLessonLearnedPosition++ | |||
| if (!learnData.isAutoPlay) { //自动播放不记录学习点位置 | |||
| if (currentIsLastSelect && isCycleFirst) { | |||
| currentLessonLearnedPosition++ | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| * 显示或关闭的返回弹窗: 不影响总计时,只影响当前的下一题计时或者倒计时 | |||
| * 显示返回弹窗的时机,只会在测试当种,则是结束后则不会再显示此弹窗 | |||
| * @param isShow Boolean | |||
| */ | |||
| fun showOrDismissBackDialogForTime(isShow : Boolean) { | |||
| isShowBackDialog = isShow | |||
| when { | |||
| isShowBackDialog -> { //显示返回弹窗 总计时不停,停止有效计时 | |||
| isAutoPlaying = false | |||
| isRunValidTime = false | |||
| } | |||
| else -> { //关闭返回弹窗 恢复有效计时 | |||
| isRunValidTime = true | |||
| if (learnData.isAutoPlay) isAutoPlaying = true | |||
| } | |||
| } | |||
| } | |||
| @@ -242,15 +295,17 @@ class LearnWordViewModel : LearnBaseViewModel() { | |||
| // TODO: 2022/4/14 传递保存record信息 | |||
| // record 已经实例化并已经将数据保存 | |||
| if (!saveInit) { | |||
| learnData.lesson.apply { | |||
| learnedIndex = currentLessonLearnedPosition | |||
| correctNumber += learnRuleUtil.currentCorrectMap.size | |||
| errorNumber += learnRuleUtil.currentErrorMap.size | |||
| learnIsOver = learnedIndex == wordIds.size - 1 | |||
| //自动播放不修改课时信息 | |||
| if (!learnData.isAutoPlay) { | |||
| learnData.lesson.apply { | |||
| learnedIndex = currentLessonLearnedPosition | |||
| correctNumber += learnRuleUtil.currentCorrectMap.size | |||
| errorNumber += learnRuleUtil.currentErrorMap.size | |||
| learnIsOver = learnedIndex == wordIds.size - 1 | |||
| } | |||
| //添加到错误本集合中,主要用于小游戏练习(学前总 课时都没有添加,从这里添加到集合后发送出去,添加到集合) | |||
| learnData.examErrorMap?.putAll(learnRuleUtil.currentErrorMap) | |||
| } | |||
| //添加到错误本集合中,主要用于小游戏练习(学前总 课时都没有添加,从这里添加到集合后发送出去,添加到集合) | |||
| learnData.examErrorMap?.putAll(learnRuleUtil.currentErrorMap) | |||
| record.addDuration(saveCurrentLearnDuration()) | |||
| saveInit = true | |||
| } | |||
| @@ -282,6 +337,8 @@ class LearnWordViewModel : LearnBaseViewModel() { | |||
| /** 发送数据事件 */ | |||
| private fun sendEventBus() { | |||
| //自动播放不发送信息通知 | |||
| if (learnData.isAutoPlay) return | |||
| LiveEventBus.get<LearnEventData>(AppConstants.EVENT_LESSON_DATA).post(LearnEventData(learnData.lesson.subjectId, | |||
| learnData.lesson.courseId, | |||
| AppConstants.DATA_LESSON_LEARN_OVER).apply { | |||
| @@ -301,5 +358,19 @@ class LearnWordViewModel : LearnBaseViewModel() { | |||
| stopTotalCountTing() | |||
| } | |||
| /** | |||
| * 历史轨迹item的点击 | |||
| * @param item LearnWord | |||
| */ | |||
| fun clickHistoricalItem(item : LearnWord) { | |||
| //历史轨迹的点击,改变当前选项 | |||
| currentIsHistoricalItemClick = true | |||
| //拼写时,需要对当前内容选项进行拼接 | |||
| if (learnData.lesson.courseType == AppConstants.COURSE_TYPE_ENGLISH_SPELL) { | |||
| initSpellOption(item.word) | |||
| } | |||
| currentLearnWord.value = item | |||
| } | |||
| } | |||
| @@ -22,14 +22,20 @@ import com.xkl.cdl.R | |||
| import com.xkl.cdl.adapter.AdapterAutoPlaySelectRepeat | |||
| import com.xkl.cdl.adapter.ViewPagerAdapter | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.DataTransferHolder | |||
| import com.xkl.cdl.data.bean.LearnDialogBean | |||
| import com.xkl.cdl.data.bean.LearnWord | |||
| import com.xkl.cdl.data.bean.course.Lesson | |||
| import com.xkl.cdl.data.bean.intentdata.LearnData | |||
| import com.xkl.cdl.data.binding.BindingAdapter | |||
| import com.xkl.cdl.data.manager.CourseManager | |||
| import com.xkl.cdl.data.repository.DataRepository | |||
| import com.xkl.cdl.databinding.ActivityCourseMainBinding | |||
| import com.xkl.cdl.databinding.DialogBottomAutoPlaySelectBinding | |||
| import com.xkl.cdl.databinding.DialogBottomCourseMoreBinding | |||
| import com.xkl.cdl.dialog.CommonDialog | |||
| import com.xkl.cdl.dialog.CommonDialogBean | |||
| import com.xkl.cdl.module.learn.LearnWordActivity | |||
| import com.xkl.cdl.module.m_center_learn.coursechildren.CourseMainFragment | |||
| import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly | |||
| @@ -37,34 +43,33 @@ import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly | |||
| * 课程中心 | |||
| */ | |||
| class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CoursePackMainActivityViewModel>() { | |||
| companion object { | |||
| @JvmStatic | |||
| fun newInstance(context: Context, subjectId: Int, coursePackInPosition: Int) { | |||
| fun newInstance(context : Context, subjectId : Int, coursePackInPosition : Int) { | |||
| context.startActivity(Intent(context, CoursePackMainActivity::class.java).apply { | |||
| putExtra(AppConfig.INTENT_1, subjectId) | |||
| putExtra(AppConfig.INTENT_2, coursePackInPosition) | |||
| }) | |||
| } | |||
| } | |||
| //子课程对应的Fragment | |||
| private var childFragments = mutableListOf<Fragment>() | |||
| //更多按钮的弹窗显示 | |||
| private var moreDialog: BottomSheetDialog? = null | |||
| private var moreDialog : BottomSheetDialog? = null | |||
| //自动播放次数的弹窗选择 | |||
| private var autoPlaySeletDialog: BottomSheetDialog? = null | |||
| override fun initViewModel(): CoursePackMainActivityViewModel { | |||
| private var autoPlaySeletDialog : BottomSheetDialog? = null | |||
| override fun initViewModel() : CoursePackMainActivityViewModel { | |||
| val subjectId = intent.getIntExtra(AppConfig.INTENT_1, 0) | |||
| val coursePackInListPosition = intent.getIntExtra(AppConfig.INTENT_2, 0) | |||
| return ViewModelProvider( | |||
| this, ViewModelFactory(subjectId, coursePackInListPosition) | |||
| )[CoursePackMainActivityViewModel::class.java] | |||
| return ViewModelProvider(this, ViewModelFactory(subjectId, | |||
| coursePackInListPosition))[CoursePackMainActivityViewModel::class.java] | |||
| } | |||
| override fun initStatusBar() { | |||
| statusBarOnly { | |||
| fitWindow = false //布局是否侵入状态栏 | |||
| @@ -72,9 +77,9 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP | |||
| light = true | |||
| } | |||
| } | |||
| override fun initActivity(savedInstanceState: Bundle?) { | |||
| LogUtil.e("CoursePackMainActivity coursePackMainActivityVM hashCode -> ${vm.hashCode()}") | |||
| override fun initActivity(savedInstanceState : Bundle?) { | |||
| // LogUtil.e("CoursePackMainActivity coursePackMainActivityVM hashCode -> ${vm.hashCode()}") | |||
| //设置布局数据 | |||
| binding.coursePack = vm.coursePack | |||
| //状态栏高度设置 | |||
| @@ -86,13 +91,13 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP | |||
| } | |||
| //返回事件 | |||
| binding.includeTitleBar.titleBar.run { | |||
| onBackClick = {_ -> finish() } | |||
| onBackClick = { _ -> finish() } | |||
| } | |||
| //初始化tab和viewPager | |||
| initTabAndViewPager() | |||
| } | |||
| private fun initTabAndViewPager() { | |||
| //英语课程包且为英语单词课程包才显示tab | |||
| if (vm.coursePack.subjectId == AppConstants.SUBJECT_ENGLISH && vm.coursePack.coursePackType == AppConstants.COURSEPACK_TYPE_ENGLISH_WORD) { | |||
| @@ -124,52 +129,28 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP | |||
| adapter = ViewPagerAdapter(this@CoursePackMainActivity, childFragments) | |||
| //注册页面改变监听 | |||
| registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { | |||
| override fun onPageSelected(position: Int) { | |||
| override fun onPageSelected(position : Int) { | |||
| super.onPageSelected(position) | |||
| //改变tab的选中颜色 | |||
| if (binding.includeCourseTypeTab.root.visibility == View.VISIBLE) { //可见,才做操作 | |||
| binding.includeCourseTypeTab.apply { | |||
| tabDiscern.run { | |||
| setTextColor( | |||
| ContextCompat.getColor( | |||
| this@CoursePackMainActivity, | |||
| if (position == 0) R.color.theme_color else R.color.gray_2 | |||
| ) | |||
| ) | |||
| BindingAdapter.loadVectorDrawable( | |||
| this, ContextCompat.getColor( | |||
| this@CoursePackMainActivity, | |||
| if (position == 0) R.color.theme_color else R.color.gray_2 | |||
| ) | |||
| ) | |||
| setTextColor(ContextCompat.getColor(this@CoursePackMainActivity, | |||
| if (position == 0) R.color.theme_color else R.color.gray_2)) | |||
| BindingAdapter.loadVectorDrawable(this, ContextCompat.getColor(this@CoursePackMainActivity, | |||
| if (position == 0) R.color.theme_color else R.color.gray_2)) | |||
| } | |||
| tabSpell.run { | |||
| setTextColor( | |||
| ContextCompat.getColor( | |||
| this@CoursePackMainActivity, | |||
| if (position == 1) R.color.theme_color else R.color.gray_2 | |||
| ) | |||
| ) | |||
| BindingAdapter.loadVectorDrawable( | |||
| this, ContextCompat.getColor( | |||
| this@CoursePackMainActivity, | |||
| if (position == 1) R.color.theme_color else R.color.gray_2 | |||
| ) | |||
| ) | |||
| setTextColor(ContextCompat.getColor(this@CoursePackMainActivity, | |||
| if (position == 1) R.color.theme_color else R.color.gray_2)) | |||
| BindingAdapter.loadVectorDrawable(this, ContextCompat.getColor(this@CoursePackMainActivity, | |||
| if (position == 1) R.color.theme_color else R.color.gray_2)) | |||
| } | |||
| tabVoice.run { | |||
| setTextColor( | |||
| ContextCompat.getColor( | |||
| this@CoursePackMainActivity, | |||
| if (position == 2) R.color.theme_color else R.color.gray_2 | |||
| ) | |||
| ) | |||
| BindingAdapter.loadVectorDrawable( | |||
| this, ContextCompat.getColor( | |||
| this@CoursePackMainActivity, | |||
| if (position == 2) R.color.theme_color else R.color.gray_2 | |||
| ) | |||
| ) | |||
| setTextColor(ContextCompat.getColor(this@CoursePackMainActivity, | |||
| if (position == 2) R.color.theme_color else R.color.gray_2)) | |||
| BindingAdapter.loadVectorDrawable(this, ContextCompat.getColor(this@CoursePackMainActivity, | |||
| if (position == 2) R.color.theme_color else R.color.gray_2)) | |||
| } | |||
| } | |||
| } | |||
| @@ -204,29 +185,26 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP | |||
| } | |||
| //更多按钮点击 | |||
| binding.includeCourseProgress.ivMore.click { | |||
| if (vm.showMoreEnable) | |||
| showMoreDialog() | |||
| else | |||
| showToast("请先进行学习") | |||
| if ((childFragments[binding.viewPager2.currentItem] as CourseMainFragment).vm.showMoreIsEnable()) showMoreDialog() | |||
| else showToast("请先进行学习") | |||
| } | |||
| } | |||
| override fun loadData() { | |||
| } | |||
| /** 点击显示更多的弹窗 */ | |||
| private fun showMoreDialog() { | |||
| if (moreDialog == null) { | |||
| moreDialog = BottomSheetDialog(this, R.style.dialog_style).apply { | |||
| val moreBinding = DataBindingUtil.inflate<DialogBottomCourseMoreBinding>( | |||
| layoutInflater, R.layout.dialog_bottom_course_more, null, false | |||
| ) | |||
| val moreBinding = DataBindingUtil.inflate<DialogBottomCourseMoreBinding>(layoutInflater, | |||
| R.layout.dialog_bottom_course_more, null, | |||
| false) | |||
| setContentView(moreBinding.root) | |||
| //体验账号、拼些、口语课程、作文课程不显示自动播放 | |||
| when (vm.coursePack.childrenCourses[binding.viewPager2.currentItem].courseType) { | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL, AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> moreBinding.tvAutoPlay.visibility = | |||
| View.GONE | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL, AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> moreBinding.tvAutoPlay.visibility = View.GONE | |||
| else -> moreBinding.tvAutoPlay.visibility = View.VISIBLE | |||
| } | |||
| moreBinding.ivCancel.click { | |||
| @@ -244,34 +222,35 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP | |||
| } | |||
| moreDialog?.show() | |||
| } | |||
| /** 课程重学弹窗提示 */ | |||
| fun showCourseRelearnDialog(){ | |||
| fun showCourseRelearnDialog() { | |||
| //弹窗显示清除学习记录 | |||
| val bean = CommonDialogBean(titleText = R.string.course_relearn_title, | |||
| contentText = R.string.course_relearn_content, | |||
| val bean = CommonDialogBean(titleText = R.string.course_relearn_title, contentText = R.string.course_relearn_content, | |||
| leftText = R.string.course_relearn_sure, rightText = R.string.cancel) | |||
| CommonDialog.newInstance(bean).apply { | |||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||
| dialog.dismissAllowingStateLoss() | |||
| if (!isRightClick){ | |||
| ((this@CoursePackMainActivity.binding.viewPager2.adapter as ViewPagerAdapter).getItem(this@CoursePackMainActivity.binding.viewPager2.currentItem) as CourseMainFragment).run { | |||
| if (!isRightClick) { | |||
| ((this@CoursePackMainActivity.binding.viewPager2.adapter as ViewPagerAdapter).getItem( | |||
| this@CoursePackMainActivity.binding.viewPager2.currentItem) as CourseMainFragment).run { | |||
| this.courseRelearn() | |||
| } | |||
| } | |||
| } | |||
| }.show(supportFragmentManager,"course_relearn_dialog") | |||
| }.show(supportFragmentManager, "course_relearn_dialog") | |||
| } | |||
| /** 自动播放次数选择: 仅单词会有该选项 */ | |||
| private fun showAutoPlaySelectDialog() { | |||
| if (autoPlaySeletDialog == null) { | |||
| autoPlaySeletDialog = BottomSheetDialog(this, R.style.dialog_style).apply { | |||
| val autoPlayBinding = DataBindingUtil.inflate<DialogBottomAutoPlaySelectBinding>( | |||
| layoutInflater, R.layout.dialog_bottom_auto_play_select, null, false | |||
| ) | |||
| val autoPlayBinding = DataBindingUtil.inflate<DialogBottomAutoPlaySelectBinding>(layoutInflater, | |||
| R.layout.dialog_bottom_auto_play_select, | |||
| null, false) | |||
| setContentView(autoPlayBinding.root) | |||
| autoPlayBinding.ivCancel.click { v -> dismiss() } | |||
| autoPlayBinding.rvRepeat.run { | |||
| adapter = AdapterAutoPlaySelectRepeat().apply { | |||
| onItemClick = { _, position, _ -> | |||
| @@ -284,31 +263,48 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP | |||
| } | |||
| autoPlaySeletDialog?.show() | |||
| } | |||
| /** | |||
| * 开始自动播放 | |||
| * @param time Int | |||
| * @param time Int 播放次数 | |||
| */ | |||
| private fun startAutoPlay(time: Int) { | |||
| private fun startAutoPlay(time : Int) { | |||
| //查询单词,进入学习界面,开始播放 | |||
| (childFragments[binding.viewPager2.currentItem] as CourseMainFragment).vm.let { | |||
| it.queryWordAutoPlayWord().observe(this) { value -> | |||
| value?.let { data -> | |||
| val lesson = Lesson(it.course.subjectId, it.course.coursePackId, it.course.coursePackType, | |||
| it.course.courseId, it.course.courseType, 0, "", 0, it.course.courseTitle) | |||
| DataTransferHolder.instance.putData(value = LearnData(lesson).apply { | |||
| learnWordList = data | |||
| isAutoPlay = true | |||
| autoPlayTime = time | |||
| }) | |||
| startActivity(LearnWordActivity::class.java) | |||
| } ?: let { | |||
| showToast("未查询到自动播放数据") | |||
| } | |||
| } | |||
| } | |||
| } | |||
| override fun onStop() { | |||
| super.onStop() | |||
| //如果是中文项目,更新课程包的进度 | |||
| if (vm.coursePack.subjectId == AppConstants.SUBJECT_CHINESE){ | |||
| if (vm.coursePack.subjectId == AppConstants.SUBJECT_CHINESE) { | |||
| vm.coursePack.learnProgress = vm.coursePack.childrenCourses[0].courseLearnProgress | |||
| } | |||
| } | |||
| /** ViewModel Factory工厂 */ | |||
| inner class ViewModelFactory(private val subjectId: Int, private val coursePackInPosition: Int) : | |||
| inner class ViewModelFactory(private val subjectId : Int, private val coursePackInPosition : Int) : | |||
| ViewModelProvider.Factory { | |||
| override fun <T : ViewModel?> create(modelClass: Class<T>): T { | |||
| override fun <T : ViewModel?> create(modelClass : Class<T>) : T { | |||
| return CoursePackMainActivityViewModel(subjectId, coursePackInPosition) as T | |||
| } | |||
| } | |||
| } | |||
| @@ -17,7 +17,4 @@ class CoursePackMainActivityViewModel(subjectId: Int , coursePackInPosition : In | |||
| //设置显示当前课程的进度值和显示内容 | |||
| val currentCourseProgress = MutableLiveData<Double>() | |||
| //更多点击是否有效 | |||
| var showMoreEnable = false | |||
| } | |||
| @@ -127,7 +127,7 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| adapterLesson.notifyItemChanged(learnEventData.leesonPositionIndex) | |||
| vm.allLesson[learnEventData.leesonPositionIndex].let { | |||
| val key = "${it.chapterId}_${it.lessonId}" | |||
| vm.courseDetail.after.put(key, it.beforeTestScore) | |||
| vm.courseDetail.after.put(key, it.afterTestScore) | |||
| } | |||
| } | |||
| } | |||
| @@ -235,7 +235,7 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| } else if (!entity.learnIsOver) { //当前课时未学完,直接开始学习 | |||
| startLearn(entity) | |||
| } else if (entity.afterTestScore == AppConstants.NOT_DOING) { | |||
| loadLessonAfterTest(entity) | |||
| loadLessonAfterTest(entity,true) | |||
| } else { //当前课时学习完成的弹窗 | |||
| showLessonAllOverDialog(entity) | |||
| } | |||
| @@ -338,13 +338,49 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| } | |||
| } | |||
| /** 开始课时学后测试 | |||
| * 请求数据后,直接开始跳转 | |||
| /** 请求测试数据,去进行课时学后测试 | |||
| * @param isNeedShowLearningOverDialog 是否需要显示课时学习完成,去学后测试的弹窗,只有在直接进行item点击的时候才为true | |||
| */ | |||
| private fun loadLessonAfterTest(lesson : Lesson) { | |||
| private fun loadLessonAfterTest(lesson : Lesson, isNeedShowLearningOverDialog:Boolean = false) { | |||
| vm.loadTest(AppConstants.TEST_TYPE_AFTER, lesson).observe(this) { | |||
| startLessonTest(lesson, AppConstants.TEST_TYPE_AFTER, it) | |||
| when { | |||
| !isNeedShowLearningOverDialog -> startLessonTest(lesson, AppConstants.TEST_TYPE_AFTER, it) | |||
| else -> LearnDialog.newInstance(LearnDialogBean(AppConstants.DIALOG_TYPE_LESSON_ITEM_CLICK_NOT_DOING_AFTER_TEST).apply { | |||
| correctNumber = lesson.correctNumber | |||
| errorNumber = lesson.errorNumber | |||
| this.showTimeCount = CourseManager.expectedTestTime(lesson.courseType,AppConstants.TEST_TYPE_AFTER,it) | |||
| }).apply { | |||
| onDialogListener = { action, dialog -> | |||
| when (action) { | |||
| //重学动作 | |||
| AppConstants.DIALOG_LESSON_RELEARN -> CommonDialog.newInstance(CommonDialogBean(titleText = R.string.lesson_relearn_title, | |||
| contentText = R.string.lesson_relearn_content, | |||
| leftText = R.string.cancel, | |||
| rightText = R.string.sure)) | |||
| .apply { | |||
| onCommonDialogButtonClickListener = { relearnDialog, isRightClick -> | |||
| relearnDialog.dismissAllowingStateLoss() | |||
| if (isRightClick){ | |||
| dialog.dismissAllowingStateLoss() | |||
| //发送动作 : 重新学习 | |||
| LiveEventBus.get<LearnEventData>(AppConstants.EVENT_LESSON_ACTION).post( | |||
| LearnEventData(lesson.subjectId, | |||
| lesson.courseId, | |||
| AppConstants.ACTION_LESSON_AFTER_TEST_RELEARN).apply { | |||
| leesonPositionIndex = lesson.lessonPositionInList | |||
| }) | |||
| } | |||
| } | |||
| }.show(childFragmentManager,"lesson_relearn_tip") | |||
| // 开始学后测试 | |||
| AppConstants.DIALOG_START_TEST -> { | |||
| dialog.dismissAllowingStateLoss() | |||
| startLessonTest(lesson, AppConstants.TEST_TYPE_AFTER, it) | |||
| } | |||
| } | |||
| } | |||
| }.show(childFragmentManager, "learn_over_dialog") | |||
| } | |||
| } | |||
| } | |||
| @@ -377,7 +413,7 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| * 当前课时学前、学习、学后都完成了的弹窗 | |||
| */ | |||
| private fun showLessonAllOverDialog(lesson : Lesson) { | |||
| LearnDialog.newInstance(LearnDialogBean(AppConstants.DIALOG_TYPE_LESSON_ITEM_OVER).apply { | |||
| LearnDialog.newInstance(LearnDialogBean(AppConstants.DIALOG_TYPE_LESSON_ITEM_CLICK_ALL_OVER).apply { | |||
| correctNumber = lesson.correctNumber | |||
| errorNumber = lesson.errorNumber | |||
| score = lesson.afterTestScore.toInt() | |||
| @@ -439,7 +475,7 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| courseType = vm.course.courseType | |||
| this.testData = it | |||
| } | |||
| (parentFragment as CourseMainFragment).startExam(examData) | |||
| (requireParentFragment().parentFragment as CourseMainFragment).startExam(examData) | |||
| } | |||
| } | |||
| } | |||
| @@ -42,7 +42,6 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
| requireArguments().getInt(AppConfig.INTENT_1)))[CourseMainFragmentViewModel::class.java].apply { | |||
| coursePackMainActivityVM = ViewModelProvider(requireActivity())[CoursePackMainActivityViewModel::class.java] | |||
| } | |||
| lifecycle.addObserver(vmmodel) | |||
| return vmmodel | |||
| } | |||
| @@ -2,11 +2,13 @@ package com.xkl.cdl.module.m_center_learn.coursechildren | |||
| import androidx.lifecycle.LifecycleOwner | |||
| import androidx.lifecycle.MutableLiveData | |||
| import com.google.common.base.Joiner | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| import com.suliang.common.extension.diskIo2DiskIo | |||
| import com.suliang.common.extension.diskIo2Main | |||
| import com.suliang.common.util.DateUtil | |||
| import com.suliang.common.util.file.FileUtil | |||
| import com.suliang.common.util.thread.AppExecutors | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.bean.LearnWord | |||
| import com.xkl.cdl.data.bean.course.Course | |||
| @@ -19,8 +21,11 @@ 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.Scheduler | |||
| import io.reactivex.rxjava3.functions.BiFunction | |||
| import io.reactivex.rxjava3.schedulers.Schedulers | |||
| import mqComsumerV1.Struct | |||
| import java.io.File | |||
| import java.util.* | |||
| @@ -61,18 +66,17 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| //获取课程的章节数据 | |||
| fun loadMain() : MutableLiveData<Boolean> { | |||
| val mutableLiveData = MutableLiveData<Boolean>() | |||
| Observable.zip(DataRepository.getCourseStatistics(course.subjectId,course.coursePackId,course.courseId).flatMap { | |||
| Observable.zip(DataRepository.getCourseStatistics(course.subjectId, course.coursePackId, course.courseId).flatMap { | |||
| courseDetail = it | |||
| course.courseLearnProgress = it.courseLearnProgress | |||
| coursePackMainActivityVM.currentCourseProgress.postValue(course.courseLearnProgress) | |||
| return@flatMap DataRepository.getCourseAllLesson(dbControlBase, it) | |||
| }.flatMap { | |||
| allLesson = it | |||
| return@flatMap DataRepository.getCourseCollect() | |||
| }, | |||
| DataRepository.getReviewData(), | |||
| }, DataRepository.getReviewData(), | |||
| BiFunction<HashMap<String, Long>, Array<String>, Boolean> { t1 : HashMap<String, Long>?, t2 : Array<String>? -> | |||
| // TODO: 2022/5/5 初始化需要复习的数据 | |||
| showMoreIsEnable() | |||
| true | |||
| }).compose(diskIo2Main()).subscribe { | |||
| mutableLiveData.value = it | |||
| @@ -81,8 +85,8 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| } | |||
| /** 课程包主页上的更多按钮点击是否有效 */ | |||
| private fun showMoreIsEnable(){ | |||
| coursePackMainActivityVM.showMoreEnable = courseDetail.st_before != AppConstants.NOT_DOING | |||
| fun showMoreIsEnable() : Boolean { | |||
| return courseDetail.st_before != AppConstants.NOT_DOING | |||
| } | |||
| /** | |||
| @@ -131,18 +135,17 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| * @param lesson Lesson 课时 | |||
| * @return MutableLiveData<LearnData> | |||
| */ | |||
| fun loadLessonLearnData(lesson : Lesson): MutableLiveData<LearnData>{ | |||
| fun loadLessonLearnData(lesson : Lesson) : MutableLiveData<LearnData> { | |||
| val result = MutableLiveData<LearnData>() | |||
| Observable.create<List<LearnWord>> { | |||
| it.onNext(DBCourseManager.queryLearnData(dbControlBase, lesson)) | |||
| it.onComplete() | |||
| }.compose(diskIo2Main()) | |||
| .subscribe { | |||
| result.value = LearnData(lesson).apply { | |||
| learnWordList = it | |||
| examErrorMap = courseDetail.exam_w_r_list | |||
| }.compose(diskIo2Main()).subscribe { | |||
| result.value = LearnData(lesson).apply { | |||
| learnWordList = it | |||
| examErrorMap = courseDetail.exam_w_r_list | |||
| } | |||
| } | |||
| } | |||
| return result | |||
| } | |||
| @@ -169,7 +172,7 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| fun initSelectPosition() : Int { | |||
| //没有学习点,默认课程学习课时位置为第一个 | |||
| var temposition = 0 | |||
| when{ | |||
| when { | |||
| //学后总测试完成,默认无选中 | |||
| courseDetail.st_after != AppConstants.NOT_DOING -> temposition = -1 | |||
| //有学习点 | |||
| @@ -191,16 +194,17 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| * 课时重学 | |||
| * @param lessonPositionIndex Int 重学课时所在位置 | |||
| */ | |||
| fun relearnLesson(lessonPositionIndex : Int):MutableLiveData<Boolean> { | |||
| fun relearnLesson(lessonPositionIndex : Int) : MutableLiveData<Boolean> { | |||
| val result = MutableLiveData<Boolean>() | |||
| //重学课时后课程的进度 | |||
| val courseProgress = CourseManager.calculateEnglishCourseProgress(allLesson,true,lessonPositionIndex) | |||
| val courseProgress = CourseManager.calculateEnglishCourseProgress(allLesson, true, lessonPositionIndex) | |||
| //重学课时后项目的总进度 | |||
| val subjectProgress = CourseManager.calculateSubjectProgressWithCourseLessonRelearn(course.subjectId,course.coursePackId,course.courseId,courseProgress) | |||
| val subjectProgress = CourseManager.calculateSubjectProgressWithCourseLessonRelearn(course.subjectId, course.coursePackId, | |||
| course.courseId, courseProgress) | |||
| Observable.fromCallable { | |||
| // TODO: 2022/5/5 进行数据清除和保存 | |||
| }.compose(diskIo2DiskIo()).subscribe{ | |||
| }.compose(diskIo2DiskIo()).subscribe { | |||
| //更新lesson | |||
| val lesson = allLesson[lessonPositionIndex].apply { | |||
| errorNumber = 0 | |||
| @@ -223,11 +227,11 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| //移除总测、课时学前测试的学习错误数据 | |||
| exam_w_r_list.filterKeys { | |||
| it.startsWith(wordStartKey) | |||
| }.forEach{ | |||
| }.forEach { | |||
| exam_w_r_list.remove(it.key) | |||
| } | |||
| //移除错误本中的记录 | |||
| temporary_words.filterKeys { it.startsWith(wordStartKey) }.forEach{ | |||
| temporary_words.filterKeys { it.startsWith(wordStartKey) }.forEach { | |||
| temporary_words.remove(it.key) | |||
| } | |||
| //更新课程进度 | |||
| @@ -235,8 +239,7 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| course.courseLearnProgress = courseProgress | |||
| coursePackMainActivityVM.currentCourseProgress.postValue(courseProgress) | |||
| //更新统计总进度 | |||
| CourseManager.calculateSubjectProgress(course.subjectId, course.coursePackId, | |||
| course.courseId, courseProgress) | |||
| CourseManager.calculateSubjectProgress(course.subjectId, course.coursePackId, course.courseId, courseProgress) | |||
| //完成 | |||
| result.postValue(true) | |||
| } | |||
| @@ -248,16 +251,18 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| /** | |||
| * 课程重学方法 | |||
| */ | |||
| fun relearnCourse(): MutableLiveData<Boolean> { | |||
| fun relearnCourse() : MutableLiveData<Boolean> { | |||
| val result = MutableLiveData<Boolean>() | |||
| //重学需要重新计算进度 | |||
| val subjectProgress = CourseManager.calculateSubjectProgressWithCourseRelearn(course.subjectId,course.coursePackId,course.courseId) | |||
| val subjectProgress = CourseManager.calculateSubjectProgressWithCourseRelearn(course.subjectId, course.coursePackId, | |||
| course.courseId) | |||
| Observable.fromCallable { | |||
| // TODO: 2022/5/5 调用课程重学 重新请求详情 | |||
| val file = File(FileUtil.getSaveDirPath("appcache"), "${course.subjectId}_${course.coursePackId}_${course.courseId}") | |||
| if (file.exists()){ file.delete() } | |||
| }.compose(diskIo2DiskIo()) | |||
| .subscribe{ | |||
| if (file.exists()) { | |||
| file.delete() | |||
| } | |||
| }.compose(diskIo2DiskIo()).subscribe { | |||
| courseDetail.run { | |||
| courseLearnProgress = 0.0 | |||
| st_before = AppConstants.NOT_DOING | |||
| @@ -282,9 +287,61 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| // TODO: 2022/5/6 这里在退出时进行了统计数据缓存保存,在loadMain的时候进行了read缓存 ,后期需要清除 | |||
| override fun onDestroy(owner : LifecycleOwner) { | |||
| val objectToBytes = FileUtil.objectToBytes(courseDetail) | |||
| FileUtil.writeBytesToFile(FileUtil.getSaveDirPath("appcache"),"${course.subjectId}_${course.coursePackId}_${course.courseId}",objectToBytes) | |||
| FileUtil.writeBytesToFile(FileUtil.getSaveDirPath("appcache"), | |||
| "${course.subjectId}_${course.coursePackId}_${course.courseId}", objectToBytes) | |||
| super.onDestroy(owner) | |||
| } | |||
| /** | |||
| * 单词类,自动播放,查询自动播放使用的单词 | |||
| * @return MutableLiveData<LearnData?> | |||
| */ | |||
| fun queryWordAutoPlayWord() : MutableLiveData<List<LearnWord>?> { | |||
| val result = MutableLiveData<List<LearnWord>?>() | |||
| Observable.create<Boolean> { | |||
| //获取当前学习点所在课时 | |||
| val courseLearnPoint = courseDetail.course_learn_point | |||
| //没有学习点,则没有可自动播放的单词 | |||
| if (courseLearnPoint.isEmpty()) { | |||
| result.postValue(null) | |||
| it.onComplete() | |||
| return@create | |||
| } | |||
| //有学习点,找到学习点的课时,先自动播放向后播放,再自动播放向前播放 | |||
| val allAutoPlayWordList = mutableListOf<LearnWord>() | |||
| val split = courseLearnPoint.split("_") | |||
| val currentLessonChapterId = split[0].toLong() | |||
| val currentLessonLessonId = split[1].toLong() | |||
| var isFindCurrentLesson = false | |||
| allLesson.forEachIndexed { index, lesson -> | |||
| //进行判断,查询,学习内容 | |||
| if (lesson.learnedIndex != -1) { //学习位置不为-1 ,即有已学 | |||
| //查询 | |||
| val wordIds = Joiner.on(",").join(lesson.wordIds.subList(0, lesson.learnedIndex + 1)) | |||
| DBCourseManager.queryLearnWord(dbControlBase, lesson.chapterId, lesson.lessonId, wordIds).let { | |||
| //当前学习课时 | |||
| if (lesson.chapterId == currentLessonChapterId && lesson.lessonId == currentLessonLessonId) { | |||
| isFindCurrentLesson = true | |||
| //添加到第一个 | |||
| allAutoPlayWordList.addAll(0, it) | |||
| } else { | |||
| when { | |||
| isFindCurrentLesson -> allAutoPlayWordList.addAll(1, it)// 直接插入到第一个后面,第一个则为当前课时 | |||
| else -> allAutoPlayWordList.addAll(it) //直接添加到最后就行 | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| if (allAutoPlayWordList.isEmpty()) { | |||
| result.postValue(null) | |||
| } else { | |||
| result.postValue(allAutoPlayWordList) | |||
| } | |||
| }.subscribeOn(Schedulers.from(AppExecutors.diskIO)).subscribe() | |||
| return result | |||
| } | |||
| } | |||
| @@ -308,7 +308,7 @@ class LearnRuleUtil<T : BaseWord> constructor(private val originLearnList : List | |||
| currentCorrectMap[key] = true | |||
| } | |||
| if (isLearnFirst) currentIsError = false | |||
| LogUtil.e("currentCorrectMap--->${currentCorrectMap.size} --> $currentCorrectMap") | |||
| // LogUtil.e("currentCorrectMap--->${currentCorrectMap.size} --> $currentCorrectMap") | |||
| } | |||
| /** | |||
| @@ -326,7 +326,7 @@ class LearnRuleUtil<T : BaseWord> constructor(private val originLearnList : List | |||
| } else if (!isInExamErrorMap(key) && !isInCurrentErrorMap(key)) { //测试中数据的错误 | |||
| currentErrorMap[key] = true | |||
| } | |||
| LogUtil.e("currentErrorMap --->${currentErrorMap.size} $currentErrorMap") | |||
| // LogUtil.e("currentErrorMap --->${currentErrorMap.size} $currentErrorMap") | |||
| //开始进行数据扩容替换 | |||
| replaceErrorToNull(item) //替换已有错误为空 | |||
| insertErrorWord(item, false) //开始插入 | |||
| @@ -346,7 +346,7 @@ class LearnRuleUtil<T : BaseWord> constructor(private val originLearnList : List | |||
| } | |||
| /** 判断源数据中的数据是否已经学习完成:由外部调用,originLearnPosition下标的数据为已经取出来学习的数据,只有为第一个的时候,才判断此方法 */ | |||
| fun isLearnOverForOrigin() : Boolean { | |||
| private fun isLearnOverForOrigin() : Boolean { | |||
| return originLearnPosition >= originLearnList.size - 1 | |||
| } | |||
| } | |||
| @@ -22,7 +22,7 @@ | |||
| android:layout_width="match_parent" | |||
| android:layout_height="30dp" | |||
| android:background="@color/gray_1" | |||
| app:layout_constraintTop_toTopOf="@id/rv_historical_route"/> | |||
| app:layout_constraintTop_toTopOf="@id/rv_historical_route" /> | |||
| <!--历史轨迹--> | |||
| <androidx.recyclerview.widget.RecyclerView | |||
| android:id="@+id/rv_historical_route" | |||
| @@ -31,15 +31,14 @@ | |||
| android:orientation="horizontal" | |||
| android:paddingStart="@dimen/global_spacing" | |||
| android:paddingEnd="@dimen/global_spacing" | |||
| app:layout_constraintTop_toBottomOf="@+id/inc_learn_title" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| tools:reverseLayout="true" | |||
| tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" | |||
| tools:itemCount="1" | |||
| app:layout_constraintHorizontal_bias="0" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/inc_learn_title" | |||
| tools:itemCount="1" | |||
| tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" | |||
| tools:listitem="@layout/item_historical_route" | |||
| /> | |||
| tools:reverseLayout="true" /> | |||
| <TextView | |||
| android:id="@+id/tv_banner" | |||
| @@ -50,8 +49,8 @@ | |||
| android:text="@string/auto_playing" | |||
| android:textColor="@color/theme_color" | |||
| android:textSize="@dimen/smallerSize" | |||
| app:layout_constraintTop_toBottomOf="@+id/rv_historical_route" | |||
| android:visibility="gone"/> | |||
| android:visibility="gone" | |||
| app:layout_constraintTop_toBottomOf="@+id/rv_historical_route" /> | |||
| <!--内容布局--> | |||
| @@ -103,12 +102,11 @@ | |||
| <!--详情--> | |||
| <!-- <include layout="@layout/inc_word_detail" />--> | |||
| <!-- <include layout="@layout/inc_word_detail" />--> | |||
| <!--拼写提示--> | |||
| <!-- <include layout="@layout/inc_spell_learn_tip" />--> | |||
| <!-- <include layout="@layout/inc_spell_learn_tip" />--> | |||
| </FrameLayout> | |||
| <!--总时间--> | |||
| @@ -121,51 +119,65 @@ | |||
| android:textColor="@color/gray_2" | |||
| android:textSize="@dimen/smallerSize" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent"/> | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" /> | |||
| <Button | |||
| <com.google.android.material.button.MaterialButton | |||
| android:id="@+id/tv_play" | |||
| style="@style/Widget.MaterialComponents.Button.UnelevatedButton" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="22dp" | |||
| style="@style/Widget.MaterialComponents.Button.UnelevatedButton" | |||
| tools:text="暂停播放" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| android:layout_marginStart="@dimen/global_spacing" | |||
| android:layout_marginBottom="4dp" | |||
| android:gravity="center" | |||
| android:insetTop="0dp" | |||
| android:insetBottom="0dp" | |||
| android:paddingTop="0dp" | |||
| android:paddingBottom="0dp" | |||
| android:paddingStart="8dp" | |||
| android:paddingEnd="8dp" | |||
| android:textColor="@color/theme_color" | |||
| app:strokeColor = "@color/theme_color" | |||
| app:strokeWidth="@dimen/line_height" | |||
| android:layout_marginBottom="4dp" | |||
| android:layout_marginStart="@dimen/global_spacing" | |||
| android:textSize="@dimen/smallerSize" | |||
| android:visibility="gone" | |||
| app:icon="@drawable/ic_play_pause" | |||
| app:iconGravity="textStart" | |||
| app:iconSize="16dp" | |||
| app:iconTint="@color/theme_color" | |||
| app:rippleColor="@color/white"/> | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:rippleColor="@color/white" | |||
| app:strokeColor="@color/theme_color" | |||
| app:strokeWidth="@dimen/line_height" | |||
| tools:text="暂停播放" | |||
| tools:visibility="visible" /> | |||
| <Button | |||
| <com.google.android.material.button.MaterialButton | |||
| android:id="@+id/tv_play_stop" | |||
| style="@style/Widget.MaterialComponents.Button.UnelevatedButton" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="22dp" | |||
| style="@style/Widget.MaterialComponents.Button.UnelevatedButton" | |||
| tools:text="停止播放" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| android:layout_marginEnd="@dimen/global_spacing" | |||
| android:layout_marginBottom="4dp" | |||
| android:gravity="center" | |||
| android:insetTop="0dp" | |||
| android:insetBottom="0dp" | |||
| android:paddingTop="0dp" | |||
| android:paddingBottom="0dp" | |||
| android:paddingStart="8dp" | |||
| android:paddingEnd="8dp" | |||
| android:textColor="@color/theme_color" | |||
| app:strokeColor = "@color/theme_color" | |||
| app:strokeWidth="@dimen/line_height" | |||
| android:layout_marginBottom="4dp" | |||
| android:layout_marginEnd="@dimen/global_spacing" | |||
| android:textSize="@dimen/smallerSize" | |||
| android:visibility="gone" | |||
| app:icon="@drawable/ic_play_stop" | |||
| app:iconSize="10dp" | |||
| app:iconTint="@color/theme_color" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:rippleColor="@color/white" | |||
| /> | |||
| app:strokeColor="@color/theme_color" | |||
| app:strokeWidth="@dimen/line_height" | |||
| tools:text="停止播放" | |||
| tools:visibility="visible" /> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -45,6 +45,7 @@ | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/img_word" | |||
| app:layout_goneMarginTop="87dp" | |||
| android:visibility="gone" | |||
| tools:visibility="visible" /> | |||
| @@ -72,11 +72,17 @@ | |||
| <string name="phrase">词组</string> | |||
| <string name="example">例句</string> | |||
| <string name="reference">参考</string> | |||
| <string name="auto_playing">自动播放中···</string> | |||
| <string name="auto_playing">自动播放中…</string> | |||
| <string name="auto_play_banner_paused">已暂停</string> | |||
| <string name="auto_play_continue">继续播放</string> | |||
| <string name="auto_play_paused">暂停播放</string> | |||
| <string name="auto_play_stop">停止播放</string> | |||
| <string name="quit_learn_title">你确定要退出本课程的学习吗?</string> | |||
| <string name="quit_learn_content">退出后系统将保存你的学习进度</string> | |||
| <string name="course_relearn_title">你确定要清空本课程的学习记录吗?</string> | |||
| <string name="course_relearn_content">清空后你的学习记录将不可恢复,请谨慎操作</string> | |||
| <string name="course_relearn_sure">确认清空</string> | |||
| <string name="quit_auto_play_title">你确定要退出自动播放吗?</string> | |||
| <string name="quit_auto_play_title_over">本课程自动播放完毕</string> | |||
| </resources> | |||
| @@ -28,7 +28,7 @@ abstract class BaseActivityVM<VB : ViewBinding, VM : BaseViewModel> : BaseActivi | |||
| vm.pageEvent.observe(this) { startActivity(it) } | |||
| vm.toastEvent.observe(this){ showToast(it) } | |||
| vm.loadingEvent.observe(this) { | |||
| LogUtil.e("监听到 activity vm 的 loadingEvent isShow $it") | |||
| // LogUtil.e("监听到 activity vm 的 loadingEvent isShow $it") | |||
| showHideLoading(it)} | |||
| } | |||
| @@ -14,6 +14,7 @@ import androidx.annotation.StringRes | |||
| import androidx.core.content.ContextCompat | |||
| import androidx.core.content.res.ResourcesCompat | |||
| import androidx.core.graphics.drawable.DrawableCompat | |||
| import androidx.core.graphics.drawable.toBitmap | |||
| import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat | |||
| import java.io.ByteArrayOutputStream | |||
| @@ -21,6 +22,7 @@ import java.io.ByteArrayOutputStream | |||
| * author suliang | |||
| * create 2022/3/25 17:08 | |||
| * Describe: | |||
| * Android 中 Bitmap 和 Drawable 相互转换的方法 https://blog.csdn.net/l_lhc/article/details/50923372 | |||
| */ | |||
| class DrawableUti { | |||
| companion object { | |||
| @@ -93,6 +95,17 @@ class DrawableUti { | |||
| return BitmapFactory.decodeByteArray(byteArray,0,byteArray.size) | |||
| } | |||
| fun changeSvgSizeAndColor(resource:Resources,svgId : Int,changeColor : Int,scale:Int):Drawable{ | |||
| val vectorDrawableCompat = VectorDrawableCompat.create(resource, svgId, null)!! | |||
| vectorDrawableCompat.setTint(ResourcesCompat.getColor(resource, changeColor, null)) | |||
| val oldBitmap = vectorDrawableCompat.toBitmap(scale*vectorDrawableCompat.intrinsicWidth,scale*vectorDrawableCompat.intrinsicHeight,Bitmap.Config.ARGB_8888) | |||
| return BitmapDrawable(resource,oldBitmap) | |||
| // val newBitmap = Bitmap.createScaledBitmap(oldBitmap,scale*oldBitmap.width,scale*oldBitmap.height,true) | |||
| } | |||
| } | |||
| } | |||
| @@ -296,9 +296,9 @@ internal class MPUtil : IMP,MediaPlayer.OnPreparedListener,MediaPlayer.OnErrorLi | |||
| val temp = currentState | |||
| synchronized(temp) { | |||
| listener?.let { | |||
| LogUtil.e("MPUtil发送 --》 ${Thread.currentThread()}") | |||
| // LogUtil.e("MPUtil发送 --》 ${Thread.currentThread()}") | |||
| Handler(Looper.getMainLooper()).post { | |||
| LogUtil.e("MPUtil发送 --》 ${temp.name}") | |||
| // LogUtil.e("MPUtil发送 --》 ${temp.name}") | |||
| it.onMpState(temp) | |||
| } | |||
| } | |||