@@ -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) | |||
} | |||
} |