<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_exam_learn_spell.xml" value="0.47690217391304346" /> | |||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_base.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_base.xml" value="0.4979166666666667" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_exam.xml" value="0.23632218844984804" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_exam.xml" value="0.23632218844984804" /> | ||||
<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_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_word2.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_word2.xml" value="0.4979166666666667" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.5" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.5" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> |
课时后测试将包含 小游戏练习、再测一次、重新学习、下一步(进入学后总测试)或者下一课时(为当前课时的下一课时), | 课时后测试将包含 小游戏练习、再测一次、重新学习、下一步(进入学后总测试)或者下一课时(为当前课时的下一课时), | ||||
需要考虑的是,下一步或者下一课时的判断为循环判断,如,共10课时,现在点击学习的第5个,如果后面几个课时都学习完后,而1课时没有学习完成,下一课时将直接进入1课时的学习, | 需要考虑的是,下一步或者下一课时的判断为循环判断,如,共10课时,现在点击学习的第5个,如果后面几个课时都学习完后,而1课时没有学习完成,下一课时将直接进入1课时的学习, | ||||
而下一步是在所有课时都学习完成的情况下,直接进入学后总测试的界面。 | 而下一步是在所有课时都学习完成的情况下,直接进入学后总测试的界面。 | ||||
学前总测试 :不让跳过,必须测,用于进行界面判断 和 统计的判断 | |||||
课时学前测试: 不让跳过,必须测,不然对统计和判断有影响 | |||||
targetSdk androidConfig.target_sdk_version | targetSdk androidConfig.target_sdk_version | ||||
versionCode androidConfig.version_code | versionCode androidConfig.version_code | ||||
versionName androidConfig.version_name | versionName androidConfig.version_name | ||||
multiDexEnabled true //解决64k 分包限制 | |||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
} | } | ||||
/*构建类型: 定义Gradle在构建阶段和打包应用时使用的某些属性*/ | /*构建类型: 定义Gradle在构建阶段和打包应用时使用的某些属性*/ |
<activity | <activity | ||||
android:name=".module.learn.LearnCVideoActivity" | android:name=".module.learn.LearnCVideoActivity" | ||||
android:exported="true" /> | android:exported="true" /> | ||||
<activity | |||||
android:name=".module.learn.LearnExamSpellActivity" | |||||
android:exported="true" /> | |||||
<activity | <activity | ||||
android:name=".module.learn.LearnExamActivity" | android:name=".module.learn.LearnExamActivity" | ||||
android:exported="true" /> | android:exported="true" /> | ||||
<activity | |||||
android:name=".module.learn.LearnSpellActivity" | |||||
android:exported="true" /> | |||||
<activity | <activity | ||||
android:name=".module.learn.LearnWordActivity" | android:name=".module.learn.LearnWordActivity" | ||||
android:exported="true" /> | android:exported="true" /> |
import android.view.ViewGroup | import android.view.ViewGroup | ||||
import android.widget.Toast | import android.widget.Toast | ||||
import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||
import androidx.core.text.isDigitsOnly | |||||
import com.suliang.common.base.adapter.BaseRVAdapter | import com.suliang.common.base.adapter.BaseRVAdapter | ||||
import com.suliang.common.base.adapter.BaseAdapterViewHolder | import com.suliang.common.base.adapter.BaseAdapterViewHolder | ||||
* @param isOver 纠错是否完成 | * @param isOver 纠错是否完成 | ||||
* @param nextPositon 纠错中的下一项 | * @param nextPositon 纠错中的下一项 | ||||
*/ | */ | ||||
lateinit var onItemRecoveryClick : (showValue : SpannableStringBuilder, isOver:Boolean, nextPosition:Int) -> Unit | |||||
lateinit var onItemRecoveryClick : (showValue : SpannableStringBuilder, isOver : Boolean, nextPosition : Int) -> Unit | |||||
/** | /** | ||||
* 拼写时的事件 | * 拼写时的事件 | ||||
* @param correctValue 正确的拼写值,但未选中的赋值了颜色 | * @param correctValue 正确的拼写值,但未选中的赋值了颜色 | ||||
* @param errorSize 错误的个数,拼写完成后才会有这个值 | * @param errorSize 错误的个数,拼写完成后才会有这个值 | ||||
*/ | */ | ||||
lateinit var onItemSpellingClickListener : (selectedValue : SpannableStringBuilder, nextPosition : Int, isOver : Boolean , correctValue : SpannableStringBuilder, errorSize : Int) -> Unit | |||||
lateinit var onItemSpellingClickListener : (selectedValue : SpannableStringBuilder, nextPosition : Int, isOver : Boolean, correctValue : SpannableStringBuilder, errorSize : Int) -> Unit | |||||
override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder { | override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder { | ||||
playItem(item.char) | playItem(item.char) | ||||
item.isSelected = true | item.isSelected = true | ||||
getItem(if (position % 2 == 0) position + 1 else position - 1).isSelected = false | getItem(if (position % 2 == 0) position + 1 else position - 1).isSelected = false | ||||
notifyDataSetChanged() | |||||
notifyUIUpdate(position) | notifyUIUpdate(position) | ||||
notifyDataSetChanged() | |||||
} | } | ||||
} | } | ||||
} else { //不可拼写时 正确项未选中为红色,否则为白色 | } else { //不可拼写时 正确项未选中为红色,否则为白色 | ||||
binding.tv.setBackgroundColor(ContextCompat.getColor(context, | binding.tv.setBackgroundColor(ContextCompat.getColor(context, | ||||
if (item.isCorrect && !item.isSelected) R.color.red_2 else R.color.white)) | |||||
if (item.isCorrect && !item.isSelected) R.color.red_1 else R.color.white)) | |||||
binding.tv.setOnClickListener { | binding.tv.setOnClickListener { | ||||
//非纠错校正则点击无效 | //非纠错校正则点击无效 | ||||
if (!isErrorRecovery) return@setOnClickListener | if (!isErrorRecovery) return@setOnClickListener | ||||
} else { | } else { | ||||
playItem(item.char) | playItem(item.char) | ||||
item.isSelected = true | item.isSelected = true | ||||
notifyItemChanged(position) | |||||
notifyUIUpdate(position) | notifyUIUpdate(position) | ||||
notifyItemChanged(position) | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
*/ | */ | ||||
private fun clickInvalid() { | private fun clickInvalid() { | ||||
Toast.makeText(context, "请依次点击", Toast.LENGTH_SHORT).show() | Toast.makeText(context, "请依次点击", Toast.LENGTH_SHORT).show() | ||||
MPManager.play("common_voice/mistake.mp3") | |||||
MPManager.playAsset("common_voice/mistake.mp3") | |||||
} | } | ||||
/** item 播放 */ | /** item 播放 */ | ||||
private fun playItem(letter : Char) { | private fun playItem(letter : Char) { | ||||
if (letter.isLetter()) { | if (letter.isLetter()) { | ||||
when (defaultSoundWay) { | when (defaultSoundWay) { | ||||
AppConstants.SOUND_TYPE_UK -> MPManager.play("common_voice_uk/${letter}_uk.mp3") | |||||
AppConstants.SOUND_TYPE_US -> MPManager.play("common_voice_us/${letter}_us.mp3") | |||||
AppConstants.SOUND_TYPE_UK -> MPManager.playAsset("common_voice_uk/${letter}_uk.mp3") | |||||
AppConstants.SOUND_TYPE_US -> MPManager.playAsset("common_voice_us/${letter}_us.mp3") | |||||
} | } | ||||
} else { | } else { | ||||
MPManager.play("common_voice/konge.mp3") | |||||
MPManager.playAsset("common_voice/konge.mp3") | |||||
} | } | ||||
} | } | ||||
//第二列 position为 1 3 5 7 9 | //第二列 position为 1 3 5 7 9 | ||||
if (position == 0 || position == 1) return true | if (position == 0 || position == 1) return true | ||||
//上一列的第一行坐标 | //上一列的第一行坐标 | ||||
val previousColumnFirstPosition = if (position % 2 == 0) position - 2 else position - 1 | |||||
val previousColumnFirstPosition = if (position % 2 == 0) position - 2 else position - 3 | |||||
if (getItem(previousColumnFirstPosition).isSelected) return true | if (getItem(previousColumnFirstPosition).isSelected) return true | ||||
//上一列的第二行坐标 | //上一列的第二行坐标 | ||||
val previousColumnSecondPosition = previousColumnFirstPosition + 1 | val previousColumnSecondPosition = previousColumnFirstPosition + 1 | ||||
//是否是最后一列 | //是否是最后一列 | ||||
val isOver = (position == itemCount - 1) || (position == itemCount - 2) | val isOver = (position == itemCount - 1) || (position == itemCount - 2) | ||||
var errorSize = 0 | var errorSize = 0 | ||||
var nextPosition = 0 | |||||
var nextPosition = 0 //下一列的第一行坐标位置 | |||||
//记录正确的值,如果没有选中,则需要设置颜色 | //记录正确的值,如果没有选中,则需要设置颜色 | ||||
val correctValue = SpannableStringBuilder() | val correctValue = SpannableStringBuilder() | ||||
if (!isOver) { | if (!isOver) { | ||||
//拼写时,直接赋值 | //拼写时,直接赋值 | ||||
getData().forEachIndexed { index, it -> | getData().forEachIndexed { index, it -> | ||||
if (it.isSelected) builder.append(it.char) | if (it.isSelected) builder.append(it.char) | ||||
if (it.isCorrect && (index == position + 1 || index == position + 2)) nextPosition = index | |||||
//滑动到下一列的位置 | |||||
if (position == index) { //点击的位置 | |||||
if (position % 2 == 0) { | |||||
nextPosition = position + 2 | |||||
} else { | |||||
nextPosition = position + 1 | |||||
} | |||||
} | |||||
} | } | ||||
} else { //拼写完成 | } else { //拼写完成 | ||||
getData().forEachIndexed { index, it -> | getData().forEachIndexed { index, it -> | ||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | ||||
}) | }) | ||||
//错误数加1 | //错误数加1 | ||||
errorSize+=1 | |||||
//下一个位置定义到第一个错误的位置 | |||||
if (nextPosition == 0 ){ | |||||
nextPosition = index | |||||
} | |||||
errorSize += 1 | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
isSpelling = false | |||||
} | } | ||||
//回调出去 | //回调出去 | ||||
onItemSpellingClickListener(builder,nextPosition,isOver,correctValue,errorSize) | |||||
onItemSpellingClickListener(builder, nextPosition, isOver, correctValue, errorSize) | |||||
} | } | ||||
//纠错: 拼接所有正确的内容,未选中的需要设置颜色 | //纠错: 拼接所有正确的内容,未选中的需要设置颜色 | ||||
//判断是否还有大于当前列第二行坐标的未选,如果有,则为纠错未完成 | //判断是否还有大于当前列第二行坐标的未选,如果有,则为纠错未完成 | ||||
if (index > currentSecondPosition) { | if (index > currentSecondPosition) { | ||||
isOver = false | isOver = false | ||||
nextPosition = index | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
onItemRecoveryClick(builder, isOver,nextPosition) | |||||
onItemRecoveryClick(builder, isOver, nextPosition) | |||||
} | |||||
} | |||||
/** 测试时倒计时结束,进行取值结果回调 */ | |||||
fun testCountingTimeOver() { | |||||
val builder = SpannableStringBuilder() | |||||
val correctValue = SpannableStringBuilder() | |||||
var errorSize = 0 | |||||
getData().forEachIndexed { index, it -> | |||||
//记录拼写的值 | |||||
if (it.isSelected) builder.append(it.char) | |||||
//记录正确的值 | |||||
when { | |||||
it.isCorrect -> when { | |||||
it.isSelected -> correctValue.append(it.char) | |||||
else -> { | |||||
correctValue.append(SpannableString(it.char.toString()).apply { | |||||
setSpan(ForegroundColorSpan(ContextCompat.getColor(context, R.color.theme_color)), | |||||
0, | |||||
1, | |||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | |||||
}) | |||||
//错误数加1 | |||||
errorSize += 1 | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
isSpelling = false | |||||
onItemSpellingClickListener(builder, 0, true, correctValue, errorSize) | |||||
notifyDataSetChanged() | |||||
} | } | ||||
} | } |
package com.xkl.cdl.adapter.itemdecoration | package com.xkl.cdl.adapter.itemdecoration | ||||
import android.content.Context | |||||
import android.graphics.Rect | import android.graphics.Rect | ||||
import android.view.View | import android.view.View | ||||
import com.suliang.common.util.os.ScreenUtil | |||||
import androidx.recyclerview.widget.RecyclerView.ItemDecoration | |||||
import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||
import androidx.recyclerview.widget.RecyclerView.ItemDecoration | |||||
import com.suliang.common.util.os.ScreenUtil | |||||
/** | /** | ||||
* Created by su on 2018/11/26. | * Created by su on 2018/11/26. | ||||
*/ | */ | ||||
class SpellItemDecoration : ItemDecoration() { | class SpellItemDecoration : ItemDecoration() { | ||||
private var mDividerWidth = ScreenUtil.dp2px(1f) | |||||
private val mDividerWidth = ScreenUtil.dp2px(1f) | |||||
override fun getItemOffsets(outRect : Rect, view : View, parent : RecyclerView, state : RecyclerView.State) { | override fun getItemOffsets(outRect : Rect, view : View, parent : RecyclerView, state : RecyclerView.State) { | ||||
val space = mDividerWidth/2 | |||||
val chilidCount = parent.adapter!!.itemCount | val chilidCount = parent.adapter!!.itemCount | ||||
val position = (view.layoutParams as RecyclerView.LayoutParams).viewLayoutPosition | |||||
//获取view所在的位置(从0计数) | |||||
val position = parent.getChildAdapterPosition(view) | |||||
var left = 0 | var left = 0 | ||||
var top = 0 | |||||
var right = 0 | |||||
var top = 0 | |||||
var right = 0 | |||||
var bottom = 0 | var bottom = 0 | ||||
when{ | when{ | ||||
position % 2 == 0 -> { //第一行 | |||||
position % 2 == 0 -> { //第一行 | |||||
left = mDividerWidth | |||||
top = mDividerWidth | top = mDividerWidth | ||||
bottom = space | |||||
when{ | |||||
position == 0 && position == chilidCount - 2 -> { //只有一列 | |||||
left = mDividerWidth | |||||
right = mDividerWidth | |||||
} | |||||
position == 0 -> { //第一列 | |||||
left = mDividerWidth | |||||
right = space | |||||
} | |||||
position == chilidCount - 2 -> { //最后一列 | |||||
left = space | |||||
right = mDividerWidth | |||||
} | |||||
else -> { | |||||
left = space | |||||
right = space | |||||
} | |||||
} | |||||
bottom = mDividerWidth | |||||
right = 0 | |||||
} | } | ||||
else -> { //第二行 | |||||
top = space | |||||
else -> { //第二行 | |||||
left = mDividerWidth | |||||
top = 0 | |||||
bottom = mDividerWidth | bottom = mDividerWidth | ||||
when{ | |||||
position == 1 && position == chilidCount - 1 -> { //只有一列 | |||||
left = mDividerWidth | |||||
right = mDividerWidth | |||||
} | |||||
position == 1 -> { //第一列 | |||||
left = mDividerWidth | |||||
right = space | |||||
} | |||||
position == chilidCount - 2 -> { //最后一列 | |||||
left = space | |||||
right = mDividerWidth | |||||
} | |||||
else -> { | |||||
left = space | |||||
right = space | |||||
} | |||||
} | |||||
right = 0 | |||||
} | } | ||||
} | } | ||||
//最后一列 | |||||
if (position == chilidCount - 1 || position == chilidCount - 2){ | |||||
right = mDividerWidth | |||||
} | |||||
outRect.set(left,top,right, bottom) | outRect.set(left,top,right, bottom) | ||||
} | } | ||||
/**测试错误: 未答到一题的时间*/ | /**测试错误: 未答到一题的时间*/ | ||||
const val TEST_TO_NEXT_ERROR_TIME = 2000L | const val TEST_TO_NEXT_ERROR_TIME = 2000L | ||||
/** 对话框类型: 测试 */ | |||||
const val DIALOG_TYPE_EXAM = 1 | |||||
/** 对话框类型: 学习 */ | |||||
const val DIALOG_TYPE_LEARN = 2 | |||||
/** 对话框类型: 测试开始弹窗 与 结束弹窗 */ | |||||
const val DIALOG_TYPE_EXAM_START = 1 | |||||
const val DIALOG_TYPE_EXAM_OVER = 2 | |||||
/** 对话框类型: 学习结束弹窗类型 */ | |||||
const val DIALOG_TYPE_LEARN_OVER = 3 | |||||
/**--- 总线动作 --------------------------------- */ | /**--- 总线动作 --------------------------------- */ | ||||
/**action key 改变界面 到目录页 */ | /**action key 改变界面 到目录页 */ | ||||
const val EVENT_COURSE = "action_change_page" | const val EVENT_COURSE = "action_change_page" | ||||
/** 动作:学前总测之 开始学习 */ | |||||
/** 事件动作:学前总测结束弹窗之 开始学习 */ | |||||
const val ACTION_COURSE_TEST_START_LEARN = 1 | const val ACTION_COURSE_TEST_START_LEARN = 1 | ||||
/** 数据动作:学前总测结束传递数据 */ | /** 数据动作:学前总测结束传递数据 */ | ||||
const val DATA_COURSE_TEST_BEFORE = 2 | |||||
const val DATA_COURSE_BEFORE_TEST_OVER = 2 | |||||
/** 数据动作:课时学前测试结束传递数据 */ | |||||
const val DATA_LESSON_BEFORE_TEST_OVER = 3 | |||||
/** 课时学前测试结束 : 开始学习 */ | |||||
const val ACTION_LESSON_BEFORE_TEST_OVER_START_LEARN = 4 | |||||
/**数据动作: 课时学后测试结束传递数据*/ | |||||
const val DATA_LESSON_AFTER_TEST_OVER = 5 | |||||
/**课时学后测试结束发送动作: 重新学习*/ | |||||
const val ACTION_LESSON_AFTER_TEST_RELEARN = 6 | |||||
/**课时学后测试弹窗动作: 再测一次*/ | |||||
const val ACTION_LESSON_AFTER_TEST_AGAIN = 7 | |||||
/**课时学后测试弹窗动作: 下一步*/ | |||||
const val ACTION_LESSON_AFTER_TEST_NEXT = 8 | |||||
/**--- 弹窗动作 --------------------------------- */ | /**--- 弹窗动作 --------------------------------- */ | ||||
/** 学前总测弹窗: 开始学习 */ | |||||
/** 学前总测结束弹窗: 开始学习 ,课时学前测试开始弹窗*/ | |||||
const val DIALOG_START_LEARN = 1 | const val DIALOG_START_LEARN = 1 | ||||
/** 弹窗按钮动作:开始测试 */ | |||||
const val DIALOG_START_TEST = 2 | |||||
/**课时学后测试弹窗动作: 重新学习*/ | |||||
const val DIALOG_LESSON_AFTER_TEST_RELEARN = 3 | |||||
/**课时学后测试弹窗动作: 再测一次*/ | |||||
const val DIALOG_LESSON_AFTER_TEST_AGAIN = 4 | |||||
/**课时学后测试弹窗动作: 下一步*/ | |||||
const val DIALOG_LESSON_AFTER_TEST_NEXT = 5 | |||||
} | } |
package com.xkl.cdl.data.bean | |||||
import com.xkl.cdl.data.bean.course.Lesson | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/2 14:55 | |||||
* Describe: 课时学习时的传参 | |||||
*/ | |||||
class IntentLearnData(val lesson: Lesson) { | |||||
} |
* author suliang | * author suliang | ||||
* create 2022/4/14 9:37 | * create 2022/4/14 9:37 | ||||
* Describe: 学习测试对话框--传参实体 | * Describe: 学习测试对话框--传参实体 | ||||
* | |||||
* 所有的测试必传测试类型 [examType] | * 所有的测试必传测试类型 [examType] | ||||
* | * | ||||
* 学前总测 :[examType],[score],[correctNumber],[errorNumber] | |||||
* 学前总测结束 :[examType],[score],[correctNumber],[errorNumber] | |||||
* | |||||
* 课时学前测试开始: [examType],[chapter_lesson_name],[showTimeCount] | |||||
* | * | ||||
* 课时学前测试 | |||||
* 课时学前测试结束:[examType],[score],[correctNumber],[errorNumber] | |||||
* | * | ||||
* 课时学后测试 | * 课时学后测试 | ||||
* | * | ||||
/** 错误数据 */ | /** 错误数据 */ | ||||
var errorNumber : Int = 0 | var errorNumber : Int = 0 | ||||
/**章节课时名称: 第一章 第二课时 */ | |||||
var chapter_lesson_name = "" | |||||
/** 题数与时间 */ | |||||
var showTimeCount = "" | |||||
package com.xkl.cdl.data.bean | package com.xkl.cdl.data.bean | ||||
import com.xkl.cdl.data.bean.BaseWord | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
* create 2022/3/31 17:52 | * create 2022/3/31 17:52 | ||||
var phonetic_uk: String? = null // 英式音标 | var phonetic_uk: String? = null // 英式音标 | ||||
var phonetic_us: String? = null // 美式音标 | var phonetic_us: String? = null // 美式音标 | ||||
var phonetic_cn: String? = null // 中文拼音 | var phonetic_cn: String? = null // 中文拼音 | ||||
var photo: String? = null // 图片 | |||||
var audio: String? = null // 音频 | |||||
// var photo: ByteArray? = null // 图片 | |||||
var basic_explanation: String? = null //基本释义 | var basic_explanation: String? = null //基本释义 | ||||
var all_explanation: String? = null //扩展释义 | |||||
var extend_explanation: String? = null //扩展释义 | |||||
var phrase: String? = null //词组 | var phrase: String? = null //词组 | ||||
var example: String? = null //例句 | var example: String? = null //例句 | ||||
var reference: String? = null //参考 | var reference: String? = null //参考 | ||||
var literacyIspolyphone = false //识字课程,是否是多音字标识 | var literacyIspolyphone = false //识字课程,是否是多音字标识 | ||||
var pattern: String? = null //口语: 语法 | var pattern: String? = null //口语: 语法 | ||||
} | } |
package com.xkl.cdl.data.bean.course | package com.xkl.cdl.data.bean.course | ||||
import android.R.bool | |||||
/** | /** | ||||
* author suliang | * author suliang |
var chapterId : Long = 0 //章节id | var chapterId : Long = 0 //章节id | ||||
var lessonId : Long = 0 //课时id | var lessonId : Long = 0 //课时id | ||||
//口语 | |||||
var questionIsAudio = false //问题是否是音频 | var questionIsAudio = false //问题是否是音频 | ||||
var answersIsAudio = false //答案是否是音频 | var answersIsAudio = false //答案是否是音频 | ||||
//口语总测的发音 | |||||
var wordAudioUk : String = "" | |||||
var wordAudioUs : String = "" | |||||
var correctAudioUk : String = "" | |||||
var correctAudioUs : String = "" | |||||
var error1AudioUk : String = "" | |||||
var error1AudioUs : String = "" | |||||
var error2AudioUk : String = "" | |||||
var error2AudioUs : String = "" | |||||
var error3AudioUk : String = "" | |||||
var error3AudioUs : String = "" | |||||
} | } |
data class Lesson( | data class Lesson( | ||||
val subjectId: Int, | val subjectId: Int, | ||||
val coursePackId:Long, | val coursePackId:Long, | ||||
val coursePackType:Int, | |||||
val courseId:Long, | val courseId:Long, | ||||
var courseType: Int, | var courseType: Int, | ||||
val chapterId:Long, | val chapterId:Long, | ||||
var lessonPositionInList : Int = 0 | var lessonPositionInList : Int = 0 | ||||
/** 该课时的总数据 或者作文关联的数据,如视频关联的内容*/ | /** 该课时的总数据 或者作文关联的数据,如视频关联的内容*/ | ||||
var wordIds = mutableListOf<Long>() | var wordIds = mutableListOf<Long>() | ||||
/** 学习进度位置,为医学下标的位置,在作文课堂练习时,学习取当前条,其他取下一条*/ | |||||
var learnedIndex: Int = 0 | |||||
/** 学习进度位置,为已学下标的位置,在作文课堂练习时,学习取当前条,其他取下一条*/ | |||||
var learnedIndex: Int = -1 | |||||
/**课时学前测试成绩 */ | /**课时学前测试成绩 */ | ||||
var beforeTestScore = -1.0 | var beforeTestScore = -1.0 | ||||
/** 课时学后测试 */ | /** 课时学后测试 */ |
package com.xkl.cdl.data.bean.intentdata | package com.xkl.cdl.data.bean.intentdata | ||||
import com.xkl.cdl.data.bean.course.DialogueSpokenItem | |||||
import com.xkl.cdl.data.bean.course.ExamBean | import com.xkl.cdl.data.bean.course.ExamBean | ||||
import com.xkl.cdl.data.bean.course.Lesson | import com.xkl.cdl.data.bean.course.Lesson | ||||
import kotlin.collections.HashMap | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
* create 2022/4/8 12:01 | * create 2022/4/8 12:01 | ||||
* Describe: 测试传递数据 | * Describe: 测试传递数据 | ||||
* | |||||
* 学前总测: coursePackType, courseType, testData ,mExamWRMap | * 学前总测: coursePackType, courseType, testData ,mExamWRMap | ||||
* | |||||
* 学后总测: coursePackType, courseType, testData | * 学后总测: coursePackType, courseType, testData | ||||
* | |||||
* 课时学前测: coursePackType, courseType, testData ,mExamWRMap,lesson | * 课时学前测: coursePackType, courseType, testData ,mExamWRMap,lesson | ||||
* | |||||
* 课时学后测: coursePackType, courseType, testData ,lesson. | * 课时学后测: coursePackType, courseType, testData ,lesson. | ||||
* | * | ||||
*/ | */ | ||||
val subjectId : Int, //项目类型 | val subjectId : Int, //项目类型 | ||||
val examType : Int, //测试类型 | val examType : Int, //测试类型 | ||||
val showTitle : String, //去测试本地显示名称,学前学后 为课程名称,课时前、后为课时名称 | val showTitle : String, //去测试本地显示名称,学前学后 为课程名称,课时前、后为课时名称 | ||||
val saveTitle : String, //测试试卷上传名称 : 学习中心:课程名称 + “ ” + 学习类型 课程测试:课程名称 + “ ” 学习类型 + (章节拼接名称) 词汇量测试:词汇量测试:阶段名称 | |||||
//测试试卷上传名称 : 学习中心:“课程名称 章节名称 课时名称” | |||||
// 课程测试:课程名称 + “ ” 学习类型 + (章节拼接名称) | |||||
// 词汇量测试:词汇量测试:阶段名称 | |||||
val saveTitle : String, | |||||
) { | ) { | ||||
var coursePackId : Long = 0 //课程包id, 测试错误上次数据需要 | var coursePackId : Long = 0 //课程包id, 测试错误上次数据需要 | ||||
var coursePackType:Int = 0 //课程包类型 | var coursePackType:Int = 0 //课程包类型 |
package com.xkl.cdl.data.bean.intentdata | |||||
import com.xkl.cdl.data.bean.LearnWord | |||||
import com.xkl.cdl.data.bean.course.Lesson | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/2 14:55 | |||||
* Describe: 课时学习时的传参 | |||||
*/ | |||||
class LearnData(val lesson: Lesson) { | |||||
/**学习数据*/ | |||||
var learnWordList : List<LearnWord> = mutableListOf() | |||||
} |
* Describe: 事件通知更新实体封装 | * Describe: 事件通知更新实体封装 | ||||
* @param subjectId 项目id | * @param subjectId 项目id | ||||
* @param courseId 课程id | * @param courseId 课程id | ||||
* @param actionFlag 动作id 动作 或 传递数据 | |||||
* @param actionFlag 动作id 动作(action开头) 或 传递数据(data开头) | |||||
*/ | */ | ||||
class LearnEventData(val subjectId : Int, var courseId : Long, val actionFlag : Int) { | class LearnEventData(val subjectId : Int, var courseId : Long, val actionFlag : Int) { | ||||
var scoreValue = 0 //分数 | var scoreValue = 0 //分数 | ||||
var newErrorMap : HashMap<String, Boolean>? = null //新错误单词列表 | var newErrorMap : HashMap<String, Boolean>? = null //新错误单词列表 | ||||
//课时学前测试 除了学前总测传递数据,还需要课时位置数据, | |||||
var leesonPositionIndex = 0 // lesson所在位置 | |||||
} | } |
import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||
import android.widget.Toast | import android.widget.Toast | ||||
import androidx.core.database.getBlobOrNull | |||||
import androidx.core.database.getLongOrNull | import androidx.core.database.getLongOrNull | ||||
import androidx.core.database.getStringOrNull | |||||
import com.google.common.base.Joiner | |||||
import com.suliang.common.AppConfig | |||||
import com.suliang.common.util.AppGlobals | import com.suliang.common.util.AppGlobals | ||||
import com.suliang.common.util.file.FileUtil | |||||
import com.suliang.common.util.thread.AppExecutors | import com.suliang.common.util.thread.AppExecutors | ||||
import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
import com.xkl.cdl.data.bean.LearnWord | |||||
import com.xkl.cdl.data.bean.course.CourseDetail | import com.xkl.cdl.data.bean.course.CourseDetail | ||||
import com.xkl.cdl.data.bean.course.ExamBean | import com.xkl.cdl.data.bean.course.ExamBean | ||||
import com.xkl.cdl.data.bean.course.Lesson | import com.xkl.cdl.data.bean.course.Lesson | ||||
import com.xkl.cdl.data.manager.FilePathManager | import com.xkl.cdl.data.manager.FilePathManager | ||||
import io.reactivex.rxjava3.annotations.NonNull | |||||
import net.sqlcipher.database.SQLiteDatabase | import net.sqlcipher.database.SQLiteDatabase | ||||
import java.io.File | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
AppConstants.COURSE_TYPE_CHINESE_LITERACY -> LITERACY | AppConstants.COURSE_TYPE_CHINESE_LITERACY -> LITERACY | ||||
else -> NORMAL | else -> NORMAL | ||||
} | } | ||||
mDataBase = SQLiteDatabase.openDatabase( | |||||
FilePathManager.getCourseDbPath(currentBase).path, p, null, SQLiteDatabase.OPEN_READONLY | |||||
) | |||||
mDataBase = SQLiteDatabase.openDatabase(FilePathManager.getCourseDbPath(currentBase).path, | |||||
p, | |||||
null, | |||||
SQLiteDatabase.OPEN_READONLY) | |||||
} | } | ||||
if (mDataBase == null) { | if (mDataBase == null) { | ||||
AppExecutors.mainThread.run { | AppExecutors.mainThread.run { | ||||
Toast.makeText( | |||||
AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG | |||||
) | |||||
Toast.makeText(AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG) | |||||
} | } | ||||
} | } | ||||
} | } | ||||
val chapterId : Long = it.getLong(it.getColumnIndex("chapter_id")) | val chapterId : Long = it.getLong(it.getColumnIndex("chapter_id")) | ||||
val chapterName : String = it.getString(it.getColumnIndex("chapter_title")) | val chapterName : String = it.getString(it.getColumnIndex("chapter_title")) | ||||
val lessonId = it.getLong(it.getColumnIndex("lesson_id")) | val lessonId = it.getLong(it.getColumnIndex("lesson_id")) | ||||
val lessonName = when (base.courseType) { | |||||
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("lesson_title")) | |||||
val lessonName : String = when (base.courseType) { | |||||
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION, AppConstants.COURSE_TYPE_CHINESE_LITERACY, AppConstants.COURSE_TYPE_CHINESE_PINYIN -> it.getString( | |||||
it.getColumnIndex("lesson_title")) | |||||
else -> it.getString(it.getColumnIndex("lesson")) | else -> it.getString(it.getColumnIndex("lesson")) | ||||
} | } | ||||
val wordIds = when (base.courseType) { | |||||
val wordIds : MutableList<Long> = when (base.courseType) { | |||||
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("relation_id")).split(",") | AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("relation_id")).split(",") | ||||
else -> it.getString(it.getColumnIndex("wordIds")).split(",") | else -> it.getString(it.getColumnIndex("wordIds")).split(",") | ||||
}.map { value -> value.toLong() }.toMutableList() | }.map { value -> value.toLong() }.toMutableList() | ||||
val lessonType : Int = when (base.courseType) { | val lessonType : Int = when (base.courseType) { | ||||
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getInt(it.getColumnIndex("type")) | AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getInt(it.getColumnIndex("type")) | ||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> it.getInt(it.getColumnIndex("lesson_type")) | AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> it.getInt(it.getColumnIndex("lesson_type")) | ||||
} | } | ||||
val key = "${chapterId}_${lessonId}" | val key = "${chapterId}_${lessonId}" | ||||
val learnIndex = wordIds.indexOf( | |||||
detail.lesson_learn_point.getOrElse(key, { -1 }) | |||||
) //学习位置,当前位置为已学 | |||||
val learnIndex = wordIds.indexOf(detail.lesson_learn_point.getOrElse(key, { -1 })) //学习位置,当前位置为已学 | |||||
// 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 | // 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 | ||||
val learnIsOver = wordIds.size - 1 == learnIndex | val learnIsOver = wordIds.size - 1 == learnIndex | ||||
val lesson = Lesson( | |||||
base.subjectId, | |||||
base.coursePackId, | |||||
base.courseId, | |||||
base.courseType, | |||||
chapterId, | |||||
chapterName, | |||||
lessonId, | |||||
lessonName).apply { | |||||
val lesson = Lesson(base.subjectId, | |||||
base.coursePackId, | |||||
base.coursePackType, | |||||
base.courseId, | |||||
base.courseType, | |||||
chapterId, | |||||
chapterName, | |||||
lessonId, | |||||
lessonName).apply { | |||||
lessonPositionInList = positionIndex | lessonPositionInList = positionIndex | ||||
this.wordIds = wordIds //内容 | this.wordIds = wordIds //内容 | ||||
totalNumber = this.wordIds.size //总数 | totalNumber = this.wordIds.size //总数 | ||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { //口语 | AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { //口语 | ||||
when (testType) { | when (testType) { | ||||
AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> { //总测 | AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> { //总测 | ||||
"SELECT * FROM exam WHERE type = 5 ORDER by random() LIMIT $count" | |||||
// "SELECT * FROM exam WHERE type = 5 ORDER by random() LIMIT $count" | |||||
"SELECT * FROM exam JOIN exam_res on exam.type = 5 AND exam.word_id = exam_res.exam_spoken_id LIMIT $count" | |||||
} | } | ||||
AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> { | AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> { | ||||
"SELECT exam.* FROM chapter JOIN exam on chapter.word_id = exam.word_id AND exam.type = 1 AND chapter_id = ${lesson!!.chapterId} AND lesson_id = ${lesson.lessonId} ORDER by random() LIMIT $count" | "SELECT exam.* FROM chapter JOIN exam on chapter.word_id = exam.word_id AND exam.type = 1 AND chapter_id = ${lesson!!.chapterId} AND lesson_id = ${lesson.lessonId} ORDER by random() LIMIT $count" | ||||
else -> "" //没有任何课程,课程都特别写入类型,用于匹配 | else -> "" //没有任何课程,课程都特别写入类型,用于匹配 | ||||
} | } | ||||
mDataBase?.rawQuery( | |||||
sql, null | |||||
)?.run { | |||||
mDataBase?.rawQuery(sql, null)?.run { | |||||
when (base.courseType) { | when (base.courseType) { | ||||
AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE, | AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE, | ||||
AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_CHINESE_LITERACY, | AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_CHINESE_LITERACY, | ||||
} | } | ||||
} | } | ||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { | AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { | ||||
while (moveToNext()) { | |||||
result.add(ExamBean().apply { | |||||
id = getLong(0) | |||||
word_id = getLong(1) | |||||
word = getString(2) | |||||
correct = getString(3) | |||||
error1 = getString(4) | |||||
error2 = getString(5) | |||||
error3 = getString(6) | |||||
type = AppConstants.TEST_QUEST_TYPE_CHOICE // 为1 | |||||
lesson?.let { //课时测试时才会有课时存在 | |||||
chapterId = it.chapterId | |||||
lessonId = it.lessonId | |||||
when (testType) { | |||||
AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> { | |||||
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||||
while (moveToNext()) { | |||||
result.add(ExamBean().apply { | |||||
id = getLong(0) | |||||
word_id = getLong(1) | |||||
word = getString(2) | |||||
correct = getString(3) | |||||
error1 = getString(4) | |||||
error2 = getString(5) | |||||
error3 = getString(6) | |||||
questionIsAudio = getInt(7) > 0 | |||||
answersIsAudio = getInt(8) > 0 | |||||
type = 5 // 口语总测 type=5 | |||||
//总测时将发音字节码写入文件 | |||||
if (!File(parentPath, word).exists()) { //不存在 | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(12)) | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(13)) | |||||
} | |||||
if (!File(parentPath, correct).exists()) { //不存在 | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(14)) | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(15)) | |||||
} | |||||
if (!File(parentPath, error1).exists()) { //不存在 | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(16)) | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(17)) | |||||
} | |||||
if (!File(parentPath, error2).exists()) { //不存在 | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(18)) | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(19)) | |||||
} | |||||
if (!File(parentPath, error3).exists()) { //不存在 | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(20)) | |||||
FileUtil.writeBytesToFile(parentPath, word, getBlob(21)) | |||||
} | |||||
}) | |||||
} | } | ||||
}) | |||||
} | |||||
else -> { | |||||
while (moveToNext()) { | |||||
result.add(ExamBean().apply { | |||||
id = getLong(0) | |||||
word_id = getLong(1) | |||||
word = getString(2) | |||||
correct = getString(3) | |||||
error1 = getString(4) | |||||
error2 = getString(5) | |||||
error3 = getString(6) | |||||
type = AppConstants.TEST_QUEST_TYPE_CHOICE // 为1 | |||||
lesson?.let { //课时测试时才会有课时存在 | |||||
chapterId = it.chapterId | |||||
lessonId = it.lessonId | |||||
} | |||||
}) | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return result | return result | ||||
} | } | ||||
/** 获取指定课时(指定数据源wordIds)的随机个数测试数据 */ | |||||
fun queryLessonTestRandomSize( | |||||
coursePackId : Long, | |||||
courseId : Long, | |||||
chapterId : Long, | |||||
lessonId : Long, | |||||
courseType : Long, | |||||
limitSize : Int, | |||||
wordIds : ArrayList<Long>? = null, | |||||
) { | |||||
/** | |||||
* 查询单词的发音音频 | |||||
* @param base DbControlBase | |||||
* @param wordId Long 查询单词 | |||||
* @param soundWay Int 发音方式 | |||||
* @return String 发音保存的文件名称 要不为空,要不为文件地址, 为空,则表明没有发音文件,如果是英美发音方式,则表示英美都没有发音内容 | |||||
*/ | |||||
fun queryAudio(base : DbControlBase, wordId : Long, soundWay : Int) : String? { | |||||
return when (base.courseType) { | |||||
// 有英美两种发音 | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, //这里的为口语的正常获取 | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL, AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_ENGLISH_VOICE -> { | |||||
val sql = "SELECT audio_us,audio_uk FROM res WHERE word_id = $wordId" | |||||
var audio_us : ByteArray? = null | |||||
var audio_uk : ByteArray? = null | |||||
open(base) | |||||
mDataBase?.rawQuery(sql, null)?.let { | |||||
while (it.moveToNext()) { | |||||
//写入文件 | |||||
audio_us = it.getBlobOrNull(0) | |||||
audio_uk = it.getBlobOrNull(1) | |||||
} | |||||
it.close() | |||||
} | |||||
//不为空,写入本身,如果为空,用另外的发音方式写入 | |||||
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||||
val audio_us_file_path = audio_us?.let { | |||||
val audioFileNameUS = | |||||
"${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_US}" | |||||
val file = File(parentPath, audioFileNameUS) | |||||
FileUtil.writeBytesToFile(file, it) | |||||
file.path | |||||
} ?: audio_uk?.let { | |||||
val audioFileNameUS = | |||||
"${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_US}" | |||||
val file = File(parentPath, audioFileNameUS) | |||||
FileUtil.writeBytesToFile(file, it) | |||||
file.path | |||||
} | |||||
//不为空,写入本身,如果为空,用另外的发音方式写入 | |||||
val audio_uk_file_path = audio_uk?.let { | |||||
val audioFileNameUk = | |||||
"${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||||
val file = File(parentPath, audioFileNameUk) | |||||
FileUtil.writeBytesToFile(file, it) | |||||
file.path | |||||
} ?: audio_us?.let { | |||||
val audioFileNameUk = | |||||
"${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||||
val file = File(parentPath, audioFileNameUk) | |||||
FileUtil.writeBytesToFile(file, it) | |||||
file.path | |||||
} | |||||
if (soundWay == AppConstants.SOUND_TYPE_UK) audio_uk_file_path else audio_us_file_path | |||||
} | |||||
//只有一种中文发音 | |||||
AppConstants.COURSE_TYPE_CHINESE_LITERACY, AppConstants.COURSE_TYPE_CHINESE_PINYIN -> { | |||||
var audio_cn_file_path : String? = null | |||||
val sql = "SELECT audio FROM res WHERE word_id = $wordId" | |||||
open(base) | |||||
mDataBase?.rawQuery(sql, null)?.let { | |||||
while (it.moveToNext()) { | |||||
//写入文件 | |||||
audio_cn_file_path = it.getBlobOrNull(0)?.let { | |||||
val audioFileNameCN = | |||||
"${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_CN}" | |||||
val file = File(FileUtil.getSaveDirPath(AppConfig.VOICE), audioFileNameCN) | |||||
FileUtil.writeBytesToFile(file, it) | |||||
file.path | |||||
} | |||||
} | |||||
it.close() | |||||
} | |||||
audio_cn_file_path | |||||
} | |||||
//作文知识点没有发音 | |||||
else -> null | |||||
} | |||||
} | } | ||||
/** 获取课时学习内容 */ | |||||
fun queryLessonLearnData( | |||||
coursePackId : Long, | |||||
courseId : Long, | |||||
chapterId : Long, | |||||
lessonId : Long, | |||||
wordIds : ArrayList<Long>, | |||||
) { | |||||
/** | |||||
* 查询学习数据 | |||||
* 适用课程类型: 单词课程、口语词汇、口语句型语法、音标、识字、拼音 | |||||
* @param dbControlBase DbControlBase | |||||
* @param lesson Lesson | |||||
* @return List<LearnWord>? | |||||
*/ | |||||
@SuppressLint("Range") | |||||
fun queryLearnDataForWord(dbcb : DbControlBase, lesson : Lesson) : @NonNull List<LearnWord> { | |||||
val result = mutableListOf<LearnWord>() | |||||
open(dbcb) | |||||
//从lesson已学位置开始获取数据 | |||||
val needLearnIds = lesson.wordIds.subList(lesson.learnedIndex + 1, lesson.wordIds.size) | |||||
val sql = | |||||
"SELECT * FROM chapter WHERE chapter_id = ${lesson.chapterId} and lesson_id = ${lesson.lessonId} AND word_id in (${ | |||||
Joiner.on(",").join(needLearnIds) | |||||
}) " + "ORDER by word_sort ASC" | |||||
mDataBase?.rawQuery(sql, null)?.run { | |||||
while (moveToNext()) { | |||||
//单词id | |||||
val word_id = getLong(getColumnIndex("word_id")) | |||||
//学习单词实体 | |||||
result.add(LearnWord(dbcb.subjectId, | |||||
dbcb.coursePackId, | |||||
dbcb.courseId, | |||||
dbcb.coursePackType, | |||||
dbcb.courseType, | |||||
lesson.chapterId, | |||||
lesson.lessonId, | |||||
word_id, | |||||
true, | |||||
lesson.lessonType).apply { | |||||
word = getString(getColumnIndex("word")) | |||||
basic_explanation = getString(getColumnIndex("basic_explaination")) | |||||
extend_explanation = getString(getColumnIndex("all_explaination")) | |||||
phrase = getString(getColumnIndex("pharse")) | |||||
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")) | |||||
} | |||||
} | |||||
//口语句型学习有句型字段 | |||||
if (lessonType == AppConstants.LESSON_TYPE_SENTENCE) { | |||||
pattern = getString(getColumnIndex("pattern")) | |||||
} | |||||
}) | |||||
} | |||||
close() | |||||
} | |||||
return result | |||||
} | } | ||||
/** 获取课程今日需要复习的数据 */ | |||||
fun queryCourseReviewDatas( | |||||
coursePackId : Long, | |||||
courseId : Long, | |||||
wordIds : ArrayList<Long>, | |||||
) { | |||||
/**获取图片*/ | |||||
fun queryPhoto(dbControlBase : DbControlBase, wordId : Long) : ByteArray? { | |||||
open(dbControlBase) | |||||
var result : ByteArray? = null | |||||
val sql = "SELECT photo FROM res WHERE word_id = $wordId" | |||||
mDataBase?.rawQuery(sql,null)?.apply { | |||||
while (moveToNext()){ | |||||
result = getBlob(0) | |||||
} | |||||
close() | |||||
} | |||||
return result | |||||
} | } | ||||
data class DbControlBase( | data class DbControlBase( | ||||
val subjectId: Int = 0, | val subjectId: Int = 0, | ||||
val coursePackId: Long = 0, | val coursePackId: Long = 0, | ||||
val coursePackType:Int = 0 , | |||||
val courseId: Long = 0, | val courseId: Long = 0, | ||||
var courseType: Int = 0) | var courseType: Int = 0) |
coursePack.inCoursePackPosition = englishCoursePack.size | coursePack.inCoursePackPosition = englishCoursePack.size | ||||
englishCoursePack.add(coursePack) | englishCoursePack.add(coursePack) | ||||
} else { | } else { | ||||
coursePack.inCoursePackPosition = englishCoursePack.size | |||||
coursePack.inCoursePackPosition = chineseCoursePack.size | |||||
chineseCoursePack.add(coursePack) | chineseCoursePack.add(coursePack) | ||||
} | } | ||||
} | } |
package com.xkl.cdl.data.repository | package com.xkl.cdl.data.repository | ||||
import android.graphics.Bitmap | |||||
import android.util.LruCache | import android.util.LruCache | ||||
import java.security.Key | |||||
import androidx.lifecycle.MutableLiveData | |||||
import com.suliang.common.AppConfig | |||||
import com.suliang.common.eventbus.NonStickyMutableLiveData | |||||
import com.suliang.common.extension.diskIo2Main | |||||
import com.suliang.common.util.file.FileUtil | |||||
import com.xkl.cdl.data.AppConstants | |||||
import com.xkl.cdl.data.manager.db.DBCourseManager | |||||
import com.xkl.cdl.data.manager.db.DbControlBase | |||||
import io.reactivex.rxjava3.core.Observable | |||||
import java.io.File | |||||
import java.util.* | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
* Describe: 音频缓存获取规则 | * Describe: 音频缓存获取规则 | ||||
*/ | */ | ||||
object AudioCache { | object AudioCache { | ||||
var lruCache : LruCache<String, ByteArray> | |||||
init { | |||||
lruCache = object : LruCache<String,ByteArray>((Runtime.getRuntime().maxMemory()/16).toInt()){ | |||||
// key : 发音文件名称 value: 发音文件地址 | |||||
private var lruCache : LruCache<String, String> = | |||||
object : LruCache<String, String>((Runtime.getRuntime().maxMemory() / 16).toInt()) { | |||||
//告知每个对象的存储大小 | //告知每个对象的存储大小 | ||||
override fun sizeOf(key: String?, value: ByteArray?): Int { | |||||
if (value == null){ | |||||
return 0 | |||||
override fun sizeOf(key : String?, value : String?) : Int { | |||||
return value?.toByteArray()?.size ?: 0 | |||||
} | |||||
} | |||||
/** 通知查到的录音 */ | |||||
var audioLiveData = NonStickyMutableLiveData<String?>() | |||||
// fun initAudioLiveData() : MutableLiveData<String?>{ | |||||
// audioLiveData = NonStickyMutableLiveData<String?>() | |||||
// return audioLiveData | |||||
// } | |||||
/** | |||||
* 获取对应音频 | |||||
* 1缓存 2文件 3数据库 | |||||
* @return String 文件路径 | |||||
*/ | |||||
fun get(dbControlBase : DbControlBase, wordId : Long, soundWay : Int) { | |||||
//父文件路劲 | |||||
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||||
//先查对应的文件路径 : 项目id_课程包id_课程id_单词id_发音方式 | |||||
val defaultKey = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}_$soundWay" | |||||
Observable.fromCallable { | |||||
//不为空,直接取值 | |||||
if (lruCache.get(defaultKey) == null){ | |||||
//为空 查文件 | |||||
val file = File(parentPath, defaultKey) | |||||
when { | |||||
file.exists() -> lruCache.put(defaultKey, file.path) //文件存在,则表示以前进行过数据库查询,且进行了保存文件,英美发音本地都有文件 | |||||
else -> { //文件不存在,则查询数据库进行查询,返回的为保存的文件地址 | |||||
val filePath = DBCourseManager.queryAudio(dbControlBase, wordId, soundWay) | |||||
filePath?.also { | |||||
when(soundWay){ | |||||
AppConstants.SOUND_TYPE_UK -> { | |||||
lruCache.put(defaultKey, it) //保存路径 | |||||
val audioFileNameUS = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}_${AppConstants.SOUND_TYPE_US}" | |||||
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径 | |||||
} | |||||
AppConstants.SOUND_TYPE_US -> { | |||||
lruCache.put(defaultKey, it) //保存路径 | |||||
val audioFileNameUS = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||||
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径 | |||||
} | |||||
else -> lruCache.put(defaultKey, it) //保存路径 | |||||
} | |||||
} //如果查询返回路径为空,则说明,没有发音内容 | |||||
} | |||||
} | } | ||||
return value.size | |||||
} | } | ||||
return@fromCallable lruCache.get(defaultKey) ?: "" | |||||
}.compose(diskIo2Main()).subscribe { | |||||
audioLiveData.value = it | |||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* 获取对应音频 | |||||
* @param key String | |||||
* @return ByteArray? | |||||
* 口语总测获取发音文件 | |||||
* @param fileName String 发音文件名称 | |||||
* @return String 发音文件路径 | |||||
*/ | */ | ||||
fun get(key:String) : ByteArray? { | |||||
return null | |||||
fun get(fileName : String):String { | |||||
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||||
if (lruCache[fileName] == null){ | |||||
lruCache.put(fileName,File(parentPath,fileName).path) | |||||
} | |||||
return lruCache[fileName] | |||||
} | } | ||||
} | } |
package com.xkl.cdl.data.repository | |||||
import android.util.LruCache | |||||
import androidx.lifecycle.MutableLiveData | |||||
import com.suliang.common.AppConfig | |||||
import com.suliang.common.eventbus.NonStickyMutableLiveData | |||||
import com.suliang.common.extension.diskIo2Main | |||||
import com.suliang.common.util.file.FileUtil | |||||
import com.xkl.cdl.data.AppConstants | |||||
import com.xkl.cdl.data.manager.db.DBCourseManager | |||||
import com.xkl.cdl.data.manager.db.DbControlBase | |||||
import io.reactivex.rxjava3.core.Observable | |||||
import java.io.File | |||||
import java.util.* | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/1 9:53 | |||||
* Describe: 图片缓存获取规则 | |||||
*/ | |||||
object PhotoCache { | |||||
// key : value: 发音文件地址 | |||||
private var lruCache : LruCache<String, ByteArray?> = | |||||
object : LruCache<String, ByteArray?>((Runtime.getRuntime().maxMemory() / 16).toInt()) { | |||||
//告知每个对象的存储大小 | |||||
override fun sizeOf(key : String?, value : ByteArray?) : Int { | |||||
return value?.size ?: 0 | |||||
} | |||||
} | |||||
/** 通知查到的录音 */ | |||||
var photoLiveData = NonStickyMutableLiveData<ByteArray?>() | |||||
/** | |||||
* 获取对应图片 | |||||
* 1缓存 2文件 3数据库 | |||||
* @return String 文件路径 | |||||
*/ | |||||
fun get(dbControlBase : DbControlBase, wordId : Long) { | |||||
//先查对应的文件路径 : 项目id_课程包id_课程id_单词id_发音方式 | |||||
val defaultKey = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}" | |||||
Observable.fromCallable { | |||||
//不为空,直接取值 | |||||
if (lruCache.get(defaultKey) == null) { | |||||
val value = DBCourseManager.queryPhoto(dbControlBase, wordId) | |||||
lruCache.put(defaultKey, value) //保存路径 | |||||
} | |||||
return@fromCallable lruCache.get(defaultKey) | |||||
}.compose(diskIo2Main()).subscribe { | |||||
photoLiveData.value = it | |||||
} | |||||
} | |||||
} |
import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||
import com.suliang.common.AppConfig | import com.suliang.common.AppConfig | ||||
import com.suliang.common.base.BaseDialogFragment | import com.suliang.common.base.BaseDialogFragment | ||||
import com.suliang.common.extension.click | |||||
import com.suliang.common.util.DrawableUti | import com.suliang.common.util.DrawableUti | ||||
import com.xkl.cdl.databinding.DialogCommonBinding | import com.xkl.cdl.databinding.DialogCommonBinding | ||||
binding.tvTitle.setText(it) | binding.tvTitle.setText(it) | ||||
binding.tvTitle.visibility = View.VISIBLE | binding.tvTitle.visibility = View.VISIBLE | ||||
} | } | ||||
contentText?.let { | |||||
contentColor?.let { | |||||
binding.tvContent.setTextColor(ContextCompat.getColor(requireContext(),it)) | binding.tvContent.setTextColor(ContextCompat.getColor(requireContext(),it)) | ||||
binding.tvContent.visibility = View.VISIBLE | |||||
} | } | ||||
contentColor?.let { | |||||
contentText?.let { | |||||
binding.tvContent.setText(it) | binding.tvContent.setText(it) | ||||
binding.tvContent.visibility = View.VISIBLE | |||||
} | } | ||||
leftColor?.let { | leftColor?.let { | ||||
binding.tvLeft.setTextColor(ContextCompat.getColor(requireContext(),it)) | binding.tvLeft.setTextColor(ContextCompat.getColor(requireContext(),it)) | ||||
} | } | ||||
leftText?.let { | leftText?.let { | ||||
binding.tvLeft.setText(it) | binding.tvLeft.setText(it) | ||||
onCommonDialogButtonClickListener(this@CommonDialog,false) | |||||
binding.tvLeft.click { | |||||
onCommonDialogButtonClickListener(this@CommonDialog,false) | |||||
} | |||||
}?:let { | }?:let { | ||||
binding.tvLeft.visibility = View.GONE | binding.tvLeft.visibility = View.GONE | ||||
binding.vSpace.visibility = View.GONE | binding.vSpace.visibility = View.GONE | ||||
} | } | ||||
rightText?.let { | |||||
rightColor?.let { | |||||
binding.tvRight.setTextColor(ContextCompat.getColor(requireContext(),it)) | binding.tvRight.setTextColor(ContextCompat.getColor(requireContext(),it)) | ||||
onCommonDialogButtonClickListener(this@CommonDialog,true) | |||||
} | } | ||||
rightColor?.let { | |||||
rightText?.let { | |||||
binding.tvRight.setText(it) | binding.tvRight.setText(it) | ||||
binding.tvRight.click { | |||||
onCommonDialogButtonClickListener(this@CommonDialog,true) | |||||
} | |||||
} | } | ||||
imgFlag?.let { | imgFlag?.let { |
* create 2022/4/2 15:47 | * create 2022/4/2 15:47 | ||||
* Describe: 学习的通用弹窗 | * Describe: 学习的通用弹窗 | ||||
*/ | */ | ||||
class LearnDialog private constructor(): BaseDialogFragment<DialogLessonLearnBinding>() { | |||||
class LearnDialog private constructor() : BaseDialogFragment<DialogLessonLearnBinding>() { | |||||
companion object { | companion object { | ||||
fun newInstance(params : LearnDialogBean) : LearnDialog { | fun newInstance(params : LearnDialogBean) : LearnDialog { | ||||
val args = Bundle() | val args = Bundle() | ||||
args.putParcelable(AppConfig.INTENT_1, params) | args.putParcelable(AppConfig.INTENT_1, params) | ||||
override fun initFragment() { | override fun initFragment() { | ||||
when (learnDialogBean.dialogType) { | when (learnDialogBean.dialogType) { | ||||
AppConstants.DIALOG_TYPE_EXAM -> when (learnDialogBean.examType) { | |||||
//测试开始弹窗 | |||||
AppConstants.DIALOG_TYPE_EXAM_START -> when(learnDialogBean.examType){ | |||||
//课时学前测试开始弹窗 | |||||
AppConstants.TEST_TYPE_BEFORE -> initLessonBeforeTestStart() | |||||
} | |||||
//测试结束弹窗 | |||||
AppConstants.DIALOG_TYPE_EXAM_OVER -> when (learnDialogBean.examType) { | |||||
//学前总测结束弹窗 | |||||
AppConstants.TEST_TYPE_BEFORE_TOTAL -> initCourseBeforeTotalTestOver() | AppConstants.TEST_TYPE_BEFORE_TOTAL -> initCourseBeforeTotalTestOver() | ||||
//课时学前测试结束弹窗 | |||||
AppConstants.TEST_TYPE_BEFORE-> initLessonBeforeTestOver() | |||||
} | } | ||||
AppConstants.DIALOG_TYPE_LEARN -> { | |||||
//学习结束弹窗 | |||||
AppConstants.DIALOG_TYPE_LEARN_OVER -> { | |||||
} | } | ||||
} | } | ||||
} | } | ||||
override fun onStart() { | override fun onStart() { | ||||
when { | when { | ||||
learnDialogBean.score < AppConstants.TEST_SCORE_LEVEL_1 -> { | learnDialogBean.score < AppConstants.TEST_SCORE_LEVEL_1 -> { | ||||
binding.imgIv.setImageResource(R.mipmap.test_score_level_1) | binding.imgIv.setImageResource(R.mipmap.test_score_level_1) | ||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(),R.color.gray_2)) | |||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray_2)) | |||||
} | } | ||||
learnDialogBean.score < AppConstants.TEST_SCORE_LEVEL_2 -> { | learnDialogBean.score < AppConstants.TEST_SCORE_LEVEL_2 -> { | ||||
binding.imgIv.setImageResource(R.mipmap.test_score_level_2) | binding.imgIv.setImageResource(R.mipmap.test_score_level_2) | ||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(),R.color.red_2)) | |||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_2)) | |||||
} | } | ||||
else -> { | else -> { | ||||
binding.imgIv.setImageResource(R.mipmap.test_score_level_3) | binding.imgIv.setImageResource(R.mipmap.test_score_level_3) | ||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(),R.color.red_1)) | |||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_1)) | |||||
} | } | ||||
} | } | ||||
//分数 | //分数 | ||||
binding.tvScore.run { | |||||
visibility = View.VISIBLE | |||||
text = "${learnDialogBean.score}分" | |||||
} | |||||
//本次测试成绩显示 | |||||
binding.tvTip.visibility = View.VISIBLE | |||||
binding.tvScore.text = "${learnDialogBean.score}分" | |||||
} | } | ||||
/** 设置成绩数量 */ | /** 设置成绩数量 */ | ||||
private fun initNumber(){ | |||||
private fun initNumber() { | |||||
binding.incStatisticsNumber.run { | binding.incStatisticsNumber.run { | ||||
tvCorrectNumber.text = "${learnDialogBean.correctNumber}" | tvCorrectNumber.text = "${learnDialogBean.correctNumber}" | ||||
tvErrorNumber.text = "${learnDialogBean.errorNumber}" | tvErrorNumber.text = "${learnDialogBean.errorNumber}" | ||||
/** 学前总测试 结束 */ | /** 学前总测试 结束 */ | ||||
private fun initCourseBeforeTotalTestOver() { | private fun initCourseBeforeTotalTestOver() { | ||||
binding.groupTotalTest.visibility = View.VISIBLE | |||||
binding.run { | |||||
tvScore.visibility = View.VISIBLE | |||||
tvTip.visibility = View.VISIBLE | |||||
tvTip1.visibility = View.VISIBLE | |||||
tvTitle.visibility = View.VISIBLE | |||||
incStatisticsNumber.root.visibility = View.VISIBLE | |||||
tvTitle.text = "恭喜你,完成了学前总测试!" | |||||
tvTip1.text = "学考乐已为您智能生成了个性化学习计划" | |||||
tvRight.text = resources.getString(R.string.start_learn) | |||||
tvRight.click { | |||||
onDialogListener(AppConstants.ACTION_COURSE_TEST_START_LEARN, this@LearnDialog) | |||||
} | |||||
} | |||||
initScore() | initScore() | ||||
binding.tvTitle.text = "恭喜你,完成了学前总测试!" | |||||
binding.tvTip1.text = "学考乐已为您智能生成了个性化学习计划" | |||||
initNumber() | initNumber() | ||||
binding.tvRight.text = resources.getString(R.string.start_learn) | |||||
binding.tvRight.click { | |||||
onDialogListener(AppConstants.ACTION_COURSE_TEST_START_LEARN, this) | |||||
} | |||||
} | } | ||||
fun initLessonBeforeTest() { | |||||
binding.tvTitle.text = "课时学前测试" | |||||
binding.tvLessonName.text = "章节课时名称" | |||||
binding.tvCountTime.text = "测试题目时间" | |||||
binding.tvRight.text = "开始测试" | |||||
binding.tvLeft.visibility = View.VISIBLE | |||||
binding.tvLeft.text = "随便设置" | |||||
binding.tvRight.click { | |||||
LogUtil.e("Dialog -- > show()") | |||||
} | |||||
binding.tvLeft.click { | |||||
LogUtil.e("Dialog -- > hide()") | |||||
/** | |||||
* 课时学前测试开始开始前提示 | |||||
*/ | |||||
private fun initLessonBeforeTestStart() { | |||||
binding.run { | |||||
tvTitle.visibility = View.VISIBLE | |||||
tvLessonName.visibility = View.VISIBLE | |||||
tvCountTime.visibility = View.VISIBLE | |||||
tvTitle.text = "课时学前测试" | |||||
tvLessonName.text = learnDialogBean.chapter_lesson_name | |||||
tvCountTime.text = learnDialogBean.showTimeCount | |||||
tvRight.text = "开始测试" | |||||
imgIv.setImageResource(R.mipmap.girl_1) | |||||
//开始测试,进入课时学前测试界面 | |||||
binding.tvRight.click { | |||||
onDialogListener(AppConstants.DIALOG_START_TEST, this@LearnDialog) | |||||
} | |||||
// tvLeft.visibility = View.VISIBLE | |||||
// vSplit.visibility = View.VISIBLE | |||||
// tvLeft.text = "跳过测试" | |||||
//跳过测试,直接进入学习界面 | |||||
// binding.tvLeft.click { | |||||
// onDialogListener(AppConstants.DIALOG_START_LEARN, this@LearnDialog) | |||||
// } | |||||
} | } | ||||
} | } | ||||
fun initLessonBeforeTestOver() { | |||||
binding.tvScore.run { | |||||
visibility = View.VISIBLE | |||||
text = "100分" | |||||
/** | |||||
* 课时学前测试结束 | |||||
*/ | |||||
private fun initLessonBeforeTestOver() { | |||||
binding.run { | |||||
tvScore.visibility = View.VISIBLE | |||||
tvTip.visibility = View.VISIBLE | |||||
tvTitle.visibility = View.VISIBLE | |||||
tvTip1.visibility = View.VISIBLE | |||||
incStatisticsNumber.root.visibility = View.VISIBLE | |||||
tvTitle.text = "恭喜你,完成了课时学前测试" | |||||
tvTip1.setText(resources.getString(R.string.test_before_test_over_tip)) | |||||
tvRight.setText(resources.getString(R.string.start_learn)) | |||||
} | } | ||||
binding.tvTip.visibility = View.VISIBLE | |||||
binding.tvTitle.text = "恭喜你,完成了课时学前测试" | |||||
binding.tvLessonName.visibility = View.GONE | |||||
binding.tvCountTime.visibility = View.GONE | |||||
binding.tvTip1.visibility = View.VISIBLE | |||||
binding.tvTip1.setText(resources.getString(R.string.test_before_test_over_tip)) | |||||
binding.incStatisticsNumber.root.visibility = View.VISIBLE | |||||
binding.tvLeft.visibility = View.GONE | |||||
binding.tvRight.setText(resources.getString(R.string.start_learn)) | |||||
initScore() | |||||
initNumber() | |||||
binding.tvRight.click { | binding.tvRight.click { | ||||
// TODO: 2022/4/2 开始学习 | |||||
//开始学习 | |||||
onDialogListener(AppConstants.DIALOG_START_LEARN,this) | |||||
} | } | ||||
} | } | ||||
fun initLessonAfterTestOver() { | |||||
//未通过、通过和通过已通过,均为一致,只是显示的状态不同 | |||||
val score = if (1 + 1 == 2) 100 else 88 | |||||
when { | |||||
score < AppConstants.TEST_SCORE_LEVEL_1 -> { //小于80 | |||||
binding.imgIv.setImageResource(R.mipmap.test_score_level_1) | |||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray_2)) | |||||
} | |||||
score >= AppConstants.TEST_SCORE_LEVEL_2 -> { //大于90 | |||||
binding.imgIv.setImageResource(R.mipmap.test_score_level_2) | |||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_1)) | |||||
} | |||||
else -> { // 大于等于80 且小于90 | |||||
binding.imgIv.setImageResource(R.mipmap.test_score_level_3) | |||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_2)) | |||||
} | |||||
/** | |||||
* 课时测试结束 | |||||
*/ | |||||
private fun initLessonAfterTestOver(){ | |||||
initNumber() | |||||
initScore() | |||||
binding.run { | |||||
tvScore.visibility = View.VISIBLE | |||||
tvTip.visibility = View.VISIBLE | |||||
tvTitle.visibility = View.VISIBLE | |||||
tvTip1.visibility = View.VISIBLE | |||||
incStatisticsNumber.root.visibility = View.VISIBLE | |||||
tvTop.visibility = View.VISIBLE | |||||
tvLeft.visibility = View.VISIBLE | |||||
vSplit.visibility = View.VISIBLE | |||||
tvTitle.text = "恭喜你,完成了课时学后测试" | |||||
tvTop1.text = "重新学习" | |||||
tvLeft.text = "再测一次" | |||||
tvRight.text = "下一课时" | |||||
// TODO: 2022/4/21 小游戏练习先搁置 | |||||
// tvTop1.visibility = View.VISIBLE | |||||
// tvTop.text = "小游戏练习" | |||||
} | } | ||||
binding.tvScore.run { | |||||
visibility = View.VISIBLE | |||||
text = "${score}分" | |||||
binding.tvTop.click { | |||||
onDialogListener(AppConstants.DIALOG_LESSON_AFTER_TEST_RELEARN,this) | |||||
} | |||||
binding.tvLeft.click { | |||||
onDialogListener(AppConstants.DIALOG_LESSON_AFTER_TEST_AGAIN,this) | |||||
} | } | ||||
binding.tvTip.visibility = View.VISIBLE | |||||
binding.tvTitle.text = "恭喜你,完成了课时学前测试" | |||||
binding.tvLessonName.visibility = View.GONE | |||||
binding.tvCountTime.visibility = View.GONE | |||||
binding.incStatisticsNumber.root.visibility = View.VISIBLE | |||||
// binding.tvTip1.visibility = View.VISIBLE | |||||
// binding.tvTip1.setText(resources.getString(R.string.test_before_test_over_tip)) | |||||
binding.tvRight.setText(resources.getString(R.string.start_learn)) | |||||
binding.tvRight.click { | binding.tvRight.click { | ||||
// TODO: 2022/4/2 开始学习 | |||||
onDialogListener(AppConstants.DIALOG_LESSON_AFTER_TEST_NEXT,this) | |||||
} | } | ||||
} | } | ||||
} | } |
import androidx.lifecycle.LifecycleOwner | import androidx.lifecycle.LifecycleOwner | ||||
import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||
import com.suliang.common.base.viewmodel.BaseViewModel | import com.suliang.common.base.viewmodel.BaseViewModel | ||||
import com.suliang.common.util.LogUtil | |||||
import java.util.* | import java.util.* | ||||
import kotlin.concurrent.timerTask | import kotlin.concurrent.timerTask | ||||
private var timeTask : TimerTask? = null | private var timeTask : TimerTask? = null | ||||
//是否进行计时,默认计时,为false不计时,更改为false的条件时,出现弹窗或者当前页不在前台 | //是否进行计时,默认计时,为false不计时,更改为false的条件时,出现弹窗或者当前页不在前台 | ||||
var countingEnable = true | |||||
// var countingEnable = true | |||||
/** 开始计时 */ | /** 开始计时 */ | ||||
fun startTotalCounting(){ | fun startTotalCounting(){ | ||||
timeTask = timerTask { | timeTask = timerTask { | ||||
if (countingEnable) { | |||||
totalUseTime.postValue(totalUseTime.value?.plus(1000)) | |||||
} | |||||
}.apply { | |||||
if (!isAllOver) { | |||||
totalTimer.schedule(this, 1000, 1000) | |||||
} | |||||
totalUseTime.postValue(totalUseTime.value?.plus(200)) | |||||
} | } | ||||
totalTimer.schedule(timeTask, 200, 200) | |||||
LogUtil.e("开始总计时") | |||||
} | } | ||||
/** 停止计时 */ | /** 停止计时 */ | ||||
fun stopTotalCountTing(){ | fun stopTotalCountTing(){ | ||||
totalTimer.cancel() | |||||
LogUtil.e("停止总计时") | |||||
timeTask?.cancel() | |||||
timeTask = null | timeTask = null | ||||
} | } | ||||
} | } |
import android.widget.TextView | import android.widget.TextView | ||||
import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||
import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
import androidx.recyclerview.widget.GridLayoutManager | |||||
import androidx.recyclerview.widget.SimpleItemAnimator | import androidx.recyclerview.widget.SimpleItemAnimator | ||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager | |||||
import com.airbnb.lottie.LottieAnimationView | import com.airbnb.lottie.LottieAnimationView | ||||
import com.suliang.common.base.activity.BaseActivityVM | import com.suliang.common.base.activity.BaseActivityVM | ||||
import com.suliang.common.eventbus.LiveDataBus | import com.suliang.common.eventbus.LiveDataBus | ||||
import com.suliang.common.extension.click | import com.suliang.common.extension.click | ||||
import com.suliang.common.util.DateUtil | import com.suliang.common.util.DateUtil | ||||
import com.suliang.common.util.DrawableUti | import com.suliang.common.util.DrawableUti | ||||
import com.suliang.common.util.LogUtil | |||||
import com.suliang.common.util.media.EMediaState | |||||
import com.suliang.common.util.media.IMPListener | |||||
import com.suliang.common.util.media.MPManager | |||||
import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
import com.xkl.cdl.adapter.AdapterSpell | import com.xkl.cdl.adapter.AdapterSpell | ||||
import com.xkl.cdl.adapter.itemdecoration.SpellItemDecoration | import com.xkl.cdl.adapter.itemdecoration.SpellItemDecoration | ||||
import com.xkl.cdl.data.event.LearnEventData | import com.xkl.cdl.data.event.LearnEventData | ||||
import com.xkl.cdl.data.manager.CourseManager | import com.xkl.cdl.data.manager.CourseManager | ||||
import com.xkl.cdl.data.manager.UserInfoManager | import com.xkl.cdl.data.manager.UserInfoManager | ||||
import com.xkl.cdl.data.repository.AudioCache | |||||
import com.xkl.cdl.databinding.* | import com.xkl.cdl.databinding.* | ||||
import com.xkl.cdl.dialog.CommonDialog | import com.xkl.cdl.dialog.CommonDialog | ||||
import com.xkl.cdl.dialog.CommonDialogBean | import com.xkl.cdl.dialog.CommonDialogBean | ||||
resources.getString(R.string.pause) -> { //暂停点击 | resources.getString(R.string.pause) -> { //暂停点击 | ||||
vm.pauseToNext() | vm.pauseToNext() | ||||
setText(R.string.continue_) | setText(R.string.continue_) | ||||
// //如果是学前学后总测试的口语,点击暂停按钮后,需要将选项文字的点击事件恢复,用以点击发音 | |||||
// if (vm.intentData.courseType == AppConstants.COURSE_TYPE_ENGLISH_SPOKEN && (vm.intentData.examType == AppConstants.TEST_TYPE_BEFORE_TOTAL || vm.intentData.examType == AppConstants.TEST_TYPE_AFTER_TOTAL)) { | |||||
// vm.optionList.forEach { optionBinding -> | |||||
// optionBinding.ivOption.click { optionBinding.ivOption.performClick() } | |||||
// } | |||||
// } | |||||
} | } | ||||
resources.getString(R.string.continue_) -> { | resources.getString(R.string.continue_) -> { | ||||
vm.continueToNext() | vm.continueToNext() | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | |||||
/** 查询到播放数据过来 */ | |||||
AudioCache.audioLiveData.observe(this) { | |||||
if (it == null) { | |||||
//发音文件为空 | |||||
showToast("未找到发音文件") | |||||
} else { | |||||
MPManager.play(it, listener = impListener) | |||||
} | |||||
} | |||||
} | |||||
//播放监听 | |||||
private val impListener = object : IMPListener { | |||||
override fun onMpState(state : EMediaState) { | |||||
when (vm.intentData.courseType) { | |||||
AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_CHINESE_LITERACY, AppConstants.COURSE_TYPE_CHINESE_PINYIN, //不需要监听,只播放一次 | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL, // 拼写不需要监听,第一次不播放 | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { | |||||
} | |||||
AppConstants.COURSE_TYPE_ENGLISH_VOICE -> { //辨音需要监听是否需要重复播放 | |||||
when (state) { | |||||
EMediaState.RUNNING -> if (wordChooseBinding.ivVoice.visibility == View.VISIBLE) { | |||||
wordChooseBinding.ivVoice.playAnimation() | |||||
} | |||||
EMediaState.ERROR -> showToast("播放异常") | |||||
EMediaState.COMPLETE -> if (wordChooseBinding.ivVoice.visibility == View.VISIBLE) { | |||||
wordChooseBinding.ivVoice.postDelayed({ | |||||
LogUtil.e("------开始重复播放----------------------------------------") | |||||
wordChooseBinding.ivVoice.performClick() | |||||
}, 700) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** 新单词来了的发音事件 */ | |||||
private fun initNewWordRead(it : ExamBean) { | |||||
when (vm.intentData.courseType) { | |||||
//拼写初始不发音,作文不发音 | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL, AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> { | |||||
} | |||||
//英语认读,英语音标,英语口语,语文识字,语文拼音 出现即发音一次 | |||||
AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_CHINESE_LITERACY, AppConstants.COURSE_TYPE_CHINESE_PINYIN, | |||||
// 英语辨音 重复播放 | |||||
AppConstants.COURSE_TYPE_ENGLISH_VOICE -> { | |||||
wordChooseBinding.ivVoice.performClick() | |||||
} | |||||
} | } | ||||
} | } | ||||
tvTitle.text = vm.intentData.showTitle | tvTitle.text = vm.intentData.showTitle | ||||
//题目进度 | //题目进度 | ||||
binding.incLearnTitle.tvNumProgress.text = "${vm.currentIndex + 1}/${vm.testData.size}" | binding.incLearnTitle.tvNumProgress.text = "${vm.currentIndex + 1}/${vm.testData.size}" | ||||
//默认发音 | |||||
vm.defaultSoundWay = UserInfoManager.getDefaultSoundWay() | |||||
voiceSwitch.setSoundWay(vm.defaultSoundWay) | |||||
voiceSwitch.soundWayChange.observe(this@LearnExamActivity) { | |||||
vm.defaultSoundWay = it | |||||
UserInfoManager.putDefaultSoundWay(it) | |||||
if (this@LearnExamActivity::spellAdapter.isInitialized) { | |||||
spellAdapter.defaultSoundWay = it | |||||
//发音方式 | |||||
when (vm.intentData.subjectId) { | |||||
AppConstants.SUBJECT_CHINESE -> { | |||||
vm.defaultSoundWay = AppConstants.SOUND_TYPE_CN | |||||
voiceSwitch.visibility = View.GONE | |||||
} | |||||
else -> { | |||||
//默认发音 | |||||
vm.defaultSoundWay = UserInfoManager.getDefaultSoundWay() | |||||
voiceSwitch.setSoundWay(vm.defaultSoundWay) | |||||
voiceSwitch.soundWayChange.observe(this@LearnExamActivity) { | |||||
vm.defaultSoundWay = it | |||||
UserInfoManager.putDefaultSoundWay(it) | |||||
if (this@LearnExamActivity::spellAdapter.isInitialized) { | |||||
spellAdapter.defaultSoundWay = it | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { | AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { | ||||
spellBinding = IncExamSpellContentBinding.inflate(layoutInflater, binding.containerLayout, true) | spellBinding = IncExamSpellContentBinding.inflate(layoutInflater, binding.containerLayout, true) | ||||
spellBinding.spellRecyclerView.run { | spellBinding.spellRecyclerView.run { | ||||
layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.HORIZONTAL) | |||||
layoutManager = GridLayoutManager(this@LearnExamActivity, 2, GridLayoutManager.HORIZONTAL, false) | |||||
addItemDecoration(SpellItemDecoration()) | addItemDecoration(SpellItemDecoration()) | ||||
setHasFixedSize(true) | |||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false | (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false | ||||
spellAdapter = AdapterSpell().apply { | spellAdapter = AdapterSpell().apply { | ||||
onItemSpellingClickListener = itemSpellingClick | onItemSpellingClickListener = itemSpellingClick | ||||
adapter = spellAdapter | adapter = spellAdapter | ||||
} | } | ||||
//初始化事件 | //初始化事件 | ||||
initChooseListener() | |||||
initChooseQuestionListener() | |||||
} | } | ||||
else -> { | else -> { | ||||
wordChooseBinding = IncExamWordChooseContentBinding.inflate(layoutInflater, binding.containerLayout, true) | wordChooseBinding = IncExamWordChooseContentBinding.inflate(layoutInflater, binding.containerLayout, true) | ||||
tvLable.text = "D" | tvLable.text = "D" | ||||
}) | }) | ||||
//初始化事件 | //初始化事件 | ||||
initChooseListener() | |||||
initChooseQuestionListener() | |||||
//正常、正确、错误 文本和背景颜色 | //正常、正确、错误 文本和背景颜色 | ||||
vm.normalTextColor = ContextCompat.getColor(this, R.color.main_text_color) | vm.normalTextColor = ContextCompat.getColor(this, R.color.main_text_color) | ||||
vm.correctTextColor = ContextCompat.getColor(this, R.color.green_1) | vm.correctTextColor = ContextCompat.getColor(this, R.color.green_1) | ||||
} | } | ||||
/** 初始四选一的点击事件 只处理问题和单词的事件,选项事件,每个新的单词开始时设置,答题后再取消设置 | /** 初始四选一的点击事件 只处理问题和单词的事件,选项事件,每个新的单词开始时设置,答题后再取消设置 | ||||
* 拼写,释义、单词都不发音,只有拼写和拼写完成发音 | |||||
* 拼写,释义、单词都不发音,只有拼写在拼写完成发音 | |||||
* 作文知识点测试,没有发音 | * 作文知识点测试,没有发音 | ||||
* 辨音、口语单词与音频均可发音, | * 辨音、口语单词与音频均可发音, | ||||
* 其他单词可以发音 | * 其他单词可以发音 | ||||
* */ | * */ | ||||
private fun initChooseListener() { | |||||
private fun initChooseQuestionListener() { | |||||
when (vm.intentData.courseType) { | when (vm.intentData.courseType) { | ||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL, AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> { | AppConstants.COURSE_TYPE_ENGLISH_SPELL, AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> { | ||||
} | } | ||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_ENGLISH_VOICE -> { | |||||
else -> { | |||||
wordChooseBinding.tvWord.setOnClickListener(ivVoiceClick) | wordChooseBinding.tvWord.setOnClickListener(ivVoiceClick) | ||||
wordChooseBinding.ivVoice.setOnClickListener(ivVoiceClick) | wordChooseBinding.ivVoice.setOnClickListener(ivVoiceClick) | ||||
} | } | ||||
else -> { //四选一题目可以发音 | |||||
wordChooseBinding.tvWord.setOnClickListener(ivVoiceClick) | |||||
} | |||||
} | } | ||||
} | } | ||||
override fun loadData() { | override fun loadData() { | ||||
//初始获取第一条数据 | //初始获取第一条数据 | ||||
vm.loadNext() | |||||
vm.loadFirst() | |||||
//监听倒计时 | //监听倒计时 | ||||
vm.currentSuplusTime.observe(this) { | vm.currentSuplusTime.observe(this) { | ||||
initContentUI(it) | initContentUI(it) | ||||
//设置初始事件 | //设置初始事件 | ||||
initChooseContentLister(it) | initChooseContentLister(it) | ||||
vm.startCurrentCountingTime() | |||||
//发音事件 | |||||
initNewWordRead(it) | |||||
} ?: testOver() | } ?: testOver() | ||||
} | } | ||||
if (it != AppConstants.TEST_CORRECT) { | if (it != AppConstants.TEST_CORRECT) { | ||||
binding.tvErrorState.visibility = View.VISIBLE | binding.tvErrorState.visibility = View.VISIBLE | ||||
} | } | ||||
if (isSpokenTotalTest()) { | |||||
vm.optionList.forEach { optionBinding -> | |||||
optionBinding.run { | |||||
groupVoiceChoose.visibility = View.GONE | |||||
tvOption.visibility = View.VISIBLE | |||||
} | |||||
} | |||||
} | |||||
//获取下一题 | //获取下一题 | ||||
vm.loadNext() | vm.loadNext() | ||||
} | } | ||||
//获取下一题 | //获取下一题 | ||||
vm.loadNext() | vm.loadNext() | ||||
} | } | ||||
} | |||||
/** 四选一对option设置事件 新数据来的时候 设置option的事件 | |||||
* @param examBean 非空,则需要设置事件 为空则取消点击事件 | |||||
* */ | |||||
private fun initChooseContentLister(examBean : ExamBean?) { | |||||
when (vm.intentData.courseType) { | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { | |||||
examBean?.let { | |||||
when (it.answersIsAudio) { //选项是否音频 | |||||
true -> { | |||||
// TODO: 2022/4/13 设置音频事件的路劲tag | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.layoutChooseArea.click { vm.chooseResult(index) } | |||||
optionItemBinding.layoutLottieVoiceArea.click(ivVoiceClick) | |||||
optionItemBinding.tvOption.click(null) | |||||
optionItemBinding.tvOption.tag = "" | |||||
} | |||||
} | |||||
false -> { | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.root.click { vm.chooseResult(index) } | |||||
optionItemBinding.tvOption.click(null) | |||||
optionItemBinding.tvOption.tag = "" | |||||
} | |||||
} | |||||
} | |||||
} ?: let { //取消事件 | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.root.click(null) | |||||
optionItemBinding.tvOption.click(ivVoiceClick) | |||||
} | |||||
} | |||||
} | |||||
else -> { | |||||
examBean?.let { | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.root.click { vm.chooseResult(index) } | |||||
} | |||||
} ?: let { | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.root.click(null) | |||||
} | |||||
//当前单词倒计时结束 | |||||
vm.currentCountingTimeOver.observe(this) { | |||||
when (vm.intentData.courseType) { | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { | |||||
spellAdapter.testCountingTimeOver() | |||||
} | } | ||||
else -> vm.chooseResult(-1) | |||||
} | } | ||||
} | } | ||||
} | } | ||||
private fun initContentUI(examBean : ExamBean) { | private fun initContentUI(examBean : ExamBean) { | ||||
if (isSpokenTotalTest()) { | |||||
initSpokenUIData(examBean) | |||||
return | |||||
} | |||||
when (vm.intentData.courseType) { | when (vm.intentData.courseType) { | ||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> initSpellUIData(examBean) | AppConstants.COURSE_TYPE_ENGLISH_SPELL -> initSpellUIData(examBean) | ||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> initSpokenUIData(examBean) | |||||
AppConstants.COURSE_TYPE_ENGLISH_VOICE -> initVoiceUIData(examBean) | AppConstants.COURSE_TYPE_ENGLISH_VOICE -> initVoiceUIData(examBean) | ||||
else -> initChooseUIData(examBean) | else -> initChooseUIData(examBean) | ||||
} | } | ||||
wordChooseBinding.tvWord.visibility = View.VISIBLE | wordChooseBinding.tvWord.visibility = View.VISIBLE | ||||
wordChooseBinding.ivVoice.visibility = View.GONE | wordChooseBinding.ivVoice.visibility = View.GONE | ||||
} | } | ||||
wordChooseBinding.progressWordCountdownTime.visibility = View.GONE | |||||
vm.optionList.forEachIndexed { index, optionBinding -> | vm.optionList.forEachIndexed { index, optionBinding -> | ||||
optionBinding.run { | optionBinding.run { | ||||
root.setBackgroundResource(vm.normalBg) | root.setBackgroundResource(vm.normalBg) | ||||
if (examBean.answersIsAudio) { //答案为音频 | if (examBean.answersIsAudio) { //答案为音频 | ||||
groupVoiceChoose.visibility = View.VISIBLE | groupVoiceChoose.visibility = View.VISIBLE | ||||
tvOption.visibility = View.GONE | tvOption.visibility = View.GONE | ||||
ivOption.visibility = View.GONE | |||||
} else { | } else { | ||||
groupVoiceChoose.visibility = View.GONE | groupVoiceChoose.visibility = View.GONE | ||||
tvOption.visibility = View.VISIBLE | tvOption.visibility = View.VISIBLE | ||||
ivOption.visibility = View.VISIBLE | |||||
} | } | ||||
} | } | ||||
} | } | ||||
setTextColor(ContextCompat.getColor(this@LearnExamActivity, R.color.main_text_color)) | setTextColor(ContextCompat.getColor(this@LearnExamActivity, R.color.main_text_color)) | ||||
} | } | ||||
//进度 | //进度 | ||||
wordChooseBinding.progressWordCountdownTime.run { | |||||
spellBinding.progressWordCountdownTime.run { | |||||
max = vm.currentMaxTime | max = vm.currentMaxTime | ||||
progress = vm.currentMaxTime | progress = vm.currentMaxTime | ||||
} | } | ||||
} | } | ||||
/** 四选一对option设置事件 新数据来的时候 设置option的事件 | |||||
* @param examBean 非空,则需要设置事件 为空则取消点击事件 | |||||
* */ | |||||
private fun initChooseContentLister(examBean : ExamBean?) { | |||||
if (isSpokenTotalTest()) { | |||||
examBean?.let { | |||||
when { | |||||
it.answersIsAudio -> { //选项是否音频 | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.run { | |||||
root.click(null) | |||||
layoutChooseArea.click { vm.chooseResult(index) } | |||||
ivOption.click(ivVoiceClick) | |||||
// TODO: 2022/4/20 这个的tag为该条音频的发音音频源 | |||||
ivOption.tag = tvOption.text | |||||
layoutLottieVoiceArea.click { ivOption.performClick() } | |||||
tvOption.click(null) | |||||
} | |||||
} | |||||
} | |||||
else -> { //答案选项非音频源 | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.run { | |||||
root.click { vm.chooseResult(index) } | |||||
ivOption.click(ivVoiceClick) | |||||
// TODO: 2022/4/20 这个的tag为该条音频的发音音频源 | |||||
ivOption.tag = tvOption.text | |||||
tvOption.click(null) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} ?: let { //取消事件 | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.root.click(null) | |||||
optionItemBinding.tvOption.click { optionItemBinding.ivOption.performClick() } | |||||
} | |||||
} | |||||
} else { | |||||
examBean?.let { | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.root.click { vm.chooseResult(index) } | |||||
} | |||||
} ?: let { | |||||
vm.optionList.forEachIndexed { index, optionItemBinding -> | |||||
optionItemBinding.root.click(null) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** 点击发音事件 获取发音音频,监听返回值,播放*/ | |||||
private val ivVoiceClick : (v : View) -> Unit = { view -> | |||||
vm.currentExamBean.value?.let { | |||||
//正常情况除口语,其他的都是播放的测试单词的音频 | |||||
//口语的音频,有问题,与选项 直接获取其tag获取音频 | |||||
currentPlayView?.cancelAnimation() | |||||
if (view is LottieAnimationView && view.visibility == View.VISIBLE) currentPlayView = view | |||||
if (isSpokenTotalTest()) { | |||||
//口语课程学前学后总的点击事件取tag | |||||
AudioCache.get(view.tag as String) | |||||
} else { | |||||
AudioCache.get(vm.dbBaseControl, it.word_id, vm.defaultSoundWay) | |||||
} | |||||
} | |||||
} | |||||
//是否口语总测试 | |||||
private fun isSpokenTotalTest() = | |||||
vm.intentData.courseType == AppConstants.COURSE_TYPE_ENGLISH_SPOKEN && (vm.intentData.examType == AppConstants.TEST_TYPE_BEFORE_TOTAL || vm.intentData.examType == AppConstants.TEST_TYPE_AFTER_TOTAL) | |||||
/** | /** | ||||
* 拼写时的点击事件 | * 拼写时的点击事件 | ||||
* @param selectedValue 选择的值 | * @param selectedValue 选择的值 | ||||
spellBinding.tvWord.text = correctValue | spellBinding.tvWord.text = correctValue | ||||
//拼写结束 | //拼写结束 | ||||
vm.spellOver(selectedValue.toString(), errorSize) | vm.spellOver(selectedValue.toString(), errorSize) | ||||
//执行当前的单词发音 延迟发音 | |||||
spellBinding.tvWord.postDelayed({ ivVoiceClick(spellBinding.tvWord) }, 200) | |||||
} | } | ||||
else -> { | else -> { | ||||
//设置为选中的值 | //设置为选中的值 | ||||
} | } | ||||
/** 点击发音事件 */ | |||||
private val ivVoiceClick : (v : View) -> Unit = { | |||||
} | |||||
override fun onBackPressed() { | override fun onBackPressed() { | ||||
onBack() | onBack() | ||||
} | } | ||||
/** 返回: 未结束提示弹窗 */ | /** 返回: 未结束提示弹窗 */ | ||||
private fun onBack() { | private fun onBack() { | ||||
vm.stopTotalCountTing() | |||||
when { | when { | ||||
vm.isAllOver -> finish() | vm.isAllOver -> finish() | ||||
else -> CommonDialog.newInstance(CommonDialogBean(titleText = R.string.dialog_test_not_over, | |||||
contentText = R.string.dialog_test_not_over_tip, | |||||
leftText = R.string.quit, | |||||
rightText = R.string.quit)).apply { | |||||
onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||||
if (!isRightClick){ | |||||
vm. | |||||
} | |||||
dialog.dismissAllowingStateLoss() | |||||
} | |||||
else -> { | |||||
vm.showOrDismissBackDialogForTime(true) | |||||
CommonDialog.newInstance(CommonDialogBean(titleText = R.string.dialog_test_not_over, | |||||
contentText = R.string.dialog_test_not_over_tip, | |||||
leftText = R.string.quit, | |||||
rightText = R.string.cancel)).apply { | |||||
onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||||
dialog.dismissAllowingStateLoss() | |||||
when { | |||||
isRightClick -> vm.showOrDismissBackDialogForTime(false) | |||||
else -> finish() | |||||
} | |||||
} | |||||
}.show(supportFragmentManager, "common_dialog") | |||||
} | } | ||||
} | } | ||||
finish() | |||||
} | } | ||||
/** 测试完成 : 弹窗显示 */ | /** 测试完成 : 弹窗显示 */ | ||||
private fun testOver() { | private fun testOver() { | ||||
//对话框信息实体 | //对话框信息实体 | ||||
val learnDialogBean = LearnDialogBean(AppConstants.DIALOG_TYPE_EXAM).apply { | |||||
val learnDialogBean = LearnDialogBean(AppConstants.DIALOG_TYPE_EXAM_OVER).apply { | |||||
examType = vm.intentData.examType | examType = vm.intentData.examType | ||||
score = vm.scoreValue | score = vm.scoreValue | ||||
correctNumber = vm.correctLiveData.value!! | correctNumber = vm.correctLiveData.value!! | ||||
} | } | ||||
LearnDialog.newInstance(learnDialogBean).apply { | LearnDialog.newInstance(learnDialogBean).apply { | ||||
onDialogListener = { action, dialog -> | onDialogListener = { action, dialog -> | ||||
when (action) { | |||||
AppConstants.DIALOG_START_LEARN -> { //开始学习 | |||||
dialog.dismissAllowingStateLoss() | |||||
//发动动作 : 继续学习 | |||||
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).value = | |||||
LearnEventData(vm.intentData.subjectId, | |||||
vm.intentData.courseId, | |||||
AppConstants.ACTION_COURSE_TEST_START_LEARN) | |||||
finish() | |||||
when (vm.intentData.examType) { | |||||
//学前测试结束,只有一个按钮,开始学习 -> 发送消息切换到课时列表页 | |||||
AppConstants.TEST_TYPE_BEFORE_TOTAL -> when (action) { | |||||
AppConstants.DIALOG_START_LEARN -> { | |||||
dialog.dismissAllowingStateLoss() | |||||
//发送动作 : 继续学习 | |||||
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).value = | |||||
LearnEventData(vm.intentData.subjectId, | |||||
vm.intentData.courseId, | |||||
AppConstants.ACTION_COURSE_TEST_START_LEARN) | |||||
finish() | |||||
} | |||||
} | |||||
//课时学前测试,只有一个按钮 开始学习 -> 发送消息开始课时学习 | |||||
AppConstants.TEST_TYPE_BEFORE -> when (action) { | |||||
AppConstants.DIALOG_START_LEARN -> { | |||||
dialog.dismissAllowingStateLoss() | |||||
//发送动作 : 继续学习 | |||||
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).value = | |||||
LearnEventData(vm.intentData.subjectId, | |||||
vm.intentData.courseId, | |||||
AppConstants.ACTION_LESSON_BEFORE_TEST_OVER_START_LEARN).apply { | |||||
leesonPositionIndex = vm.intentData.lesson?.lessonPositionInList!! | |||||
} | |||||
finish() | |||||
} | |||||
} | |||||
//课时学后测试结束弹窗动作 | |||||
AppConstants.TEST_TYPE_AFTER -> when(action){ | |||||
//重新学习 | |||||
AppConstants.DIALOG_LESSON_AFTER_TEST_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() | |||||
//发送动作 : 重新学习 | |||||
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).value = | |||||
LearnEventData(vm.intentData.subjectId, | |||||
vm.intentData.courseId, | |||||
AppConstants.ACTION_LESSON_AFTER_TEST_RELEARN).apply { | |||||
leesonPositionIndex = vm.intentData.lesson?.lessonPositionInList!! | |||||
} | |||||
} | |||||
} | |||||
}.show(childFragmentManager,"lesson_relearn_tip") | |||||
} | |||||
//再测一次 | |||||
AppConstants.DIALOG_LESSON_AFTER_TEST_AGAIN -> { | |||||
//发送动作 : 继续学习 | |||||
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).value = | |||||
LearnEventData(vm.intentData.subjectId, | |||||
vm.intentData.courseId, | |||||
AppConstants.ACTION_LESSON_AFTER_TEST_AGAIN).apply { | |||||
leesonPositionIndex = vm.intentData.lesson?.lessonPositionInList!! | |||||
} | |||||
} | |||||
//下一步 | |||||
AppConstants.DIALOG_LESSON_AFTER_TEST_NEXT -> { | |||||
//发送动作 : 继续学习 | |||||
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).value = | |||||
LearnEventData(vm.intentData.subjectId, | |||||
vm.intentData.courseId, | |||||
AppConstants.ACTION_LESSON_AFTER_TEST_NEXT).apply { | |||||
leesonPositionIndex = vm.intentData.lesson?.lessonPositionInList!! | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
}.show(supportFragmentManager, javaClass.name) | }.show(supportFragmentManager, javaClass.name) | ||||
} | } |
package com.xkl.cdl.module.learn | |||||
import androidx.appcompat.app.AppCompatActivity | |||||
import android.os.Bundle | |||||
import com.xkl.cdl.R | |||||
class LearnExamSpellActivity : AppCompatActivity() { | |||||
override fun onCreate(savedInstanceState: Bundle?) { | |||||
super.onCreate(savedInstanceState) | |||||
setContentView(R.layout.activity_exam_learn_spell) | |||||
} | |||||
} |
import androidx.lifecycle.LifecycleOwner | import androidx.lifecycle.LifecycleOwner | ||||
import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||
import androidx.lifecycle.viewModelScope | import androidx.lifecycle.viewModelScope | ||||
import com.googlecode.protobuf.format.JsonFormat | |||||
import com.suliang.common.eventbus.LiveDataBus | import com.suliang.common.eventbus.LiveDataBus | ||||
import com.suliang.common.extension.createRandomNewChar | import com.suliang.common.extension.createRandomNewChar | ||||
import com.suliang.common.extension.diskIo2Main | import com.suliang.common.extension.diskIo2Main | ||||
import com.xkl.cdl.data.bean.course.ExamBean | import com.xkl.cdl.data.bean.course.ExamBean | ||||
import com.xkl.cdl.data.bean.intentdata.ExamData | import com.xkl.cdl.data.bean.intentdata.ExamData | ||||
import com.xkl.cdl.data.event.LearnEventData | import com.xkl.cdl.data.event.LearnEventData | ||||
import com.xkl.cdl.data.manager.db.DbControlBase | |||||
import com.xkl.cdl.databinding.IncludTestOptionItemBinding | import com.xkl.cdl.databinding.IncludTestOptionItemBinding | ||||
import io.reactivex.rxjava3.core.Observable | import io.reactivex.rxjava3.core.Observable | ||||
import kotlinx.coroutines.delay | import kotlinx.coroutines.delay | ||||
//传递过来的测试数据 | //传递过来的测试数据 | ||||
val intentData : ExamData = DataTransferHolder.instance.getData() | val intentData : ExamData = DataTransferHolder.instance.getData() | ||||
val dbBaseControl : DbControlBase = DbControlBase(intentData.subjectId,intentData.coursePackId,intentData.coursePackType,intentData.courseId,intentData.courseType) | |||||
//测试题数据 | //测试题数据 | ||||
val testData : List<ExamBean> by lazy { | val testData : List<ExamBean> by lazy { | ||||
intentData.testData!! | intentData.testData!! | ||||
//错误集合 | //错误集合 | ||||
private var mLearnEntities : MutableList<LearnEntity> = mutableListOf() | private var mLearnEntities : MutableList<LearnEntity> = mutableListOf() | ||||
//最总上传对象 | |||||
private val record = Record.newBuilder() | |||||
//学前总测试与章节前测试时新的学习错误集合 | //学前总测试与章节前测试时新的学习错误集合 | ||||
var newErrorMap : HashMap<String, Boolean> = hashMapOf() | var newErrorMap : HashMap<String, Boolean> = hashMapOf() | ||||
//标记是否获取下一题 | //标记是否获取下一题 | ||||
var isGetNextIng = false | |||||
private var isGetNextIng = false | |||||
//标记是否为当前错误,暂停获取下一题 | //标记是否为当前错误,暂停获取下一题 | ||||
var isErrorPauseToNext = false | |||||
//标记当前是否倒计时中 | |||||
var isCurrentCounting = false | |||||
private var isErrorPauseToNext = false | |||||
//标记当前是否是否返回后台 | //标记当前是否是否返回后台 | ||||
private var isInBackground = false | private var isInBackground = false | ||||
//标记是否显示返回弹窗 : 停止计时 | //标记是否显示返回弹窗 : 停止计时 | ||||
private var isShowBackDialog = false | private var isShowBackDialog = false | ||||
//倒计时结束 | |||||
val currentCountingTimeOver = MutableLiveData<Boolean>() | |||||
/** 修改获取下一题的标记为true,则在onResume时,自动获取第一题 */ | |||||
fun loadFirst(){ | |||||
isGetNextIng = true | |||||
} | |||||
/** 获取下一题数据 */ | /** 获取下一题数据 */ | ||||
fun loadNext() { | fun loadNext() { | ||||
LogUtil.e("获取下一题") | |||||
isGetNextIng = true | isGetNextIng = true | ||||
mHandler.postDelayed(toNextRunable, toNextTime) | mHandler.postDelayed(toNextRunable, toNextTime) | ||||
} | } | ||||
/** 停止获取下一题 */ | |||||
fun pauseToNext() { | |||||
isErrorPauseToNext = true | |||||
mHandler.removeCallbacks(toNextRunable) | |||||
} | |||||
/** 继续获取下一题 */ | |||||
fun continueToNext() { | |||||
isErrorPauseToNext = false | |||||
isGetNextIng = true | |||||
mHandler.post(toNextRunable) | |||||
} | |||||
/** | /** | ||||
* 显示或关闭的返回弹窗: 影响总计时和当前的一些计时 | |||||
* 显示或关闭的返回弹窗: 不影响总计时,只影响当前的下一题计时或者倒计时 | |||||
* 显示返回弹窗的时机,只会在测试当种,则是结束后则不会再显示此弹窗 | |||||
* @param isShow Boolean | * @param isShow Boolean | ||||
*/ | */ | ||||
fun showOrDissmissBackDialogForTime(isShow: Boolean){ | |||||
fun showOrDismissBackDialogForTime(isShow : Boolean) { | |||||
isShowBackDialog = isShow | isShowBackDialog = isShow | ||||
when{ | |||||
isShow -> { //显示返回弹窗 | |||||
//关闭总计时 | |||||
stopTotalCountTing() | |||||
//停止单题的倒计时 | |||||
mHandler.removeCallbacks(currentCountingTimeRunable) | |||||
when { | |||||
isShowBackDialog -> { //显示返回弹窗 总计时不停,停止下一题和倒计时 | |||||
LogUtil.e("返回弹窗显示:移除下一题,移除单题倒计时") | |||||
mHandler.removeCallbacks(toNextRunable) | |||||
mHandler.removeCallbacks(currentCountingTimeRunnable) | |||||
} | } | ||||
else -> { //关闭返回弹窗 | |||||
//开启总计时 | |||||
startTotalCounting() | |||||
when{ | |||||
!isErrorPauseToNext ->{ // 暂停标志,不处理单题,非暂停 | |||||
when { | |||||
isGetNextIng -> { //是否获取下一题中 | |||||
//本来该获取下一题,现在获取下一题 | |||||
continueToNext() | |||||
} | |||||
else -> { //不是,进行当前题的计时 | |||||
mHandler.post(currentCountingTimeRunable) //现在恢复单题的计时 | |||||
} | |||||
} | |||||
} | |||||
} | |||||
else -> when { //关闭返回弹窗,错题暂停时,不处理,恢复下一题或单题倒计时 | |||||
isErrorPauseToNext -> {} | |||||
isGetNextIng -> continueToNext() | |||||
else -> mHandler.postDelayed(currentCountingTimeRunnable, currentCountingIntervalTime) | |||||
} | } | ||||
} | } | ||||
} | } | ||||
/** onResume | |||||
* isAllOver = true 结束了,不处理了 | |||||
* 开启总计时 | |||||
* 返回弹窗显示,则不处理 | |||||
* 返回弹窗未显示,则该获取下一题时获取下一题,该获取倒计时数据获取倒计时数据 | |||||
*/ | |||||
override fun onResume(owner : LifecycleOwner) { | override fun onResume(owner : LifecycleOwner) { | ||||
super.onResume(owner) | super.onResume(owner) | ||||
if (isAllOver) return | |||||
startTotalCounting() | |||||
isShowBackDialog = false | |||||
when{ | when{ | ||||
!isShowBackDialog -> { //没有显示返回弹窗,判断是否完成测试,没有完成,则进行计时,需要注意是否第一次进入,避免对第一题进行重复计时 | |||||
} | |||||
//弹窗未显示,可直接走弹窗关闭的流程 | |||||
!isShowBackDialog -> showOrDismissBackDialogForTime(isShowBackDialog) | |||||
} | } | ||||
} | } | ||||
/** onPause时,停止总计时 标记进入后台 如果弹窗显示,就只需要关闭总计时就可以了,否则关闭所有计时 */ | |||||
override fun onPause(owner : LifecycleOwner) { | override fun onPause(owner : LifecycleOwner) { | ||||
super.onPause(owner) | super.onPause(owner) | ||||
//关闭所有计时,如果弹窗显示,就只需要关闭总计时就可以了,否则关闭所有计时 | |||||
isInBackground = true | |||||
//测试完成,直接返回,已经停止计时了 | |||||
if (isAllOver) return | |||||
//停止总计时、下一题、倒计时 | |||||
stopTotalCountTing() | |||||
when{ | |||||
//弹窗未显示且非错误暂停,移除计时,因为在弹窗显示和错误暂停的时候,已经移除了下一题和倒计时 | |||||
!isShowBackDialog || !isErrorPauseToNext -> { | |||||
mHandler.removeCallbacks(toNextRunable) | |||||
mHandler.removeCallbacks(currentCountingTimeRunnable) | |||||
} | |||||
} | |||||
} | } | ||||
// /** | |||||
// * 暂停或恢复计时 | |||||
// * @param isPause Boolean | |||||
// */ | |||||
// fun runAllTime(isPause:Boolean){ | |||||
// if (isAllOver) return | |||||
// this.isPause = isPause | |||||
// when{ | |||||
// isPause || isShowBackDialog -> { //暂停计时 | |||||
// //停止总计时 | |||||
// stopTotalCountTing() | |||||
// //移除下一条、单条计时 | |||||
// mHandler.removeCallbacksAndMessages(null) | |||||
// } | |||||
// else -> { //运行计时 | |||||
// //恢复总计时 | |||||
// startTotalCounting() | |||||
// //错误暂停不处理,只进行总计时,否则进行是否在获取下一题的判断 | |||||
// when { | |||||
// isPause -> when { //生命周期暂停后,需要恢复 | |||||
// !isErrorPauseToNext -> when { | |||||
// isGetNextIng -> continueToNext() //本来该获取下一题,现在获取下一题 | |||||
// else -> mHandler.post(currentCountingTimeRunable) //现在恢复单题的计时 | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
/** 停止获取下一题 */ | |||||
fun pauseToNext() { | |||||
LogUtil.e("停止获取下一题") | |||||
isErrorPauseToNext = true | |||||
mHandler.removeCallbacks(toNextRunable) | |||||
} | |||||
/** 继续获取下一题 */ | |||||
fun continueToNext() { | |||||
LogUtil.e("获取下一题") | |||||
isErrorPauseToNext = false | |||||
isGetNextIng = true | |||||
mHandler.post(toNextRunable) | |||||
} | |||||
/** 获取下一题的线程,同时判断是否完成 */ | /** 获取下一题的线程,同时判断是否完成 */ | ||||
isGetNextIng = false | isGetNextIng = false | ||||
currentIndex++ | currentIndex++ | ||||
if (currentIndex >= testData.size) { | if (currentIndex >= testData.size) { | ||||
// TODO: 2022/4/11 标记学习已完成 停止计时 计算分数 设置总消费时间 | |||||
if (isAllOver) return | if (isAllOver) return | ||||
isAllOver = true | isAllOver = true | ||||
//停止计时 | |||||
stopTotalCountTing() | |||||
//计算分数 | |||||
calculateScore() | calculateScore() | ||||
when(intentData.examType){ | |||||
AppConstants.TEST_TYPE_BEFORE -> { //学前总测为lesson添加错误数 与 分数 | |||||
intentData.lesson?.let { | |||||
it.errorNumber = it.errorNumber + newErrorMap.size | |||||
it.beforeTestScore = scoreValue.toDouble() | |||||
} | |||||
} | |||||
AppConstants.TEST_TYPE_AFTER -> { //学后测试为lesson添加分数 | |||||
intentData.lesson?.let { | |||||
it.afterTestScore = scoreValue.toDouble() | |||||
} | |||||
} | |||||
} | |||||
//封装数据 | //封装数据 | ||||
packUpRecord() | packUpRecord() | ||||
//保存数据 后再使用 currentExamBean.value = null 都在saveData中 | //保存数据 后再使用 currentExamBean.value = null 都在saveData中 | ||||
} | } | ||||
} | } | ||||
currentExamBean.value = nextExam | currentExamBean.value = nextExam | ||||
//开始倒计时 | |||||
LogUtil.e("开始单题倒计时") | |||||
mHandler.postDelayed(currentCountingTimeRunnable, currentCountingIntervalTime) | |||||
} | } | ||||
} | } | ||||
} | } | ||||
/** 当前单词的计时 */ | /** 当前单词的计时 */ | ||||
private val currentCountingTimeRunable = object : Runnable { | |||||
private val currentCountingTimeRunnable = object : Runnable { | |||||
override fun run() { | override fun run() { | ||||
when (intentData.courseType) { | when (intentData.courseType) { | ||||
AppConstants.COURSE_TYPE_ENGLISH_VOICE, AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { // 累加时间 | AppConstants.COURSE_TYPE_ENGLISH_VOICE, AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { // 累加时间 | ||||
if (surplusTime > 0) { | if (surplusTime > 0) { | ||||
mHandler.postDelayed(this, currentCountingIntervalTime) | mHandler.postDelayed(this, currentCountingIntervalTime) | ||||
} else { | } else { | ||||
chooseResult(-1) //-1为未答 | |||||
currentCountingTimeOver.value = true | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/** 当前单词开始计时 */ | |||||
fun startCurrentCountingTime() { | |||||
isCurrentCounting = true | |||||
mHandler.postDelayed(currentCountingTimeRunable, currentCountingIntervalTime) | |||||
} | |||||
/** 移除单词计时 */ | |||||
fun stopCurrentCountingTime() { | |||||
isCurrentCounting = false | |||||
mHandler.removeCallbacks(currentCountingTimeRunable) | |||||
} | |||||
/** | /** | ||||
* 四选一结果 | * 四选一结果 | ||||
* @param position Int 位置 -1..3 ,-1 为未答 | * @param position Int 位置 -1..3 ,-1 为未答 | ||||
*/ | */ | ||||
fun chooseResult(position : Int) { | fun chooseResult(position : Int) { | ||||
//移除计时 | //移除计时 | ||||
mHandler.removeCallbacks(currentCountingTimeRunable) | |||||
mHandler.removeCallbacks(currentCountingTimeRunnable) | |||||
LogUtil.e("结果移除单题倒计时") | |||||
//创建单题的测试记录 | //创建单题的测试记录 | ||||
currentExamRecord = ExamRecord.newBuilder().apply { | currentExamRecord = ExamRecord.newBuilder().apply { | ||||
questionId = currentExamBean.value!!.word_id | questionId = currentExamBean.value!!.word_id | ||||
* */ | * */ | ||||
fun spellOver(selectedValue : String, errorSize : Int) { | fun spellOver(selectedValue : String, errorSize : Int) { | ||||
//移除计时 | //移除计时 | ||||
mHandler.removeCallbacks(currentCountingTimeRunable) | |||||
mHandler.removeCallbacks(currentCountingTimeRunnable) | |||||
LogUtil.e("结果移除单题倒计时") | |||||
//创建测试记录 | //创建测试记录 | ||||
currentExamRecord = ExamRecord.newBuilder().apply { | currentExamRecord = ExamRecord.newBuilder().apply { | ||||
questionId = currentExamBean.value!!.word_id | questionId = currentExamBean.value!!.word_id | ||||
val result : Long | val result : Long | ||||
when { | when { | ||||
selectedValue.isEmpty() -> { //未答 | selectedValue.isEmpty() -> { //未答 | ||||
errorLiveData.value = errorLiveData.value!!.plus(1) //错误 | |||||
errorLiveData.value = errorLiveData.value!!.plus(1) | |||||
currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR | currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR | ||||
toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME | toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME | ||||
unAnswerNumber += 1 | unAnswerNumber += 1 | ||||
currentExamRecord!!.answerStatus = AppConstants.TEST_UN_ANSWER | currentExamRecord!!.answerStatus = AppConstants.TEST_UN_ANSWER | ||||
result = AppConstants.TEST_UN_ANSWER | result = AppConstants.TEST_UN_ANSWER | ||||
//创建错误记录 | |||||
createErrorRecord() | |||||
} | } | ||||
errorSize > 0 -> { //错误 | errorSize > 0 -> { //错误 | ||||
errorLiveData.value = errorLiveData.value!!.plus(1) //错误 | |||||
errorLiveData.value = errorLiveData.value!!.plus(1) | |||||
currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR | currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR | ||||
toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME | toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME | ||||
result = AppConstants.TEST_TO_NEXT_ERROR_TIME | result = AppConstants.TEST_TO_NEXT_ERROR_TIME | ||||
//创建错误记录 | |||||
createErrorRecord() | |||||
} | } | ||||
else -> { //正确 | else -> { //正确 | ||||
correctLiveData.value = correctLiveData.value!!.plus(1) | correctLiveData.value = correctLiveData.value!!.plus(1) | ||||
currentExamRecord!!.answerStatus = AppConstants.TEST_CORRECT | currentExamRecord!!.answerStatus = AppConstants.TEST_CORRECT | ||||
toNextTime = AppConstants.TEST_CORRECT | |||||
toNextTime = AppConstants.TEST_TO_NEXT_CORRECT_TIME | |||||
result = AppConstants.TEST_CORRECT | result = AppConstants.TEST_CORRECT | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/** 封装试卷 */ | /** 封装试卷 */ | ||||
private fun packUpRecord() : Record.Builder { | |||||
private fun packUpRecord() { | |||||
val examType = intentData.examType | val examType = intentData.examType | ||||
//生成试卷 | //生成试卷 | ||||
learnExam = LearnExam.newBuilder().apply { | learnExam = LearnExam.newBuilder().apply { | ||||
tag = "android" | tag = "android" | ||||
created = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1) //数据创建时间 | created = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1) //数据创建时间 | ||||
//courseType | //courseType | ||||
val ext = "{\"courseType\": ${intentData.courseType} }" | |||||
ext = "{\"courseType\": ${intentData.courseType} }" | |||||
//词汇量测试: 覆盖率、阶段 | //词汇量测试: 覆盖率、阶段 | ||||
if (examType == AppConstants.TEST_TYPE_NORMAL) { | if (examType == AppConstants.TEST_TYPE_NORMAL) { | ||||
// TODO: 2022/4/14 设置词汇量测试的范围题目和覆盖率 | // TODO: 2022/4/14 设置词汇量测试的范围题目和覆盖率 | ||||
// } | // } | ||||
//最终上传数据 | //最终上传数据 | ||||
val record = Record.newBuilder() //上传对象 | |||||
record.addExam(learnExam) //测试试卷 | record.addExam(learnExam) //测试试卷 | ||||
//时间 | //时间 | ||||
if (examType == AppConstants.TEST_TYPE_BEFORE_TOTAL || examType == AppConstants.TEST_TYPE_BEFORE || examType == AppConstants.TEST_TYPE_AFTER || examType == AppConstants.TEST_TYPE_AFTER_TOTAL) { | if (examType == AppConstants.TEST_TYPE_BEFORE_TOTAL || examType == AppConstants.TEST_TYPE_BEFORE || examType == AppConstants.TEST_TYPE_AFTER || examType == AppConstants.TEST_TYPE_AFTER_TOTAL) { | ||||
if (mLearnEntities.size != 0) { | if (mLearnEntities.size != 0) { | ||||
record.addAllEntity(mLearnEntities) | record.addAllEntity(mLearnEntities) | ||||
} | } | ||||
return record | |||||
} | } | ||||
/** | /** | ||||
it.onNext(true) | it.onNext(true) | ||||
} | } | ||||
// TODO: 2022/4/14 传递保存record信息 | // TODO: 2022/4/14 传递保存record信息 | ||||
// record 已经实例化并已经将数据保存 | |||||
LogUtil.e(JsonFormat.printToString(record.build())) | |||||
}.compose(diskIo2Main()).subscribe({ | }.compose(diskIo2Main()).subscribe({ | ||||
showHideLoading(false) | showHideLoading(false) | ||||
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).value = LearnEventData( | |||||
intentData.subjectId, | |||||
intentData.courseId, | |||||
AppConstants.DATA_COURSE_TEST_BEFORE).apply { | |||||
this.scoreValue = this@LearnExamViewModel.scoreValue | |||||
this.newErrorMap = this@LearnExamViewModel.newErrorMap | |||||
} | |||||
sendEventBus() //返回发送数据 | |||||
//数据保存完成后,通过数据为空进行通知,测试完成 | |||||
currentExamBean.value = null | currentExamBean.value = null | ||||
}, { | }, { | ||||
showHideLoading(false) | showHideLoading(false) | ||||
it.printStackTrace() | it.printStackTrace() | ||||
}) | }) | ||||
} | |||||
/** 发送事件 发送的key为EVENT_COURSE 但动作不同 */ | |||||
private fun sendEventBus(){ | |||||
when(intentData.examType){ | |||||
AppConstants.TEST_TYPE_BEFORE_TOTAL -> { //学前总测发送数据、学前总测发送数据 | |||||
LiveDataBus.withNonSticky<LearnEventData>(AppConstants.EVENT_COURSE).value = LearnEventData( | |||||
intentData.subjectId, | |||||
intentData.courseId, | |||||
AppConstants.DATA_COURSE_BEFORE_TEST_OVER).apply { | |||||
this.scoreValue = this@LearnExamViewModel.scoreValue | |||||
this.newErrorMap = this@LearnExamViewModel.newErrorMap | |||||
} | |||||
} | |||||
AppConstants.TEST_TYPE_BEFORE-> { // 课时学前测试结束发送数据 | |||||
LiveDataBus.withNonSticky<LearnEventData>(AppConstants.EVENT_COURSE).value = LearnEventData( | |||||
intentData.subjectId, | |||||
intentData.courseId, | |||||
AppConstants.DATA_LESSON_BEFORE_TEST_OVER).apply { | |||||
this.newErrorMap = this@LearnExamViewModel.newErrorMap | |||||
intentData.lesson?.let { | |||||
leesonPositionIndex = it.lessonPositionInList | |||||
} | |||||
} | |||||
} | |||||
AppConstants.TEST_TYPE_AFTER -> { //课时学后测试结束发送数据 | |||||
LiveDataBus.withNonSticky<LearnEventData>(AppConstants.EVENT_COURSE).value = LearnEventData( | |||||
intentData.subjectId, | |||||
intentData.courseId, | |||||
AppConstants.DATA_LESSON_AFTER_TEST_OVER).apply { | |||||
this.newErrorMap = this@LearnExamViewModel.newErrorMap | |||||
intentData.lesson?.let { | |||||
leesonPositionIndex = it.lessonPositionInList | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
package com.xkl.cdl.module.learn | |||||
import androidx.appcompat.app.AppCompatActivity | |||||
import android.os.Bundle | |||||
import com.xkl.cdl.R | |||||
class LearnSpellActivity : AppCompatActivity() { | |||||
override fun onCreate(savedInstanceState: Bundle?) { | |||||
super.onCreate(savedInstanceState) | |||||
setContentView(R.layout.activity_learn_spell) | |||||
} | |||||
} |
import android.os.Bundle | import android.os.Bundle | ||||
import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
/** | |||||
* 正常的学习、复习、自动播放 | |||||
*/ | |||||
class LearnWordActivity : AppCompatActivity() { | class LearnWordActivity : AppCompatActivity() { | ||||
override fun onCreate(savedInstanceState: Bundle?) { | override fun onCreate(savedInstanceState: Bundle?) { | ||||
super.onCreate(savedInstanceState) | super.onCreate(savedInstanceState) |
import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
import androidx.recyclerview.widget.LinearLayoutManager | import androidx.recyclerview.widget.LinearLayoutManager | ||||
import com.suliang.common.base.fragment.BaseFragmentVM | import com.suliang.common.base.fragment.BaseFragmentVM | ||||
import com.suliang.common.eventbus.LiveDataBus | |||||
import com.xkl.cdl.adapter.AdapterLesson | import com.xkl.cdl.adapter.AdapterLesson | ||||
import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
import com.xkl.cdl.data.bean.LearnDialogBean | |||||
import com.xkl.cdl.data.bean.course.ExamBean | |||||
import com.xkl.cdl.data.bean.course.Lesson | import com.xkl.cdl.data.bean.course.Lesson | ||||
import com.xkl.cdl.data.bean.intentdata.ExamData | |||||
import com.xkl.cdl.data.event.LearnEventData | |||||
import com.xkl.cdl.data.manager.CourseManager | |||||
import com.xkl.cdl.databinding.FragmentCourseLessonBinding | import com.xkl.cdl.databinding.FragmentCourseLessonBinding | ||||
import com.xkl.cdl.dialog.LearnDialog | |||||
/** | /** | ||||
* 课程章节目录 | * 课程章节目录 | ||||
return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java] | return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java] | ||||
} | } | ||||
private lateinit var adapterLesson: AdapterLesson | |||||
override fun initFragment() { | override fun initFragment() { | ||||
//口语显示顶部的自动播放与跟读模式 | //口语显示顶部的自动播放与跟读模式 | ||||
if (vm.course.courseType == AppConstants.COURSE_TYPE_ENGLISH_SPOKEN){ | if (vm.course.courseType == AppConstants.COURSE_TYPE_ENGLISH_SPOKEN){ | ||||
} | } | ||||
binding.recyclerView.apply { | binding.recyclerView.apply { | ||||
layoutManager = LinearLayoutManager(requireContext(),LinearLayoutManager.VERTICAL,false) | layoutManager = LinearLayoutManager(requireContext(),LinearLayoutManager.VERTICAL,false) | ||||
adapter = AdapterLesson(vm).apply { | |||||
adapterLesson = AdapterLesson(vm).apply { | |||||
onItemClick = onLessonClick | onItemClick = onLessonClick | ||||
} | } | ||||
adapter = adapterLesson | |||||
} | } | ||||
//设置数据 | //设置数据 | ||||
(binding.recyclerView.adapter as AdapterLesson).setData(vm.allLesson.toMutableList()) | (binding.recyclerView.adapter as AdapterLesson).setData(vm.allLesson.toMutableList()) | ||||
} | } | ||||
override fun loadData() { | override fun loadData() { | ||||
//监听数据传递事件 | |||||
listenerLiveBus() | |||||
} | |||||
private fun listenerLiveBus(){ | |||||
//监听数据 | |||||
LiveDataBus.withNonSticky<LearnEventData>(AppConstants.EVENT_COURSE).observe(this) { learnEventData -> | |||||
if (learnEventData.subjectId != vm.course.subjectId || learnEventData.courseId != vm.course.courseId) return@observe | |||||
when (learnEventData.actionFlag) { | |||||
//课时学前测试结束,传递数据回来更新数据 | |||||
AppConstants.DATA_LESSON_BEFORE_TEST_OVER -> { | |||||
// 更新Lesson 更新detail课时学前成绩,错误条目数,测试前错误列表 | |||||
adapterLesson.notifyItemChanged(learnEventData.leesonPositionIndex) | |||||
//获取lesson | |||||
vm.allLesson[learnEventData.leesonPositionIndex].let { | |||||
val key = "${it.chapterId}_${it.lessonId}" | |||||
vm.courseDetail.before.put(key,it.beforeTestScore) | |||||
vm.courseDetail.wrong.put(key,it.errorNumber) | |||||
learnEventData.newErrorMap?.let { it1 -> | |||||
vm.courseDetail.exam_w_r_list.putAll(it1) | |||||
} | |||||
} | |||||
} | |||||
//课时学前测试结束,开始学习 -> 进入学习界面 | |||||
AppConstants.ACTION_LESSON_BEFORE_TEST_OVER_START_LEARN -> { | |||||
} | |||||
//学后测试结束传递数据回来更新数据 | |||||
AppConstants.DATA_LESSON_AFTER_TEST_OVER -> { | |||||
//更新lesson 更新detail成绩 | |||||
adapterLesson.notifyItemChanged(learnEventData.leesonPositionIndex) | |||||
vm.allLesson[learnEventData.leesonPositionIndex].let { | |||||
val key = "${it.chapterId}_${it.lessonId}" | |||||
vm.courseDetail.after.put(key,it.beforeTestScore) | |||||
} | |||||
} | |||||
//学后测试结束,弹窗动作 | |||||
AppConstants.ACTION_LESSON_AFTER_TEST_RELEARN -> { | |||||
//课时学后测试,点击重学 | |||||
// TODO: 2022/4/22 清除当前课时数据,并更新当前课时,后进入学习界面 | |||||
learnEventData.leesonPositionIndex | |||||
} | |||||
AppConstants.ACTION_LESSON_AFTER_TEST_AGAIN -> { | |||||
//学后测试,再测一次 | |||||
loadLessonAfterTest(vm.allLesson[learnEventData.leesonPositionIndex]) | |||||
} | |||||
AppConstants.ACTION_LESSON_AFTER_TEST_NEXT -> { | |||||
//下一课时 or 下一步(提示课程学习完成) | |||||
// TODO: 2022/4/22 判断课程是否学习完成,学习完成,显示课程学习完成弹窗,否则进入下一个没有学习完成的课时,进行学习 | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
/** 课时点击实现 */ | /** 课时点击实现 */ | ||||
private val onLessonClick : (v:View,position: Int, leeson: Lesson) -> Unit = { view, position, entity -> | |||||
// TODO: 2022/3/31 根据情况具体跳转实现 学前、学习、学后 | |||||
private val onLessonClick : (v:View,position: Int, lesson: Lesson) -> Unit = { view, position, entity -> | |||||
when(entity.lessonType){ | when(entity.lessonType){ | ||||
AppConstants.LESSON_TYPE_WORD -> { | AppConstants.LESSON_TYPE_WORD -> { | ||||
if (entity.beforeTestScore == AppConstants.NOT_DOING){ | |||||
if (entity.beforeTestScore == AppConstants.NOT_DOING){ //课时学前测试,没有做 | |||||
//弹窗显示学前测试提示 | |||||
showLessonBeforeTestStartDialog(entity) | |||||
}else if (!entity.learnIsOver){ | }else if (!entity.learnIsOver){ | ||||
}else if (entity.afterTestScore != AppConstants.NOT_DOING){ | }else if (entity.afterTestScore != AppConstants.NOT_DOING){ | ||||
}else{ | |||||
loadLessonAfterTest(entity) | |||||
}else{ //当前课时学习完成的弹窗 | |||||
showLessonAllOverDialog(entity) | |||||
} | } | ||||
} | } | ||||
AppConstants.LESSON_TYPE_SENTENCE -> { | AppConstants.LESSON_TYPE_SENTENCE -> { | ||||
} | } | ||||
} | } | ||||
/** 显示课时前测试开始的弹窗 | |||||
* 先获取数据,然后显示弹窗 | |||||
* */ | |||||
private fun showLessonBeforeTestStartDialog(lesson:Lesson){ | |||||
vm.loadTest(AppConstants.TEST_TYPE_BEFORE,lesson).observe(this){ | |||||
//对话框信息实体 | |||||
val learnDialogBean = LearnDialogBean(AppConstants.DIALOG_TYPE_EXAM_START).apply { | |||||
examType = AppConstants.TEST_TYPE_BEFORE | |||||
chapter_lesson_name = "${lesson.chapterName} ${lesson.lessonName}" | |||||
showTimeCount = CourseManager.expectedTestTime(vm.course.courseType, AppConstants.TEST_TYPE_BEFORE, it) | |||||
} | |||||
LearnDialog.newInstance(learnDialogBean).apply { | |||||
onDialogListener = { action, dialog -> | |||||
dialog.dismissAllowingStateLoss() | |||||
when (action) { | |||||
AppConstants.DIALOG_START_LEARN -> { //跳过测试 继续学习 | |||||
// TODO: 2022/4/21 进入学习界面 | |||||
} | |||||
// 开始测试 | |||||
AppConstants.DIALOG_START_TEST -> startLessonTest(lesson,AppConstants.TEST_TYPE_BEFORE,it) | |||||
} | |||||
} | |||||
}.show(childFragmentManager, "lesson_before_test_start") | |||||
} | |||||
} | |||||
/** 开始课时学后测试 | |||||
* 请求数据后,直接开始跳转 | |||||
*/ | |||||
private fun loadLessonAfterTest(lesson : Lesson){ | |||||
vm.loadTest(AppConstants.TEST_TYPE_AFTER,lesson).observe(this){ | |||||
startLessonTest(lesson,AppConstants.TEST_TYPE_AFTER,it) | |||||
} | |||||
} | |||||
/** 调用数据,开始测试 */ | |||||
private fun startLessonTest(lesson: Lesson, examType:Int,testData : List<ExamBean>){ | |||||
//生成数据 | |||||
val examData = ExamData(vm.course.subjectId, examType, | |||||
lesson.lessonName, | |||||
"${vm.course.courseTitle} ${lesson.chapterName} ${lesson.lessonName}").apply { | |||||
coursePackId = vm.course.coursePackId | |||||
coursePackType = vm.course.coursePackType | |||||
courseId = vm.course.courseId | |||||
courseType = vm.course.courseType | |||||
this.lesson = lesson | |||||
this.testData = testData | |||||
mExamWRMap = vm.courseDetail.exam_w_r_list | |||||
} | |||||
(this@CourseLessonFragment.parentFragment as CourseMainFragment).startExam(examData) | |||||
} | |||||
/** lesson的学习 lessonType 为 word类型 */ | |||||
private fun startLessonLearnForWord(){ | |||||
} | |||||
/** | |||||
* 当前课时学前、学习、学后都完成了的弹窗 | |||||
*/ | |||||
private fun showLessonAllOverDialog(lesson : Lesson){ | |||||
} | |||||
/** | |||||
* 显示课程学习完成的弹窗 | |||||
*/ | |||||
private fun showCourseOverDialog(){ | |||||
} | |||||
} | } |
return ViewModelProvider(this, | return ViewModelProvider(this, | ||||
ViewModelFactory(requireArguments().getInt(AppConfig.INTENT_1)))[CourseMainFragmentViewModel::class.java].apply { | ViewModelFactory(requireArguments().getInt(AppConfig.INTENT_1)))[CourseMainFragmentViewModel::class.java].apply { | ||||
coursePackMainActivityVM = ViewModelProvider(requireActivity())[CoursePackMainActivityViewModel::class.java] | coursePackMainActivityVM = ViewModelProvider(requireActivity())[CoursePackMainActivityViewModel::class.java] | ||||
LogUtil.e("CourseMainFragment coursePackMainActivityVM hashCode -> ${coursePackMainActivityVM.hashCode()}") | |||||
// LogUtil.e("CourseMainFragment coursePackMainActivityVM hashCode -> ${coursePackMainActivityVM.hashCode()}") | |||||
} | } | ||||
} | } | ||||
override fun initFragment() { | override fun initFragment() { | ||||
//设置课程 和 需要操作的类型 | //设置课程 和 需要操作的类型 | ||||
vm.course = vm.coursePackMainActivityVM.coursePack.childrenCourses[vm.courseIndex].apply { | vm.course = vm.coursePackMainActivityVM.coursePack.childrenCourses[vm.courseIndex].apply { | ||||
vm.dbControlBase = DbControlBase(subjectId, coursePackId, courseId, courseType) | |||||
vm.dbControlBase = DbControlBase(subjectId, coursePackId,coursePackType, courseId, courseType) | |||||
} | } | ||||
//监听动作 总测完成,切换到目录页 | //监听动作 总测完成,切换到目录页 | ||||
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).observe(this) { | |||||
LiveDataBus.withNonSticky<LearnEventData>(AppConstants.EVENT_COURSE).observe(this) { | |||||
if (it.subjectId != vm.course.subjectId || it.courseId != vm.course.courseId) return@observe | if (it.subjectId != vm.course.subjectId || it.courseId != vm.course.courseId) return@observe | ||||
when (it.actionFlag) { | when (it.actionFlag) { | ||||
// 学前总测、学后总测 之继续学习 -> 切换到目录页 | // 学前总测、学后总测 之继续学习 -> 切换到目录页 | ||||
AppConstants.ACTION_COURSE_TEST_START_LEARN -> changeFragment(1) | AppConstants.ACTION_COURSE_TEST_START_LEARN -> changeFragment(1) | ||||
//学前总测结束,传递数据回来更新数据 | //学前总测结束,传递数据回来更新数据 | ||||
AppConstants.DATA_COURSE_TEST_BEFORE -> { | |||||
AppConstants.DATA_COURSE_BEFORE_TEST_OVER -> { | |||||
vm.courseDetail.st_before = it.scoreValue.toDouble() //学前总分 | vm.courseDetail.st_before = it.scoreValue.toDouble() //学前总分 | ||||
it.newErrorMap?.run { | it.newErrorMap?.run { | ||||
when { | when { | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
override fun loadData() { | override fun loadData() { | ||||
vm.loadMain().observe(this) { | vm.loadMain().observe(this) { | ||||
changeChildrenFragment() | changeChildrenFragment() | ||||
} | } | ||||
} | } | ||||
LearnExamActivity.newInstance(requireContext()) | LearnExamActivity.newInstance(requireContext()) | ||||
} | } | ||||
override fun onDestroy() { | |||||
super.onDestroy() | |||||
//退出本课程后移除该课程的事件监听 | |||||
// LiveDataBus.remove(AppConstants.EVENT_COURSE) | |||||
} | |||||
inner class ViewModelFactory(private val courseIndex : Int) : ViewModelProvider.Factory { | inner class ViewModelFactory(private val courseIndex : Int) : ViewModelProvider.Factory { | ||||
override fun <T : ViewModel?> create(modelClass : Class<T>) : T { | override fun <T : ViewModel?> create(modelClass : Class<T>) : T { |
package com.xkl.cdl.module.m_center_learn.coursechildren | package com.xkl.cdl.module.m_center_learn.coursechildren | ||||
import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||
import com.suliang.common.base.activity.ToastEvent | |||||
import com.suliang.common.base.viewmodel.BaseViewModel | import com.suliang.common.base.viewmodel.BaseViewModel | ||||
import com.suliang.common.extension.diskIo2DiskIo | |||||
import com.suliang.common.extension.diskIo2Main | import com.suliang.common.extension.diskIo2Main | ||||
import com.xkl.cdl.data.bean.LearnWord | |||||
import com.xkl.cdl.data.bean.course.Course | import com.xkl.cdl.data.bean.course.Course | ||||
import com.xkl.cdl.data.bean.course.CourseDetail | import com.xkl.cdl.data.bean.course.CourseDetail | ||||
import com.xkl.cdl.data.bean.course.ExamBean | import com.xkl.cdl.data.bean.course.ExamBean | ||||
import com.xkl.cdl.data.bean.course.Lesson | import com.xkl.cdl.data.bean.course.Lesson | ||||
import com.xkl.cdl.data.bean.intentdata.LearnData | |||||
import com.xkl.cdl.data.manager.db.DBCourseManager | import com.xkl.cdl.data.manager.db.DBCourseManager | ||||
import com.xkl.cdl.data.manager.db.DbControlBase | import com.xkl.cdl.data.manager.db.DbControlBase | ||||
import com.xkl.cdl.data.repository.DataRepository | import com.xkl.cdl.data.repository.DataRepository | ||||
import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | ||||
import io.reactivex.rxjava3.core.Observable | import io.reactivex.rxjava3.core.Observable | ||||
import io.reactivex.rxjava3.core.Observer | |||||
import io.reactivex.rxjava3.disposables.Disposable | |||||
import io.reactivex.rxjava3.functions.BiFunction | |||||
import java.util.* | import java.util.* | ||||
import kotlin.collections.HashMap | import kotlin.collections.HashMap | ||||
//获取课程的复习数据 | //获取课程的复习数据 | ||||
//获取课程的章节数据 | //获取课程的章节数据 | ||||
fun loadMain() : MutableLiveData<Boolean> { | fun loadMain() : MutableLiveData<Boolean> { | ||||
showHideLoading(true) | |||||
// showHideLoading(true) | |||||
val mutableLiveData = MutableLiveData<Boolean>() | val mutableLiveData = MutableLiveData<Boolean>() | ||||
Observable.concat(DataRepository.getCourseStatistics().flatMap { | |||||
Observable.zip(DataRepository.getCourseStatistics().flatMap { | |||||
courseDetail = it | courseDetail = it | ||||
return@flatMap DataRepository.getCourseAllLesson(dbControlBase, it) | return@flatMap DataRepository.getCourseAllLesson(dbControlBase, it) | ||||
}, DataRepository.getReviewData(), DataRepository.getCourseCollect()).compose(diskIo2DiskIo()) | |||||
.subscribe(object : Observer<Any> { | |||||
override fun onSubscribe(d : Disposable?) { | |||||
} | |||||
override fun onNext(t : Any?) { | |||||
t?.let { it -> | |||||
if (it is List<*>) { | |||||
allLesson = it as List<Lesson> | |||||
} | |||||
} | |||||
} | |||||
override fun onError(e : Throwable?) { | |||||
e?.printStackTrace() | |||||
showHideLoading(false) | |||||
showToast(ToastEvent(content = "数据加载异常")) | |||||
} | |||||
override fun onComplete() { | |||||
showHideLoading(false) | |||||
mutableLiveData.postValue(true) | |||||
} | |||||
}) | |||||
}.flatMap { | |||||
allLesson = it | |||||
return@flatMap DataRepository.getCourseCollect() | |||||
}, | |||||
DataRepository.getReviewData(), | |||||
BiFunction<HashMap<String, Long>, Array<String>, Boolean> { t1 : HashMap<String, Long>?, t2 : Array<String>? -> | |||||
true | |||||
}).compose(diskIo2Main()).subscribe { | |||||
// showHideLoading(false) | |||||
mutableLiveData.value = it | |||||
} | |||||
return mutableLiveData | return mutableLiveData | ||||
} | } | ||||
*/ | */ | ||||
fun loadTest(testType : Int, lesson : Lesson? = null) : MutableLiveData<List<ExamBean>> { | fun loadTest(testType : Int, lesson : Lesson? = null) : MutableLiveData<List<ExamBean>> { | ||||
val result = MutableLiveData<List<ExamBean>>() | val result = MutableLiveData<List<ExamBean>>() | ||||
// showHideLoading(true) | |||||
Observable.create<List<ExamBean>> { | Observable.create<List<ExamBean>> { | ||||
it.onNext(DBCourseManager.queryLearnTest(dbControlBase, testType, lesson)) | it.onNext(DBCourseManager.queryLearnTest(dbControlBase, testType, lesson)) | ||||
it.onComplete() | it.onComplete() | ||||
}.compose(diskIo2Main()).subscribe({ | }.compose(diskIo2Main()).subscribe({ | ||||
// showHideLoading(false) | |||||
result.value = it | result.value = it | ||||
}, { | }, { | ||||
it.printStackTrace() | it.printStackTrace() | ||||
// showHideLoading(false) | |||||
}) | }) | ||||
return result | return result | ||||
fun modifyLessonErrorNumber() { | fun modifyLessonErrorNumber() { | ||||
//重设detail中的错误数, exam_w_r_list为所有的测试前错误,因为时学前总测,所以,可以根据这个字段来计算每个课时的错误数 | //重设detail中的错误数, exam_w_r_list为所有的测试前错误,因为时学前总测,所以,可以根据这个字段来计算每个课时的错误数 | ||||
//记录错误数: key :chapterId_lessonId value :错误数 | //记录错误数: key :chapterId_lessonId value :错误数 | ||||
val errorNumber = hashMapOf<String,Int>() | |||||
val errorNumber = hashMapOf<String, Int>() | |||||
courseDetail.exam_w_r_list.keys.forEach { | courseDetail.exam_w_r_list.keys.forEach { | ||||
val split = it.split("_") | val split = it.split("_") | ||||
if (split.size == 3){ | |||||
if (split.size == 3) { | |||||
val errorKey = "${split[0]}_${split[1]}" | val errorKey = "${split[0]}_${split[1]}" | ||||
errorNumber[errorKey] = errorNumber.getOrElse(errorKey,{0}) + 1 | |||||
errorNumber[errorKey] = errorNumber.getOrElse(errorKey, { 0 }) + 1 | |||||
} | } | ||||
} | } | ||||
courseDetail.wrong.putAll(errorNumber) | courseDetail.wrong.putAll(errorNumber) | ||||
//更新lesson中的错误数 | //更新lesson中的错误数 | ||||
allLesson.forEach { | allLesson.forEach { | ||||
val key = "${it.chapterId}_${it.lessonId}" | val key = "${it.chapterId}_${it.lessonId}" | ||||
it.errorNumber = errorNumber.getOrElse(key,{0}) | |||||
it.errorNumber = errorNumber.getOrElse(key, { 0 }) | |||||
} | } | ||||
} | } | ||||
/** | |||||
* 查询获取学习数据 单词类、口语词汇、句型, 音标课程、识字、拼音、作文知识点学习 | |||||
* @param lesson Lesson 课时 | |||||
* @return MutableLiveData<LearnData> | |||||
*/ | |||||
fun loadLessonLearnForWord(lesson : Lesson): MutableLiveData<LearnData>{ | |||||
val result = MutableLiveData<LearnData>() | |||||
Observable.create<List<LearnWord>> { | |||||
it.onNext(DBCourseManager.queryLearnDataForWord(dbControlBase, lesson)) | |||||
it.onComplete() | |||||
}.compose(diskIo2Main()) | |||||
.subscribe { | |||||
result.value = LearnData(lesson).apply { | |||||
learnWordList = it | |||||
} | |||||
} | |||||
return result | |||||
} | |||||
} | } |
binding.tvCountTip.text = CourseManager.expectedTestTime(vm.course.courseType,totalTestType,testData!!) | binding.tvCountTip.text = CourseManager.expectedTestTime(vm.course.courseType,totalTestType,testData!!) | ||||
when (totalTestType) { | when (totalTestType) { | ||||
AppConstants.TEST_TYPE_BEFORE_TOTAL -> { | AppConstants.TEST_TYPE_BEFORE_TOTAL -> { | ||||
// TODO: 2022/4/21 需要把开始学习给取消了 | |||||
binding.button1.visibility = View.VISIBLE | |||||
//学前总测 | //学前总测 | ||||
binding.run { | binding.run { | ||||
tvTitle.setText(R.string.test_total_before_title) | tvTitle.setText(R.string.test_total_before_title) |
import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||
import android.os.Bundle | import android.os.Bundle | ||||
import com.suliang.common.base.activity.BaseActivity | import com.suliang.common.base.activity.BaseActivity | ||||
import com.suliang.common.util.LogUtil | |||||
import com.suliang.common.util.SpUtils | |||||
import com.suliang.common.util.file.FileUtil | |||||
import com.suliang.common.util.thread.AppExecutors | import com.suliang.common.util.thread.AppExecutors | ||||
import com.xkl.cdl.data.manager.CourseManager | import com.xkl.cdl.data.manager.CourseManager | ||||
import com.xkl.cdl.data.manager.FilePathManager | |||||
import com.xkl.cdl.data.manager.db.DbCoursePackManager | import com.xkl.cdl.data.manager.db.DbCoursePackManager | ||||
import com.xkl.cdl.databinding.ActivitySplashBinding | import com.xkl.cdl.databinding.ActivitySplashBinding | ||||
import com.xkl.cdl.dialog.LearnDialog | |||||
import com.xkl.cdl.module.main.MainActivity | import com.xkl.cdl.module.main.MainActivity | ||||
import mobile_cache.Mobile_cache | |||||
import java.io.File | |||||
import java.util.concurrent.TimeUnit | import java.util.concurrent.TimeUnit | ||||
@SuppressLint("CustomSplashScreen") | @SuppressLint("CustomSplashScreen") |
package com.xkl.cdl.util | |||||
import com.xkl.cdl.data.bean.BaseWord | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/22 17:12 | |||||
* Describe: 规则 | |||||
* @property originList 源数据 | |||||
* @property cycleTime 循环次数 | |||||
*/ | |||||
class LearnRuleUtil<T : BaseWord>(private val originList : List<T>, private val cycleTime : Int) { | |||||
//源数据学习位置 | |||||
private var originLearnPosition : Int = 0 | |||||
//学习列表位置 | |||||
private var currentLearnPosition : Int = -1 | |||||
//学习列表数据 | |||||
private var currentList = mutableListOf<T?>(null) | |||||
//当前答题是否错误 | |||||
private var currentIsError = false | |||||
//学前总和课时前测试错误集合 | |||||
var examErrorMap = hashMapOf<String,Boolean>() | |||||
//新错误集合 | |||||
var newErrorMap = hashMapOf<String,Boolean>() | |||||
//第一次点击正确的集合, 如果后面再点击错误,需要移除并添加到错误集合中 | |||||
var newCorrectMap = hashMapOf<String,Boolean>() | |||||
fun getNext(): T? { | |||||
if (currentIsError || isHaveNext()){ | |||||
return nextWord() | |||||
} | |||||
return null | |||||
} | |||||
/** | |||||
* 是否还有下一单词 | |||||
* @return Boolean | |||||
*/ | |||||
private fun isHaveNext():Boolean{ | |||||
//如果当前学习下标为-1,即第一次进入学习,直接返true | |||||
if (currentLearnPosition == -1) return true | |||||
//元数据是否学习完成,未学习完成,则还有学习数据, 元数据学习完成,还需要判断当前学习列表是否学习完成 | |||||
if (!isLearnOverForOrigin()) return true | |||||
//判断当前列表是否学习完成 | |||||
val currentLearnWord = currentList[currentLearnPosition]!! | |||||
var nextWord: T? = null | |||||
//循环找寻与当前word不一样的数据 | |||||
if (currentLearnPosition + 1 < currentList.size-1) { | |||||
for (i in currentLearnPosition + 1 .. currentList.size - 1) { | |||||
currentList[i]?.let { | |||||
if (currentLearnWord.wordId != it.wordId ){ | |||||
nextWord = it | |||||
} | |||||
} | |||||
if (nextWord != null) break | |||||
} | |||||
} | |||||
//如果获取不到下一个不为空的数据,则还有下一条,否则结束了 | |||||
return nextWord != null | |||||
} | |||||
/** | |||||
* 源数据是否学习完成 | |||||
*/ | |||||
fun isLearnOverForOrigin():Boolean{ | |||||
return originLearnPosition >= originList.size -1 | |||||
} | |||||
/** | |||||
* 获取下一条数据 | |||||
*/ | |||||
private fun nextWord(): T?{ | |||||
//声明下一条数据 | |||||
var next : T? = null | |||||
//当前列表 学习位置+1 | |||||
currentLearnPosition ++ | |||||
when{ | |||||
//从当前集合获取 | |||||
currentList.size > currentLearnPosition -> { | |||||
} | |||||
//从源数据据列表获取 | |||||
else -> { | |||||
originLearnPosition++ | |||||
if (!isLearnOverForOrigin()) { | |||||
next = originList[originLearnPosition] | |||||
//元数据如果是课时学前或总测的错误数据,那么就需要按照小循环插入 | |||||
} | |||||
} | |||||
} | |||||
return next | |||||
} | |||||
/** 是否在错误列表中 */ | |||||
} |
<?xml version="1.0" encoding="utf-8"?> | |||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||||
xmlns:app="http://schemas.android.com/apk/res-auto" | |||||
xmlns:tools="http://schemas.android.com/tools" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="match_parent" | |||||
tools:context=".module.learn.LearnExamSpellActivity"> | |||||
</androidx.constraintlayout.widget.ConstraintLayout> |
<?xml version="1.0" encoding="utf-8"?> | |||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||||
xmlns:app="http://schemas.android.com/apk/res-auto" | |||||
xmlns:tools="http://schemas.android.com/tools" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="match_parent" | |||||
tools:context=".module.learn.LearnSpellActivity"> | |||||
</androidx.constraintlayout.widget.ConstraintLayout> |
app:layout_constraintStart_toEndOf="@+id/tv_left" | app:layout_constraintStart_toEndOf="@+id/tv_left" | ||||
android:background="@color/gray_1" | android:background="@color/gray_1" | ||||
app:layout_constraintTop_toTopOf="@+id/tv_right" | app:layout_constraintTop_toTopOf="@+id/tv_right" | ||||
app:layout_constraintBottom_toBottomOf="@+id/tv_right" | |||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
app:layout_constraintBottom_toBottomOf="@+id/tv_right"/> | |||||
<!--左边按钮--> | <!--左边按钮--> | ||||
<TextView | <TextView | ||||
app:layout_constraintEnd_toStartOf="@+id/vSplit" | app:layout_constraintEnd_toStartOf="@+id/vSplit" | ||||
app:layout_constraintTop_toTopOf="@+id/vSplit" | app:layout_constraintTop_toTopOf="@+id/vSplit" | ||||
app:layout_constraintBottom_toBottomOf="@+id/vSplit" | app:layout_constraintBottom_toBottomOf="@+id/vSplit" | ||||
android:visibility="gone" | |||||
tools:visibility="visible" | |||||
/> | /> | ||||
</androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> |
android:src="@drawable/ic_delete" | android:src="@drawable/ic_delete" | ||||
android:visibility="gone" | android:visibility="gone" | ||||
app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
app:layout_constraintTop_toTopOf="parent" | |||||
tools:visibility="visible" /> | |||||
app:layout_constraintTop_toTopOf="parent"/> | |||||
<TextView | <TextView | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toTopOf="parent" | app:layout_constraintTop_toTopOf="parent" | ||||
tools:text="%d分" | tools:text="%d分" | ||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
android:visibility="gone"/> | |||||
<TextView | <TextView | ||||
android:id="@+id/tv_tip" | android:id="@+id/tv_tip" | ||||
app:layout_constraintEnd_toEndOf="@+id/tv_score" | app:layout_constraintEnd_toEndOf="@+id/tv_score" | ||||
app:layout_constraintStart_toStartOf="@+id/tv_score" | app:layout_constraintStart_toStartOf="@+id/tv_score" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_score" | app:layout_constraintTop_toBottomOf="@+id/tv_score" | ||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
android:visibility="gone"/> | |||||
<TextView | <TextView | ||||
android:id="@+id/tv_title" | android:id="@+id/tv_title" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_tip" | app:layout_constraintTop_toBottomOf="@+id/tv_tip" | ||||
tools:text="课时学前测试" | tools:text="课时学前测试" | ||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
android:visibility="gone"/> | |||||
<TextView | <TextView | ||||
android:id="@+id/tv_lesson_name" | android:id="@+id/tv_lesson_name" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_title" | app:layout_constraintTop_toBottomOf="@+id/tv_title" | ||||
tools:text="[%s%s]" | tools:text="[%s%s]" | ||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
android:visibility="gone"/> | |||||
<TextView | <TextView | ||||
android:id="@+id/tv_count_time" | android:id="@+id/tv_count_time" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_lesson_name" | app:layout_constraintTop_toBottomOf="@+id/tv_lesson_name" | ||||
tools:text="@string/test_count_time_format" | tools:text="@string/test_count_time_format" | ||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
android:visibility="gone"/> | |||||
<TextView | <TextView | ||||
android:id="@+id/tv_tip_1" | android:id="@+id/tv_tip_1" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_title" | app:layout_constraintTop_toBottomOf="@+id/tv_title" | ||||
tools:text="@string/test_before_test_over_tip" | tools:text="@string/test_before_test_over_tip" | ||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
android:visibility="gone"/> | |||||
<include | <include | ||||
android:id="@+id/inc_statistics_number" | android:id="@+id/inc_statistics_number" | ||||
app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_tip_1" | app:layout_constraintTop_toBottomOf="@+id/tv_tip_1" | ||||
android:visibility="gone"/> | |||||
<TextView | |||||
android:id="@+id/tv_top" | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
android:layout_marginTop="@dimen/global_spacing" | |||||
android:gravity="center" | |||||
tools:text="再测一次" | |||||
android:textColor="@color/theme_color" | |||||
android:textSize="@dimen/smallSize" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintTop_toBottomOf="@id/inc_statistics_number" | |||||
android:visibility="gone" | android:visibility="gone" | ||||
tools:visibility="visible"/> | |||||
/> | |||||
<TextView | |||||
android:id="@+id/tv_top_1" | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
android:layout_marginTop="10dp" | |||||
android:gravity="center" | |||||
tools:text="再测一次" | |||||
android:textColor="@color/theme_color" | |||||
android:textSize="@dimen/smallSize" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintTop_toBottomOf="@id/tv_top" | |||||
android:visibility="gone" | |||||
/> | |||||
android:layout_height="wrap_content" | android:layout_height="wrap_content" | ||||
app:barrierDirection="bottom" | app:barrierDirection="bottom" | ||||
app:barrierMargin="@dimen/smallerSize" | app:barrierMargin="@dimen/smallerSize" | ||||
app:constraint_referenced_ids="tv_count_time,inc_statistics_number" /> | |||||
app:constraint_referenced_ids="tv_count_time,inc_statistics_number,tv_top,tv_top_1" /> | |||||
<View | <View | ||||
android:id="@+id/bottom_top_line" | android:id="@+id/bottom_top_line" | ||||
android:layout_width="wrap_content" | android:layout_width="wrap_content" | ||||
android:layout_height="@dimen/line_height" | |||||
android:layout_height="1dp" | |||||
app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
android:background="@color/gray_3" | android:background="@color/gray_3" | ||||
<View | <View | ||||
android:id="@+id/vSplit" | android:id="@+id/vSplit" | ||||
android:layout_width="@dimen/line_height" | |||||
android:layout_width="1dp" | |||||
android:layout_height="0dp" | android:layout_height="0dp" | ||||
app:layout_constraintEnd_toStartOf="@+id/tv_right" | app:layout_constraintEnd_toStartOf="@+id/tv_right" | ||||
app:layout_constraintStart_toEndOf="@+id/tv_left" | app:layout_constraintStart_toEndOf="@+id/tv_left" | ||||
android:background="@color/gray_3" | android:background="@color/gray_3" | ||||
app:layout_constraintTop_toTopOf="@+id/tv_right" | app:layout_constraintTop_toTopOf="@+id/tv_right" | ||||
app:layout_constraintBottom_toBottomOf="@+id/tv_right" | app:layout_constraintBottom_toBottomOf="@+id/tv_right" | ||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
android:visibility="gone"/> | |||||
<!--左边按钮--> | <!--左边按钮--> | ||||
<TextView | <TextView | ||||
app:layout_constraintTop_toTopOf="@+id/vSplit" | app:layout_constraintTop_toTopOf="@+id/vSplit" | ||||
app:layout_constraintBottom_toBottomOf="@+id/vSplit" | app:layout_constraintBottom_toBottomOf="@+id/vSplit" | ||||
android:visibility="gone" | android:visibility="gone" | ||||
tools:visibility="visible" | |||||
/> | /> | ||||
<!--学前总测试控制显示的布局id--> | |||||
<!-- <!–学前总测试控制显示的布局id–>--> | |||||
<!-- <androidx.constraintlayout.widget.Group--> | |||||
<!-- android:id="@+id/group_total_test"--> | |||||
<!-- android:layout_width="wrap_content"--> | |||||
<!-- android:layout_height="wrap_content"--> | |||||
<!-- app:constraint_referenced_ids="tv_score,tv_tip_1,tv_title,inc_statistics_number"--> | |||||
<!-- android:visibility="invisible"--> | |||||
<!-- />--> | |||||
<!-- <!–课时学前测试开始控制显示的布局id–>--> | |||||
<!-- <androidx.constraintlayout.widget.Group--> | |||||
<!-- android:id="@+id/group_before_test_start"--> | |||||
<!-- android:layout_width="wrap_content"--> | |||||
<!-- android:layout_height="wrap_content"--> | |||||
<!-- app:constraint_referenced_ids="tv_title,tv_lesson_name,tv_count_time,tv_left,vSplit"--> | |||||
<!-- />--> | |||||
<!--课时学前测试结束绑定的布局组 --> | |||||
<!-- | |||||
<androidx.constraintlayout.widget.Group | |||||
android:id="@+id/group_before_test_start" | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
app:constraint_referenced_ids="tv_score,tv_tip,tv_tip_1,tv_title,inc_statistics_number" | |||||
/> | |||||
--> <!--课时学后测试结束绑定的布局组 --> | |||||
<androidx.constraintlayout.widget.Group | <androidx.constraintlayout.widget.Group | ||||
android:id="@+id/group_total_test" | |||||
android:layout_width="wrap_content" | android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | android:layout_height="wrap_content" | ||||
app:constraint_referenced_ids="tv_score,tv_tip_1,tv_title,inc_statistics_number" | |||||
app:constraint_referenced_ids="tv_score,tv_tip,tv_title,inc_statistics_number,tv_top,tv_left,vSplit,tv_top_1" | |||||
/> | /> | ||||
</androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> |
android:layout_marginStart="10dp" | android:layout_marginStart="10dp" | ||||
android:layout_marginEnd="10dp" | android:layout_marginEnd="10dp" | ||||
android:background="@color/red_3" | android:background="@color/red_3" | ||||
app:layout_constraintLeft_toLeftOf="parent" | |||||
app:layout_constraintRight_toRightOf="parent" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintTop_toBottomOf="@+id/tv_rule_tip" | app:layout_constraintTop_toBottomOf="@+id/tv_rule_tip" | ||||
android:layout_marginTop="12dp" | android:layout_marginTop="12dp" | ||||
app:layout_constrainedWidth="true" /> | |||||
app:layout_constrainedWidth="true"/> | |||||
</androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> |
<string name="test_spell_tip">请从左至右依次点击选择正确的选项</string> | <string name="test_spell_tip">请从左至右依次点击选择正确的选项</string> | ||||
<string name="pause">暂停</string> | <string name="pause">暂停</string> | ||||
<string name="continue_">继续</string> | <string name="continue_">继续</string> | ||||
<string name="test_tip">请选择本单词正确的意义栏</string> | |||||
<string name="test_tip">请选择正确的意义栏</string> | |||||
<string name="dialog_test_not_over">测试还未结束,你确定要退出吗?</string> | <string name="dialog_test_not_over">测试还未结束,你确定要退出吗?</string> | ||||
<string name="dialog_test_not_over_tip">退出后系统将不会保存你的本次测试数据</string> | <string name="dialog_test_not_over_tip">退出后系统将不会保存你的本次测试数据</string> | ||||
<string name="quit">退出</string> | <string name="quit">退出</string> | ||||
<string name="cancel">取消</string> | <string name="cancel">取消</string> | ||||
<string name="lesson_relearn_title">重新学习将清空本课时的学习记录</string> | |||||
<string name="lesson_relearn_content">但不会清空测试与备忘本数据</string> | |||||
<string name="sure">确定</string> | |||||
</resources> | </resources> |
const val INTENT_2 = "intent_2" | const val INTENT_2 = "intent_2" | ||||
const val INTENT_3 = "intent_3" | const val INTENT_3 = "intent_3" | ||||
const val INTENT_4 = "intent_4" | const val INTENT_4 = "intent_4" | ||||
//音量存放地址 | |||||
val VOICE : String = "voice" | |||||
} | } |
vm.pageEvent.observe(this) { startActivity(it) } | vm.pageEvent.observe(this) { startActivity(it) } | ||||
vm.toastEvent.observe(this){ showToast(it) } | vm.toastEvent.observe(this){ showToast(it) } | ||||
vm.loadingEvent.observe(this) { | vm.loadingEvent.observe(this) { | ||||
LogUtil.e("监听到 fragment vm 的 loadingEvent isShow $it") | |||||
showHideLoading(it) | showHideLoading(it) | ||||
} | } | ||||
} | } |
/** | /** | ||||
* author suliang | * author suliang | ||||
* create 2022/3/15 10:51 | * create 2022/3/15 10:51 | ||||
* Describe: 非粘性LiveData | |||||
* Describe: 非粘性LiveData ,需要先设置监听再发送数据,即后注册的监听收不到以前的监听 | |||||
*/ | */ | ||||
class NonStickyMutableLiveData<T> : MutableLiveData<T>() { | class NonStickyMutableLiveData<T> : MutableLiveData<T>() { | ||||
private var stickFlag : Boolean = false | |||||
// private var stickFlag : Boolean = false | |||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { | override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { | ||||
super.observe(owner, observer) | super.observe(owner, observer) | ||||
if (!stickFlag){ | |||||
hook(observer) | |||||
stickFlag = true | |||||
} | |||||
hook(observer) | |||||
// if (!stickFlag){ | |||||
// hook(observer) | |||||
// stickFlag = true | |||||
// } | |||||
} | } | ||||
private fun hook(observer: Observer<in T>) = try { | private fun hook(observer: Observer<in T>) = try { |
fun View.click(clickListener : ((view : View) -> Unit)?){ | fun View.click(clickListener : ((view : View) -> Unit)?){ | ||||
if (clickListener == null) { | if (clickListener == null) { | ||||
setOnClickListener(null) | setOnClickListener(null) | ||||
isClickable = false | |||||
return | return | ||||
} | } | ||||
val minTime = 500L | val minTime = 500L |
file?.let { | file?.let { | ||||
if (it.exists()) file.mkdirs() | if (it.exists()) file.mkdirs() | ||||
} | } | ||||
LogUtil.d("应用内置目录文件: ${file?.path}") | |||||
// LogUtil.d("应用内置目录文件: ${file?.path}") | |||||
return file ?: throw NullPointerException("Expression 'file' must not be null") | return file ?: throw NullPointerException("Expression 'file' must not be null") | ||||
} | } | ||||
fun play(url: String, position: Int = 0,listener: IMPListener? = null) | fun play(url: String, position: Int = 0,listener: IMPListener? = null) | ||||
/**播放asset资源 */ | /**播放asset资源 */ | ||||
fun play(asset:String,listener : IMPListener? = null) | |||||
fun playAsset(asset:String, listener : IMPListener? = null) | |||||
/** 暂停播放 ,如果没有播放文件则会忽略*/ | /** 暂停播放 ,如果没有播放文件则会忽略*/ | ||||
fun pausePlay() | fun pausePlay() |
mpUtil.play(url,position,listener) | mpUtil.play(url,position,listener) | ||||
} | } | ||||
override fun play(asset : String, listener : IMPListener?) { | |||||
mpUtil.play(asset, listener) | |||||
override fun playAsset(asset : String, listener : IMPListener?) { | |||||
mpUtil.playAsset(asset, listener) | |||||
} | } | ||||
override fun pausePlay() { | override fun pausePlay() { |
} | } | ||||
override fun play(url: String, position: Int,listener: IMPListener?) { | override fun play(url: String, position: Int,listener: IMPListener?) { | ||||
isAssetPath = false | |||||
listener?.let { | listener?.let { | ||||
this.listener = it | this.listener = it | ||||
} | } | ||||
* @param asset String asset文件名称 | * @param asset String asset文件名称 | ||||
* @param listener IMPListener? 监听 | * @param listener IMPListener? 监听 | ||||
*/ | */ | ||||
override fun play(asset : String, listener : IMPListener?) { | |||||
override fun playAsset(asset : String, listener : IMPListener?) { | |||||
isAssetPath = true | |||||
if (asset.isEmpty()) { | if (asset.isEmpty()) { | ||||
handleError("asset 参数为null ") | handleError("asset 参数为null ") | ||||
return | return | ||||
it.mediaPlayer = MediaPlayer() | it.mediaPlayer = MediaPlayer() | ||||
} | } | ||||
mediaPlayer?.apply { | mediaPlayer?.apply { | ||||
setOnPreparedListener(mOnPreparedLister) | |||||
setOnPreparedListener(this@MPUtil) | |||||
setOnErrorListener(this@MPUtil) | setOnErrorListener(this@MPUtil) | ||||
setOnCompletionListener(this@MPUtil) | setOnCompletionListener(this@MPUtil) | ||||
setOnInfoListener(this@MPUtil) | setOnInfoListener(this@MPUtil) | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private val mOnPreparedLister: MediaPlayer.OnPreparedListener = MediaPlayer.OnPreparedListener { | |||||
} | |||||
/** 异步准备完毕 ,开始播放*/ | /** 异步准备完毕 ,开始播放*/ | ||||
private fun handlePrepared() { | private fun handlePrepared() { | ||||
handleError("状态异常,这里只能是 PREPARED 而现在是:$currentState") | handleError("状态异常,这里只能是 PREPARED 而现在是:$currentState") | ||||
} | } | ||||
} | } | ||||
// private val mOnSeekCompleteListener: MediaPlayer.OnSeekCompleteListener = MediaPlayer.OnSeekCompleteListener { | |||||
// LogUtil.i("mOnSeekCompleteListener 回调") | |||||
// handleSeekComplete() | |||||
// } | |||||
// | |||||
// | |||||
// private val mOnCompleteListener: MediaPlayer.OnCompletionListener = MediaPlayer.OnCompletionListener { | |||||
// handleComplete() | |||||
// } | |||||
// | |||||
// | |||||
// private val mOnErrorListener: MediaPlayer.OnErrorListener = MediaPlayer.OnErrorListener { _, what, extra -> | |||||
// handleError("mOnErrorListener 异常调用 $extra") | |||||
// true | |||||
// } | |||||
// | |||||
// private val mOnInfoListener: MediaPlayer.OnInfoListener = MediaPlayer.OnInfoListener { mp, what, extra -> | |||||
// false | |||||
// } | |||||
private fun handleSeekComplete() { | private fun handleSeekComplete() { | ||||
when (currentState) { | when (currentState) { | ||||
EMediaState.PREPARED -> handlePrepared() | EMediaState.PREPARED -> handlePrepared() |