@@ -36,9 +36,11 @@ | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_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_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_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_main.xml" value="0.5" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> |
@@ -45,7 +45,8 @@ image包: 实现了图片加载的封装 | |||
课时后测试将包含 小游戏练习、再测一次、重新学习、下一步(进入学后总测试)或者下一课时(为当前课时的下一课时), | |||
需要考虑的是,下一步或者下一课时的判断为循环判断,如,共10课时,现在点击学习的第5个,如果后面几个课时都学习完后,而1课时没有学习完成,下一课时将直接进入1课时的学习, | |||
而下一步是在所有课时都学习完成的情况下,直接进入学后总测试的界面。 | |||
学前总测试 :不让跳过,必须测,用于进行界面判断 和 统计的判断 | |||
课时学前测试: 不让跳过,必须测,不然对统计和判断有影响 | |||
@@ -14,7 +14,7 @@ android { | |||
targetSdk androidConfig.target_sdk_version | |||
versionCode androidConfig.version_code | |||
versionName androidConfig.version_name | |||
multiDexEnabled true //解决64k 分包限制 | |||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
} | |||
/*构建类型: 定义Gradle在构建阶段和打包应用时使用的某些属性*/ |
@@ -21,15 +21,9 @@ | |||
<activity | |||
android:name=".module.learn.LearnCVideoActivity" | |||
android:exported="true" /> | |||
<activity | |||
android:name=".module.learn.LearnExamSpellActivity" | |||
android:exported="true" /> | |||
<activity | |||
android:name=".module.learn.LearnExamActivity" | |||
android:exported="true" /> | |||
<activity | |||
android:name=".module.learn.LearnSpellActivity" | |||
android:exported="true" /> | |||
<activity | |||
android:name=".module.learn.LearnWordActivity" | |||
android:exported="true" /> |
@@ -8,7 +8,6 @@ import android.text.style.ForegroundColorSpan | |||
import android.view.ViewGroup | |||
import android.widget.Toast | |||
import androidx.core.content.ContextCompat | |||
import androidx.core.text.isDigitsOnly | |||
import com.suliang.common.base.adapter.BaseRVAdapter | |||
import com.suliang.common.base.adapter.BaseAdapterViewHolder | |||
@@ -57,7 +56,7 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
* @param isOver 纠错是否完成 | |||
* @param nextPositon 纠错中的下一项 | |||
*/ | |||
lateinit var onItemRecoveryClick : (showValue : SpannableStringBuilder, isOver:Boolean, nextPosition:Int) -> Unit | |||
lateinit var onItemRecoveryClick : (showValue : SpannableStringBuilder, isOver : Boolean, nextPosition : Int) -> Unit | |||
/** | |||
* 拼写时的事件 | |||
@@ -67,7 +66,7 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
* @param correctValue 正确的拼写值,但未选中的赋值了颜色 | |||
* @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 { | |||
@@ -89,14 +88,13 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
playItem(item.char) | |||
item.isSelected = true | |||
getItem(if (position % 2 == 0) position + 1 else position - 1).isSelected = false | |||
notifyDataSetChanged() | |||
notifyUIUpdate(position) | |||
notifyDataSetChanged() | |||
} | |||
} | |||
} else { //不可拼写时 正确项未选中为红色,否则为白色 | |||
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 { | |||
//非纠错校正则点击无效 | |||
if (!isErrorRecovery) return@setOnClickListener | |||
@@ -106,10 +104,9 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
} else { | |||
playItem(item.char) | |||
item.isSelected = true | |||
notifyItemChanged(position) | |||
notifyUIUpdate(position) | |||
notifyItemChanged(position) | |||
} | |||
} | |||
} | |||
} | |||
@@ -119,19 +116,18 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
*/ | |||
private fun clickInvalid() { | |||
Toast.makeText(context, "请依次点击", Toast.LENGTH_SHORT).show() | |||
MPManager.play("common_voice/mistake.mp3") | |||
MPManager.playAsset("common_voice/mistake.mp3") | |||
} | |||
/** item 播放 */ | |||
private fun playItem(letter : Char) { | |||
if (letter.isLetter()) { | |||
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 { | |||
MPManager.play("common_voice/konge.mp3") | |||
MPManager.playAsset("common_voice/konge.mp3") | |||
} | |||
} | |||
@@ -144,7 +140,7 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
//第二列 position为 1 3 5 7 9 | |||
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 | |||
//上一列的第二行坐标 | |||
val previousColumnSecondPosition = previousColumnFirstPosition + 1 | |||
@@ -184,14 +180,21 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
//是否是最后一列 | |||
val isOver = (position == itemCount - 1) || (position == itemCount - 2) | |||
var errorSize = 0 | |||
var nextPosition = 0 | |||
var nextPosition = 0 //下一列的第一行坐标位置 | |||
//记录正确的值,如果没有选中,则需要设置颜色 | |||
val correctValue = SpannableStringBuilder() | |||
if (!isOver) { | |||
//拼写时,直接赋值 | |||
getData().forEachIndexed { index, it -> | |||
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 { //拼写完成 | |||
getData().forEachIndexed { index, it -> | |||
@@ -209,18 +212,15 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | |||
}) | |||
//错误数加1 | |||
errorSize+=1 | |||
//下一个位置定义到第一个错误的位置 | |||
if (nextPosition == 0 ){ | |||
nextPosition = index | |||
} | |||
errorSize += 1 | |||
} | |||
} | |||
} | |||
} | |||
isSpelling = false | |||
} | |||
//回调出去 | |||
onItemSpellingClickListener(builder,nextPosition,isOver,correctValue,errorSize) | |||
onItemSpellingClickListener(builder, nextPosition, isOver, correctValue, errorSize) | |||
} | |||
//纠错: 拼接所有正确的内容,未选中的需要设置颜色 | |||
@@ -245,12 +245,43 @@ class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||
//判断是否还有大于当前列第二行坐标的未选,如果有,则为纠错未完成 | |||
if (index > currentSecondPosition) { | |||
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() | |||
} | |||
} |
@@ -1,11 +1,10 @@ | |||
package com.xkl.cdl.adapter.itemdecoration | |||
import android.content.Context | |||
import android.graphics.Rect | |||
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.ItemDecoration | |||
import com.suliang.common.util.os.ScreenUtil | |||
/** | |||
* Created by su on 2018/11/26. | |||
@@ -13,67 +12,37 @@ import androidx.recyclerview.widget.RecyclerView | |||
*/ | |||
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) { | |||
val space = mDividerWidth/2 | |||
val chilidCount = parent.adapter!!.itemCount | |||
val position = (view.layoutParams as RecyclerView.LayoutParams).viewLayoutPosition | |||
//获取view所在的位置(从0计数) | |||
val position = parent.getChildAdapterPosition(view) | |||
var left = 0 | |||
var top = 0 | |||
var right = 0 | |||
var top = 0 | |||
var right = 0 | |||
var bottom = 0 | |||
when{ | |||
position % 2 == 0 -> { //第一行 | |||
position % 2 == 0 -> { //第一行 | |||
left = 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 | |||
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) | |||
} | |||
@@ -149,21 +149,44 @@ object AppConstants { | |||
/**测试错误: 未答到一题的时间*/ | |||
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 改变界面 到目录页 */ | |||
const val EVENT_COURSE = "action_change_page" | |||
/** 动作:学前总测之 开始学习 */ | |||
/** 事件动作:学前总测结束弹窗之 开始学习 */ | |||
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_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 | |||
} |
@@ -1,12 +0,0 @@ | |||
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) { | |||
} |
@@ -7,11 +7,14 @@ import android.os.Parcelable | |||
* author suliang | |||
* create 2022/4/14 9:37 | |||
* Describe: 学习测试对话框--传参实体 | |||
* | |||
* 所有的测试必传测试类型 [examType] | |||
* | |||
* 学前总测 :[examType],[score],[correctNumber],[errorNumber] | |||
* 学前总测结束 :[examType],[score],[correctNumber],[errorNumber] | |||
* | |||
* 课时学前测试开始: [examType],[chapter_lesson_name],[showTimeCount] | |||
* | |||
* 课时学前测试 | |||
* 课时学前测试结束:[examType],[score],[correctNumber],[errorNumber] | |||
* | |||
* 课时学后测试 | |||
* | |||
@@ -38,6 +41,11 @@ class LearnDialogBean(val dialogType:Int) : Parcelable { | |||
/** 错误数据 */ | |||
var errorNumber : Int = 0 | |||
/**章节课时名称: 第一章 第二课时 */ | |||
var chapter_lesson_name = "" | |||
/** 题数与时间 */ | |||
var showTimeCount = "" | |||
@@ -1,5 +1,7 @@ | |||
package com.xkl.cdl.data.bean | |||
import com.xkl.cdl.data.bean.BaseWord | |||
/** | |||
* author suliang | |||
* create 2022/3/31 17:52 | |||
@@ -30,15 +32,15 @@ class LearnWord(subjectId: Int, | |||
var phonetic_uk: String? = null // 英式音标 | |||
var phonetic_us: 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 all_explanation: String? = null //扩展释义 | |||
var extend_explanation: String? = null //扩展释义 | |||
var phrase: String? = null //词组 | |||
var example: String? = null //例句 | |||
var reference: String? = null //参考 | |||
var literacyIspolyphone = false //识字课程,是否是多音字标识 | |||
var pattern: String? = null //口语: 语法 | |||
} |
@@ -1,7 +1,5 @@ | |||
package com.xkl.cdl.data.bean.course | |||
import android.R.bool | |||
/** | |||
* author suliang |
@@ -18,9 +18,20 @@ class ExamBean { | |||
var chapterId : Long = 0 //章节id | |||
var lessonId : Long = 0 //课时id | |||
//口语 | |||
var questionIsAudio = 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 = "" | |||
} |
@@ -13,6 +13,7 @@ import com.xkl.cdl.data.AppConstants | |||
data class Lesson( | |||
val subjectId: Int, | |||
val coursePackId:Long, | |||
val coursePackType:Int, | |||
val courseId:Long, | |||
var courseType: Int, | |||
val chapterId:Long, | |||
@@ -26,8 +27,8 @@ data class Lesson( | |||
var lessonPositionInList : Int = 0 | |||
/** 该课时的总数据 或者作文关联的数据,如视频关联的内容*/ | |||
var wordIds = mutableListOf<Long>() | |||
/** 学习进度位置,为医学下标的位置,在作文课堂练习时,学习取当前条,其他取下一条*/ | |||
var learnedIndex: Int = 0 | |||
/** 学习进度位置,为已学下标的位置,在作文课堂练习时,学习取当前条,其他取下一条*/ | |||
var learnedIndex: Int = -1 | |||
/**课时学前测试成绩 */ | |||
var beforeTestScore = -1.0 | |||
/** 课时学后测试 */ |
@@ -1,17 +1,19 @@ | |||
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.Lesson | |||
import kotlin.collections.HashMap | |||
/** | |||
* author suliang | |||
* create 2022/4/8 12:01 | |||
* Describe: 测试传递数据 | |||
* | |||
* 学前总测: coursePackType, courseType, testData ,mExamWRMap | |||
* | |||
* 学后总测: coursePackType, courseType, testData | |||
* | |||
* 课时学前测: coursePackType, courseType, testData ,mExamWRMap,lesson | |||
* | |||
* 课时学后测: coursePackType, courseType, testData ,lesson. | |||
* | |||
*/ | |||
@@ -19,7 +21,10 @@ data class ExamData( | |||
val subjectId : Int, //项目类型 | |||
val examType : Int, //测试类型 | |||
val showTitle : String, //去测试本地显示名称,学前学后 为课程名称,课时前、后为课时名称 | |||
val saveTitle : String, //测试试卷上传名称 : 学习中心:课程名称 + “ ” + 学习类型 课程测试:课程名称 + “ ” 学习类型 + (章节拼接名称) 词汇量测试:词汇量测试:阶段名称 | |||
//测试试卷上传名称 : 学习中心:“课程名称 章节名称 课时名称” | |||
// 课程测试:课程名称 + “ ” 学习类型 + (章节拼接名称) | |||
// 词汇量测试:词汇量测试:阶段名称 | |||
val saveTitle : String, | |||
) { | |||
var coursePackId : Long = 0 //课程包id, 测试错误上次数据需要 | |||
var coursePackType:Int = 0 //课程包类型 |
@@ -0,0 +1,15 @@ | |||
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() | |||
} |
@@ -6,7 +6,7 @@ package com.xkl.cdl.data.event | |||
* Describe: 事件通知更新实体封装 | |||
* @param subjectId 项目id | |||
* @param courseId 课程id | |||
* @param actionFlag 动作id 动作 或 传递数据 | |||
* @param actionFlag 动作id 动作(action开头) 或 传递数据(data开头) | |||
*/ | |||
class LearnEventData(val subjectId : Int, var courseId : Long, val actionFlag : Int) { | |||
@@ -15,5 +15,7 @@ class LearnEventData(val subjectId : Int, var courseId : Long, val actionFlag : | |||
var scoreValue = 0 //分数 | |||
var newErrorMap : HashMap<String, Boolean>? = null //新错误单词列表 | |||
//课时学前测试 除了学前总测传递数据,还需要课时位置数据, | |||
var leesonPositionIndex = 0 // lesson所在位置 | |||
} |
@@ -2,15 +2,23 @@ package com.xkl.cdl.data.manager.db | |||
import android.annotation.SuppressLint | |||
import android.widget.Toast | |||
import androidx.core.database.getBlobOrNull | |||
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.file.FileUtil | |||
import com.suliang.common.util.thread.AppExecutors | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.data.bean.LearnWord | |||
import com.xkl.cdl.data.bean.course.CourseDetail | |||
import com.xkl.cdl.data.bean.course.ExamBean | |||
import com.xkl.cdl.data.bean.course.Lesson | |||
import com.xkl.cdl.data.manager.FilePathManager | |||
import io.reactivex.rxjava3.annotations.NonNull | |||
import net.sqlcipher.database.SQLiteDatabase | |||
import java.io.File | |||
/** | |||
* author suliang | |||
@@ -39,15 +47,14 @@ object DBCourseManager { | |||
AppConstants.COURSE_TYPE_CHINESE_LITERACY -> LITERACY | |||
else -> NORMAL | |||
} | |||
mDataBase = SQLiteDatabase.openDatabase( | |||
FilePathManager.getCourseDbPath(currentBase).path, p, null, SQLiteDatabase.OPEN_READONLY | |||
) | |||
mDataBase = SQLiteDatabase.openDatabase(FilePathManager.getCourseDbPath(currentBase).path, | |||
p, | |||
null, | |||
SQLiteDatabase.OPEN_READONLY) | |||
} | |||
if (mDataBase == null) { | |||
AppExecutors.mainThread.run { | |||
Toast.makeText( | |||
AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG | |||
) | |||
Toast.makeText(AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG) | |||
} | |||
} | |||
} | |||
@@ -80,14 +87,16 @@ object DBCourseManager { | |||
val chapterId : Long = it.getLong(it.getColumnIndex("chapter_id")) | |||
val chapterName : String = it.getString(it.getColumnIndex("chapter_title")) | |||
val lessonId = it.getLong(it.getColumnIndex("lesson_id")) | |||
val lessonName = when (base.courseType) { | |||
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("lesson_title")) | |||
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")) | |||
} | |||
val wordIds = when (base.courseType) { | |||
val wordIds : MutableList<Long> = when (base.courseType) { | |||
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("relation_id")).split(",") | |||
else -> it.getString(it.getColumnIndex("wordIds")).split(",") | |||
}.map { value -> value.toLong() }.toMutableList() | |||
val lessonType : Int = when (base.courseType) { | |||
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getInt(it.getColumnIndex("type")) | |||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> it.getInt(it.getColumnIndex("lesson_type")) | |||
@@ -95,22 +104,20 @@ object DBCourseManager { | |||
} | |||
val key = "${chapterId}_${lessonId}" | |||
val learnIndex = wordIds.indexOf( | |||
detail.lesson_learn_point.getOrElse(key, { -1 }) | |||
) //学习位置,当前位置为已学 | |||
val learnIndex = wordIds.indexOf(detail.lesson_learn_point.getOrElse(key, { -1 })) //学习位置,当前位置为已学 | |||
// 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 | |||
val learnIsOver = wordIds.size - 1 == learnIndex | |||
val lesson = Lesson( | |||
base.subjectId, | |||
base.coursePackId, | |||
base.courseId, | |||
base.courseType, | |||
chapterId, | |||
chapterName, | |||
lessonId, | |||
lessonName).apply { | |||
val lesson = Lesson(base.subjectId, | |||
base.coursePackId, | |||
base.coursePackType, | |||
base.courseId, | |||
base.courseType, | |||
chapterId, | |||
chapterName, | |||
lessonId, | |||
lessonName).apply { | |||
lessonPositionInList = positionIndex | |||
this.wordIds = wordIds //内容 | |||
totalNumber = this.wordIds.size //总数 | |||
@@ -169,7 +176,8 @@ object DBCourseManager { | |||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { //口语 | |||
when (testType) { | |||
AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> { //总测 | |||
"SELECT * FROM exam WHERE type = 5 ORDER by random() LIMIT $count" | |||
// "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 -> { | |||
"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" | |||
@@ -180,9 +188,7 @@ object DBCourseManager { | |||
else -> "" //没有任何课程,课程都特别写入类型,用于匹配 | |||
} | |||
mDataBase?.rawQuery( | |||
sql, null | |||
)?.run { | |||
mDataBase?.rawQuery(sql, null)?.run { | |||
when (base.courseType) { | |||
AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE, | |||
AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_CHINESE_LITERACY, | |||
@@ -217,21 +223,63 @@ object DBCourseManager { | |||
} | |||
} | |||
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 | |||
} | |||
}) | |||
} | |||
} | |||
} | |||
} | |||
} | |||
@@ -240,38 +288,164 @@ object DBCourseManager { | |||
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 | |||
} | |||
@@ -8,5 +8,6 @@ package com.xkl.cdl.data.manager.db | |||
data class DbControlBase( | |||
val subjectId: Int = 0, | |||
val coursePackId: Long = 0, | |||
val coursePackType:Int = 0 , | |||
val courseId: Long = 0, | |||
var courseType: Int = 0) |
@@ -75,7 +75,7 @@ class DbCoursePackManager { | |||
coursePack.inCoursePackPosition = englishCoursePack.size | |||
englishCoursePack.add(coursePack) | |||
} else { | |||
coursePack.inCoursePackPosition = englishCoursePack.size | |||
coursePack.inCoursePackPosition = chineseCoursePack.size | |||
chineseCoursePack.add(coursePack) | |||
} | |||
} |
@@ -1,8 +1,17 @@ | |||
package com.xkl.cdl.data.repository | |||
import android.graphics.Bitmap | |||
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 | |||
@@ -10,28 +19,79 @@ import java.security.Key | |||
* Describe: 音频缓存获取规则 | |||
*/ | |||
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] | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
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 | |||
} | |||
} | |||
} |
@@ -6,6 +6,7 @@ import android.view.View | |||
import androidx.core.content.ContextCompat | |||
import com.suliang.common.AppConfig | |||
import com.suliang.common.base.BaseDialogFragment | |||
import com.suliang.common.extension.click | |||
import com.suliang.common.util.DrawableUti | |||
import com.xkl.cdl.databinding.DialogCommonBinding | |||
@@ -51,29 +52,33 @@ class CommonDialog private constructor() : BaseDialogFragment<DialogCommonBindin | |||
binding.tvTitle.setText(it) | |||
binding.tvTitle.visibility = View.VISIBLE | |||
} | |||
contentText?.let { | |||
contentColor?.let { | |||
binding.tvContent.setTextColor(ContextCompat.getColor(requireContext(),it)) | |||
binding.tvContent.visibility = View.VISIBLE | |||
} | |||
contentColor?.let { | |||
contentText?.let { | |||
binding.tvContent.setText(it) | |||
binding.tvContent.visibility = View.VISIBLE | |||
} | |||
leftColor?.let { | |||
binding.tvLeft.setTextColor(ContextCompat.getColor(requireContext(),it)) | |||
} | |||
leftText?.let { | |||
binding.tvLeft.setText(it) | |||
onCommonDialogButtonClickListener(this@CommonDialog,false) | |||
binding.tvLeft.click { | |||
onCommonDialogButtonClickListener(this@CommonDialog,false) | |||
} | |||
}?:let { | |||
binding.tvLeft.visibility = View.GONE | |||
binding.vSpace.visibility = View.GONE | |||
} | |||
rightText?.let { | |||
rightColor?.let { | |||
binding.tvRight.setTextColor(ContextCompat.getColor(requireContext(),it)) | |||
onCommonDialogButtonClickListener(this@CommonDialog,true) | |||
} | |||
rightColor?.let { | |||
rightText?.let { | |||
binding.tvRight.setText(it) | |||
binding.tvRight.click { | |||
onCommonDialogButtonClickListener(this@CommonDialog,true) | |||
} | |||
} | |||
imgFlag?.let { |
@@ -19,10 +19,9 @@ import com.xkl.cdl.databinding.DialogLessonLearnBinding | |||
* create 2022/4/2 15:47 | |||
* Describe: 学习的通用弹窗 | |||
*/ | |||
class LearnDialog private constructor(): BaseDialogFragment<DialogLessonLearnBinding>() { | |||
class LearnDialog private constructor() : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
companion object { | |||
fun newInstance(params : LearnDialogBean) : LearnDialog { | |||
val args = Bundle() | |||
args.putParcelable(AppConfig.INTENT_1, params) | |||
@@ -49,13 +48,23 @@ class LearnDialog private constructor(): BaseDialogFragment<DialogLessonLearnBin | |||
override fun initFragment() { | |||
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-> initLessonBeforeTestOver() | |||
} | |||
AppConstants.DIALOG_TYPE_LEARN -> { | |||
//学习结束弹窗 | |||
AppConstants.DIALOG_TYPE_LEARN_OVER -> { | |||
} | |||
} | |||
} | |||
override fun onStart() { | |||
@@ -76,27 +85,23 @@ class LearnDialog private constructor(): BaseDialogFragment<DialogLessonLearnBin | |||
when { | |||
learnDialogBean.score < AppConstants.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 -> { | |||
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 -> { | |||
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 { | |||
tvCorrectNumber.text = "${learnDialogBean.correctNumber}" | |||
tvErrorNumber.text = "${learnDialogBean.errorNumber}" | |||
@@ -106,97 +111,108 @@ class LearnDialog private constructor(): BaseDialogFragment<DialogLessonLearnBin | |||
/** 学前总测试 结束 */ | |||
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() | |||
binding.tvTitle.text = "恭喜你,完成了学前总测试!" | |||
binding.tvTip1.text = "学考乐已为您智能生成了个性化学习计划" | |||
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 { | |||
// 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 { | |||
// TODO: 2022/4/2 开始学习 | |||
onDialogListener(AppConstants.DIALOG_LESSON_AFTER_TEST_NEXT,this) | |||
} | |||
} | |||
} |
@@ -5,6 +5,7 @@ import android.os.Looper | |||
import androidx.lifecycle.LifecycleOwner | |||
import androidx.lifecycle.MutableLiveData | |||
import com.suliang.common.base.viewmodel.BaseViewModel | |||
import com.suliang.common.util.LogUtil | |||
import java.util.* | |||
import kotlin.concurrent.timerTask | |||
@@ -25,26 +26,21 @@ abstract class LearnBaseViewModel : BaseViewModel() { | |||
private var timeTask : TimerTask? = null | |||
//是否进行计时,默认计时,为false不计时,更改为false的条件时,出现弹窗或者当前页不在前台 | |||
var countingEnable = true | |||
// var countingEnable = true | |||
/** 开始计时 */ | |||
fun startTotalCounting(){ | |||
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(){ | |||
totalTimer.cancel() | |||
LogUtil.e("停止总计时") | |||
timeTask?.cancel() | |||
timeTask = null | |||
} | |||
} |
@@ -9,14 +9,18 @@ import android.view.View | |||
import android.widget.TextView | |||
import androidx.core.content.ContextCompat | |||
import androidx.lifecycle.ViewModelProvider | |||
import androidx.recyclerview.widget.GridLayoutManager | |||
import androidx.recyclerview.widget.SimpleItemAnimator | |||
import androidx.recyclerview.widget.StaggeredGridLayoutManager | |||
import com.airbnb.lottie.LottieAnimationView | |||
import com.suliang.common.base.activity.BaseActivityVM | |||
import com.suliang.common.eventbus.LiveDataBus | |||
import com.suliang.common.extension.click | |||
import com.suliang.common.util.DateUtil | |||
import com.suliang.common.util.DrawableUti | |||
import com.suliang.common.util.LogUtil | |||
import com.suliang.common.util.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.adapter.AdapterSpell | |||
import com.xkl.cdl.adapter.itemdecoration.SpellItemDecoration | |||
@@ -26,6 +30,7 @@ import com.xkl.cdl.data.bean.course.ExamBean | |||
import com.xkl.cdl.data.event.LearnEventData | |||
import com.xkl.cdl.data.manager.CourseManager | |||
import com.xkl.cdl.data.manager.UserInfoManager | |||
import com.xkl.cdl.data.repository.AudioCache | |||
import com.xkl.cdl.databinding.* | |||
import com.xkl.cdl.dialog.CommonDialog | |||
import com.xkl.cdl.dialog.CommonDialogBean | |||
@@ -74,6 +79,12 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
resources.getString(R.string.pause) -> { //暂停点击 | |||
vm.pauseToNext() | |||
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_) -> { | |||
vm.continueToNext() | |||
@@ -82,6 +93,57 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
} | |||
} | |||
} | |||
} | |||
/** 查询到播放数据过来 */ | |||
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() | |||
} | |||
} | |||
} | |||
@@ -95,14 +157,24 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
tvTitle.text = vm.intentData.showTitle | |||
//题目进度 | |||
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 | |||
} | |||
} | |||
} | |||
} | |||
} | |||
@@ -134,8 +206,9 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { | |||
spellBinding = IncExamSpellContentBinding.inflate(layoutInflater, binding.containerLayout, true) | |||
spellBinding.spellRecyclerView.run { | |||
layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.HORIZONTAL) | |||
layoutManager = GridLayoutManager(this@LearnExamActivity, 2, GridLayoutManager.HORIZONTAL, false) | |||
addItemDecoration(SpellItemDecoration()) | |||
setHasFixedSize(true) | |||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false | |||
spellAdapter = AdapterSpell().apply { | |||
onItemSpellingClickListener = itemSpellingClick | |||
@@ -144,7 +217,7 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
adapter = spellAdapter | |||
} | |||
//初始化事件 | |||
initChooseListener() | |||
initChooseQuestionListener() | |||
} | |||
else -> { | |||
wordChooseBinding = IncExamWordChooseContentBinding.inflate(layoutInflater, binding.containerLayout, true) | |||
@@ -162,7 +235,7 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
tvLable.text = "D" | |||
}) | |||
//初始化事件 | |||
initChooseListener() | |||
initChooseQuestionListener() | |||
//正常、正确、错误 文本和背景颜色 | |||
vm.normalTextColor = ContextCompat.getColor(this, R.color.main_text_color) | |||
vm.correctTextColor = ContextCompat.getColor(this, R.color.green_1) | |||
@@ -180,23 +253,19 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
} | |||
/** 初始四选一的点击事件 只处理问题和单词的事件,选项事件,每个新的单词开始时设置,答题后再取消设置 | |||
* 拼写,释义、单词都不发音,只有拼写和拼写完成发音 | |||
* 拼写,释义、单词都不发音,只有拼写在拼写完成发音 | |||
* 作文知识点测试,没有发音 | |||
* 辨音、口语单词与音频均可发音, | |||
* 其他单词可以发音 | |||
* */ | |||
private fun initChooseListener() { | |||
private fun initChooseQuestionListener() { | |||
when (vm.intentData.courseType) { | |||
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.ivVoice.setOnClickListener(ivVoiceClick) | |||
} | |||
else -> { //四选一题目可以发音 | |||
wordChooseBinding.tvWord.setOnClickListener(ivVoiceClick) | |||
} | |||
} | |||
} | |||
@@ -210,7 +279,7 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
override fun loadData() { | |||
//初始获取第一条数据 | |||
vm.loadNext() | |||
vm.loadFirst() | |||
//监听倒计时 | |||
vm.currentSuplusTime.observe(this) { | |||
@@ -230,7 +299,8 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
initContentUI(it) | |||
//设置初始事件 | |||
initChooseContentLister(it) | |||
vm.startCurrentCountingTime() | |||
//发音事件 | |||
initNewWordRead(it) | |||
} ?: testOver() | |||
} | |||
@@ -263,6 +333,16 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
if (it != AppConstants.TEST_CORRECT) { | |||
binding.tvErrorState.visibility = View.VISIBLE | |||
} | |||
if (isSpokenTotalTest()) { | |||
vm.optionList.forEach { optionBinding -> | |||
optionBinding.run { | |||
groupVoiceChoose.visibility = View.GONE | |||
tvOption.visibility = View.VISIBLE | |||
} | |||
} | |||
} | |||
//获取下一题 | |||
vm.loadNext() | |||
} | |||
@@ -275,60 +355,25 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
//获取下一题 | |||
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) { | |||
if (isSpokenTotalTest()) { | |||
initSpokenUIData(examBean) | |||
return | |||
} | |||
when (vm.intentData.courseType) { | |||
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> initSpellUIData(examBean) | |||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> initSpokenUIData(examBean) | |||
AppConstants.COURSE_TYPE_ENGLISH_VOICE -> initVoiceUIData(examBean) | |||
else -> initChooseUIData(examBean) | |||
} | |||
@@ -379,6 +424,8 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
wordChooseBinding.tvWord.visibility = View.VISIBLE | |||
wordChooseBinding.ivVoice.visibility = View.GONE | |||
} | |||
wordChooseBinding.progressWordCountdownTime.visibility = View.GONE | |||
vm.optionList.forEachIndexed { index, optionBinding -> | |||
optionBinding.run { | |||
root.setBackgroundResource(vm.normalBg) | |||
@@ -389,11 +436,9 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
if (examBean.answersIsAudio) { //答案为音频 | |||
groupVoiceChoose.visibility = View.VISIBLE | |||
tvOption.visibility = View.GONE | |||
ivOption.visibility = View.GONE | |||
} else { | |||
groupVoiceChoose.visibility = View.GONE | |||
tvOption.visibility = View.VISIBLE | |||
ivOption.visibility = View.VISIBLE | |||
} | |||
} | |||
} | |||
@@ -407,7 +452,7 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
setTextColor(ContextCompat.getColor(this@LearnExamActivity, R.color.main_text_color)) | |||
} | |||
//进度 | |||
wordChooseBinding.progressWordCountdownTime.run { | |||
spellBinding.progressWordCountdownTime.run { | |||
max = vm.currentMaxTime | |||
progress = vm.currentMaxTime | |||
} | |||
@@ -416,6 +461,78 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
} | |||
/** 四选一对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 选择的值 | |||
@@ -433,6 +550,8 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
spellBinding.tvWord.text = correctValue | |||
//拼写结束 | |||
vm.spellOver(selectedValue.toString(), errorSize) | |||
//执行当前的单词发音 延迟发音 | |||
spellBinding.tvWord.postDelayed({ ivVoiceClick(spellBinding.tvWord) }, 200) | |||
} | |||
else -> { | |||
//设置为选中的值 | |||
@@ -443,41 +562,37 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
} | |||
/** 点击发音事件 */ | |||
private val ivVoiceClick : (v : View) -> Unit = { | |||
} | |||
override fun onBackPressed() { | |||
onBack() | |||
} | |||
/** 返回: 未结束提示弹窗 */ | |||
private fun onBack() { | |||
vm.stopTotalCountTing() | |||
when { | |||
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() { | |||
//对话框信息实体 | |||
val learnDialogBean = LearnDialogBean(AppConstants.DIALOG_TYPE_EXAM).apply { | |||
val learnDialogBean = LearnDialogBean(AppConstants.DIALOG_TYPE_EXAM_OVER).apply { | |||
examType = vm.intentData.examType | |||
score = vm.scoreValue | |||
correctNumber = vm.correctLiveData.value!! | |||
@@ -485,18 +600,79 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
} | |||
LearnDialog.newInstance(learnDialogBean).apply { | |||
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) | |||
} |
@@ -1,12 +0,0 @@ | |||
package com.xkl.cdl.module.learn | |||
import androidx.appcompat.app.AppCompatActivity | |||
import android.os.Bundle | |||
import com.xkl.cdl.R | |||
class LearnExamSpellActivity : AppCompatActivity() { | |||
override fun onCreate(savedInstanceState: Bundle?) { | |||
super.onCreate(savedInstanceState) | |||
setContentView(R.layout.activity_exam_learn_spell) | |||
} | |||
} |
@@ -4,6 +4,7 @@ import android.graphics.drawable.Drawable | |||
import androidx.lifecycle.LifecycleOwner | |||
import androidx.lifecycle.MutableLiveData | |||
import androidx.lifecycle.viewModelScope | |||
import com.googlecode.protobuf.format.JsonFormat | |||
import com.suliang.common.eventbus.LiveDataBus | |||
import com.suliang.common.extension.createRandomNewChar | |||
import com.suliang.common.extension.diskIo2Main | |||
@@ -15,6 +16,7 @@ import com.xkl.cdl.data.bean.SpellItemBean | |||
import com.xkl.cdl.data.bean.course.ExamBean | |||
import com.xkl.cdl.data.bean.intentdata.ExamData | |||
import com.xkl.cdl.data.event.LearnEventData | |||
import com.xkl.cdl.data.manager.db.DbControlBase | |||
import com.xkl.cdl.databinding.IncludTestOptionItemBinding | |||
import io.reactivex.rxjava3.core.Observable | |||
import kotlinx.coroutines.delay | |||
@@ -30,7 +32,7 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
//传递过来的测试数据 | |||
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 { | |||
intentData.testData!! | |||
@@ -110,117 +112,109 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
//错误集合 | |||
private var mLearnEntities : MutableList<LearnEntity> = mutableListOf() | |||
//最总上传对象 | |||
private val record = Record.newBuilder() | |||
//学前总测试与章节前测试时新的学习错误集合 | |||
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 isShowBackDialog = false | |||
//倒计时结束 | |||
val currentCountingTimeOver = MutableLiveData<Boolean>() | |||
/** 修改获取下一题的标记为true,则在onResume时,自动获取第一题 */ | |||
fun loadFirst(){ | |||
isGetNextIng = true | |||
} | |||
/** 获取下一题数据 */ | |||
fun loadNext() { | |||
LogUtil.e("获取下一题") | |||
isGetNextIng = true | |||
mHandler.postDelayed(toNextRunable, toNextTime) | |||
} | |||
/** 停止获取下一题 */ | |||
fun pauseToNext() { | |||
isErrorPauseToNext = true | |||
mHandler.removeCallbacks(toNextRunable) | |||
} | |||
/** 继续获取下一题 */ | |||
fun continueToNext() { | |||
isErrorPauseToNext = false | |||
isGetNextIng = true | |||
mHandler.post(toNextRunable) | |||
} | |||
/** | |||
* 显示或关闭的返回弹窗: 影响总计时和当前的一些计时 | |||
* 显示或关闭的返回弹窗: 不影响总计时,只影响当前的下一题计时或者倒计时 | |||
* 显示返回弹窗的时机,只会在测试当种,则是结束后则不会再显示此弹窗 | |||
* @param isShow Boolean | |||
*/ | |||
fun showOrDissmissBackDialogForTime(isShow: Boolean){ | |||
fun showOrDismissBackDialogForTime(isShow : Boolean) { | |||
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) { | |||
super.onResume(owner) | |||
if (isAllOver) return | |||
startTotalCounting() | |||
isShowBackDialog = false | |||
when{ | |||
!isShowBackDialog -> { //没有显示返回弹窗,判断是否完成测试,没有完成,则进行计时,需要注意是否第一次进入,避免对第一题进行重复计时 | |||
} | |||
//弹窗未显示,可直接走弹窗关闭的流程 | |||
!isShowBackDialog -> showOrDismissBackDialogForTime(isShowBackDialog) | |||
} | |||
} | |||
/** onPause时,停止总计时 标记进入后台 如果弹窗显示,就只需要关闭总计时就可以了,否则关闭所有计时 */ | |||
override fun onPause(owner : LifecycleOwner) { | |||
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) | |||
} | |||
/** 获取下一题的线程,同时判断是否完成 */ | |||
@@ -229,10 +223,25 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
isGetNextIng = false | |||
currentIndex++ | |||
if (currentIndex >= testData.size) { | |||
// TODO: 2022/4/11 标记学习已完成 停止计时 计算分数 设置总消费时间 | |||
if (isAllOver) return | |||
isAllOver = true | |||
//停止计时 | |||
stopTotalCountTing() | |||
//计算分数 | |||
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() | |||
//保存数据 后再使用 currentExamBean.value = null 都在saveData中 | |||
@@ -256,6 +265,9 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
} | |||
} | |||
currentExamBean.value = nextExam | |||
//开始倒计时 | |||
LogUtil.e("开始单题倒计时") | |||
mHandler.postDelayed(currentCountingTimeRunnable, currentCountingIntervalTime) | |||
} | |||
} | |||
} | |||
@@ -297,7 +309,7 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
/** 当前单词的计时 */ | |||
private val currentCountingTimeRunable = object : Runnable { | |||
private val currentCountingTimeRunnable = object : Runnable { | |||
override fun run() { | |||
when (intentData.courseType) { | |||
AppConstants.COURSE_TYPE_ENGLISH_VOICE, AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { // 累加时间 | |||
@@ -310,32 +322,21 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
if (surplusTime > 0) { | |||
mHandler.postDelayed(this, currentCountingIntervalTime) | |||
} 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 为未答 | |||
*/ | |||
fun chooseResult(position : Int) { | |||
//移除计时 | |||
mHandler.removeCallbacks(currentCountingTimeRunable) | |||
mHandler.removeCallbacks(currentCountingTimeRunnable) | |||
LogUtil.e("结果移除单题倒计时") | |||
//创建单题的测试记录 | |||
currentExamRecord = ExamRecord.newBuilder().apply { | |||
questionId = currentExamBean.value!!.word_id | |||
@@ -389,7 +390,8 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
* */ | |||
fun spellOver(selectedValue : String, errorSize : Int) { | |||
//移除计时 | |||
mHandler.removeCallbacks(currentCountingTimeRunable) | |||
mHandler.removeCallbacks(currentCountingTimeRunnable) | |||
LogUtil.e("结果移除单题倒计时") | |||
//创建测试记录 | |||
currentExamRecord = ExamRecord.newBuilder().apply { | |||
questionId = currentExamBean.value!!.word_id | |||
@@ -401,23 +403,27 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
val result : Long | |||
when { | |||
selectedValue.isEmpty() -> { //未答 | |||
errorLiveData.value = errorLiveData.value!!.plus(1) //错误 | |||
errorLiveData.value = errorLiveData.value!!.plus(1) | |||
currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR | |||
toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME | |||
unAnswerNumber += 1 | |||
currentExamRecord!!.answerStatus = AppConstants.TEST_UN_ANSWER | |||
result = AppConstants.TEST_UN_ANSWER | |||
//创建错误记录 | |||
createErrorRecord() | |||
} | |||
errorSize > 0 -> { //错误 | |||
errorLiveData.value = errorLiveData.value!!.plus(1) //错误 | |||
errorLiveData.value = errorLiveData.value!!.plus(1) | |||
currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR | |||
toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME | |||
result = AppConstants.TEST_TO_NEXT_ERROR_TIME | |||
//创建错误记录 | |||
createErrorRecord() | |||
} | |||
else -> { //正确 | |||
correctLiveData.value = correctLiveData.value!!.plus(1) | |||
currentExamRecord!!.answerStatus = AppConstants.TEST_CORRECT | |||
toNextTime = AppConstants.TEST_CORRECT | |||
toNextTime = AppConstants.TEST_TO_NEXT_CORRECT_TIME | |||
result = AppConstants.TEST_CORRECT | |||
} | |||
} | |||
@@ -547,7 +553,7 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
} | |||
/** 封装试卷 */ | |||
private fun packUpRecord() : Record.Builder { | |||
private fun packUpRecord() { | |||
val examType = intentData.examType | |||
//生成试卷 | |||
learnExam = LearnExam.newBuilder().apply { | |||
@@ -570,7 +576,7 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
tag = "android" | |||
created = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1) //数据创建时间 | |||
//courseType | |||
val ext = "{\"courseType\": ${intentData.courseType} }" | |||
ext = "{\"courseType\": ${intentData.courseType} }" | |||
//词汇量测试: 覆盖率、阶段 | |||
if (examType == AppConstants.TEST_TYPE_NORMAL) { | |||
// TODO: 2022/4/14 设置词汇量测试的范围题目和覆盖率 | |||
@@ -588,7 +594,6 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
// } | |||
//最终上传数据 | |||
val record = Record.newBuilder() //上传对象 | |||
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) { | |||
@@ -597,7 +602,6 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
if (mLearnEntities.size != 0) { | |||
record.addAllEntity(mLearnEntities) | |||
} | |||
return record | |||
} | |||
/** | |||
@@ -627,21 +631,57 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
it.onNext(true) | |||
} | |||
// TODO: 2022/4/14 传递保存record信息 | |||
// record 已经实例化并已经将数据保存 | |||
LogUtil.e(JsonFormat.printToString(record.build())) | |||
}.compose(diskIo2Main()).subscribe({ | |||
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 | |||
}, { | |||
showHideLoading(false) | |||
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 | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -1,12 +0,0 @@ | |||
package com.xkl.cdl.module.learn | |||
import androidx.appcompat.app.AppCompatActivity | |||
import android.os.Bundle | |||
import com.xkl.cdl.R | |||
class LearnSpellActivity : AppCompatActivity() { | |||
override fun onCreate(savedInstanceState: Bundle?) { | |||
super.onCreate(savedInstanceState) | |||
setContentView(R.layout.activity_learn_spell) | |||
} | |||
} |
@@ -4,6 +4,9 @@ import androidx.appcompat.app.AppCompatActivity | |||
import android.os.Bundle | |||
import com.xkl.cdl.R | |||
/** | |||
* 正常的学习、复习、自动播放 | |||
*/ | |||
class LearnWordActivity : AppCompatActivity() { | |||
override fun onCreate(savedInstanceState: Bundle?) { | |||
super.onCreate(savedInstanceState) |
@@ -4,10 +4,17 @@ import android.view.View | |||
import androidx.lifecycle.ViewModelProvider | |||
import androidx.recyclerview.widget.LinearLayoutManager | |||
import com.suliang.common.base.fragment.BaseFragmentVM | |||
import com.suliang.common.eventbus.LiveDataBus | |||
import com.xkl.cdl.adapter.AdapterLesson | |||
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.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.dialog.LearnDialog | |||
/** | |||
* 课程章节目录 | |||
@@ -22,6 +29,7 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java] | |||
} | |||
private lateinit var adapterLesson: AdapterLesson | |||
override fun initFragment() { | |||
//口语显示顶部的自动播放与跟读模式 | |||
if (vm.course.courseType == AppConstants.COURSE_TYPE_ENGLISH_SPOKEN){ | |||
@@ -29,31 +37,87 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
} | |||
binding.recyclerView.apply { | |||
layoutManager = LinearLayoutManager(requireContext(),LinearLayoutManager.VERTICAL,false) | |||
adapter = AdapterLesson(vm).apply { | |||
adapterLesson = AdapterLesson(vm).apply { | |||
onItemClick = onLessonClick | |||
} | |||
adapter = adapterLesson | |||
} | |||
//设置数据 | |||
(binding.recyclerView.adapter as AdapterLesson).setData(vm.allLesson.toMutableList()) | |||
} | |||
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){ | |||
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.afterTestScore != AppConstants.NOT_DOING){ | |||
}else{ | |||
loadLessonAfterTest(entity) | |||
}else{ //当前课时学习完成的弹窗 | |||
showLessonAllOverDialog(entity) | |||
} | |||
} | |||
AppConstants.LESSON_TYPE_SENTENCE -> { | |||
@@ -82,5 +146,79 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
} | |||
} | |||
/** 显示课时前测试开始的弹窗 | |||
* 先获取数据,然后显示弹窗 | |||
* */ | |||
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(){ | |||
} | |||
} |
@@ -38,26 +38,26 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
return ViewModelProvider(this, | |||
ViewModelFactory(requireArguments().getInt(AppConfig.INTENT_1)))[CourseMainFragmentViewModel::class.java].apply { | |||
coursePackMainActivityVM = ViewModelProvider(requireActivity())[CoursePackMainActivityViewModel::class.java] | |||
LogUtil.e("CourseMainFragment coursePackMainActivityVM hashCode -> ${coursePackMainActivityVM.hashCode()}") | |||
// LogUtil.e("CourseMainFragment coursePackMainActivityVM hashCode -> ${coursePackMainActivityVM.hashCode()}") | |||
} | |||
} | |||
override fun initFragment() { | |||
//设置课程 和 需要操作的类型 | |||
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 | |||
when (it.actionFlag) { | |||
// 学前总测、学后总测 之继续学习 -> 切换到目录页 | |||
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() //学前总分 | |||
it.newErrorMap?.run { | |||
when { | |||
@@ -70,13 +70,8 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
override fun loadData() { | |||
@@ -85,8 +80,6 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
vm.loadMain().observe(this) { | |||
changeChildrenFragment() | |||
} | |||
} | |||
@@ -132,6 +125,12 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
LearnExamActivity.newInstance(requireContext()) | |||
} | |||
override fun onDestroy() { | |||
super.onDestroy() | |||
//退出本课程后移除该课程的事件监听 | |||
// LiveDataBus.remove(AppConstants.EVENT_COURSE) | |||
} | |||
inner class ViewModelFactory(private val courseIndex : Int) : ViewModelProvider.Factory { | |||
override fun <T : ViewModel?> create(modelClass : Class<T>) : T { |
@@ -1,21 +1,20 @@ | |||
package com.xkl.cdl.module.m_center_learn.coursechildren | |||
import androidx.lifecycle.MutableLiveData | |||
import com.suliang.common.base.activity.ToastEvent | |||
import com.suliang.common.base.viewmodel.BaseViewModel | |||
import com.suliang.common.extension.diskIo2DiskIo | |||
import com.suliang.common.extension.diskIo2Main | |||
import com.xkl.cdl.data.bean.LearnWord | |||
import com.xkl.cdl.data.bean.course.Course | |||
import com.xkl.cdl.data.bean.course.CourseDetail | |||
import com.xkl.cdl.data.bean.course.ExamBean | |||
import com.xkl.cdl.data.bean.course.Lesson | |||
import com.xkl.cdl.data.bean.intentdata.LearnData | |||
import com.xkl.cdl.data.manager.db.DBCourseManager | |||
import com.xkl.cdl.data.manager.db.DbControlBase | |||
import com.xkl.cdl.data.repository.DataRepository | |||
import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | |||
import io.reactivex.rxjava3.core.Observable | |||
import io.reactivex.rxjava3.core.Observer | |||
import io.reactivex.rxjava3.disposables.Disposable | |||
import io.reactivex.rxjava3.functions.BiFunction | |||
import java.util.* | |||
import kotlin.collections.HashMap | |||
@@ -53,37 +52,22 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
//获取课程的复习数据 | |||
//获取课程的章节数据 | |||
fun loadMain() : MutableLiveData<Boolean> { | |||
showHideLoading(true) | |||
// showHideLoading(true) | |||
val mutableLiveData = MutableLiveData<Boolean>() | |||
Observable.concat(DataRepository.getCourseStatistics().flatMap { | |||
Observable.zip(DataRepository.getCourseStatistics().flatMap { | |||
courseDetail = it | |||
return@flatMap DataRepository.getCourseAllLesson(dbControlBase, it) | |||
}, DataRepository.getReviewData(), DataRepository.getCourseCollect()).compose(diskIo2DiskIo()) | |||
.subscribe(object : Observer<Any> { | |||
override fun onSubscribe(d : Disposable?) { | |||
} | |||
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 | |||
} | |||
@@ -94,16 +78,13 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
*/ | |||
fun loadTest(testType : Int, lesson : Lesson? = null) : MutableLiveData<List<ExamBean>> { | |||
val result = MutableLiveData<List<ExamBean>>() | |||
// showHideLoading(true) | |||
Observable.create<List<ExamBean>> { | |||
it.onNext(DBCourseManager.queryLearnTest(dbControlBase, testType, lesson)) | |||
it.onComplete() | |||
}.compose(diskIo2Main()).subscribe({ | |||
// showHideLoading(false) | |||
result.value = it | |||
}, { | |||
it.printStackTrace() | |||
// showHideLoading(false) | |||
}) | |||
return result | |||
@@ -115,21 +96,41 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
fun modifyLessonErrorNumber() { | |||
//重设detail中的错误数, exam_w_r_list为所有的测试前错误,因为时学前总测,所以,可以根据这个字段来计算每个课时的错误数 | |||
//记录错误数: key :chapterId_lessonId value :错误数 | |||
val errorNumber = hashMapOf<String,Int>() | |||
val errorNumber = hashMapOf<String, Int>() | |||
courseDetail.exam_w_r_list.keys.forEach { | |||
val split = it.split("_") | |||
if (split.size == 3){ | |||
if (split.size == 3) { | |||
val errorKey = "${split[0]}_${split[1]}" | |||
errorNumber[errorKey] = errorNumber.getOrElse(errorKey,{0}) + 1 | |||
errorNumber[errorKey] = errorNumber.getOrElse(errorKey, { 0 }) + 1 | |||
} | |||
} | |||
courseDetail.wrong.putAll(errorNumber) | |||
//更新lesson中的错误数 | |||
allLesson.forEach { | |||
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 | |||
} | |||
} |
@@ -59,6 +59,8 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
binding.tvCountTip.text = CourseManager.expectedTestTime(vm.course.courseType,totalTestType,testData!!) | |||
when (totalTestType) { | |||
AppConstants.TEST_TYPE_BEFORE_TOTAL -> { | |||
// TODO: 2022/4/21 需要把开始学习给取消了 | |||
binding.button1.visibility = View.VISIBLE | |||
//学前总测 | |||
binding.run { | |||
tvTitle.setText(R.string.test_total_before_title) |
@@ -3,20 +3,11 @@ package com.xkl.cdl.module.splash | |||
import android.annotation.SuppressLint | |||
import android.os.Bundle | |||
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.xkl.cdl.data.manager.CourseManager | |||
import com.xkl.cdl.data.manager.FilePathManager | |||
import com.xkl.cdl.data.manager.db.DbCoursePackManager | |||
import com.xkl.cdl.databinding.ActivitySplashBinding | |||
import com.xkl.cdl.dialog.LearnDialog | |||
import com.xkl.cdl.module.main.MainActivity | |||
import mobile_cache.Mobile_cache | |||
import java.io.File | |||
import java.util.concurrent.TimeUnit | |||
@SuppressLint("CustomSplashScreen") |
@@ -0,0 +1,103 @@ | |||
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 | |||
} | |||
/** 是否在错误列表中 */ | |||
} |
@@ -1,9 +0,0 @@ | |||
<?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> |
@@ -1,9 +0,0 @@ | |||
<?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> |
@@ -82,9 +82,7 @@ | |||
app:layout_constraintStart_toEndOf="@+id/tv_left" | |||
android:background="@color/gray_1" | |||
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 | |||
@@ -99,8 +97,6 @@ | |||
app:layout_constraintEnd_toStartOf="@+id/vSplit" | |||
app:layout_constraintTop_toTopOf="@+id/vSplit" | |||
app:layout_constraintBottom_toBottomOf="@+id/vSplit" | |||
android:visibility="gone" | |||
tools:visibility="visible" | |||
/> | |||
</androidx.constraintlayout.widget.ConstraintLayout> |
@@ -31,8 +31,7 @@ | |||
android:src="@drawable/ic_delete" | |||
android:visibility="gone" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintTop_toTopOf="parent" | |||
tools:visibility="visible" /> | |||
app:layout_constraintTop_toTopOf="parent"/> | |||
<TextView | |||
@@ -46,8 +45,7 @@ | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toTopOf="parent" | |||
tools:text="%d分" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
android:visibility="gone"/> | |||
<TextView | |||
android:id="@+id/tv_tip" | |||
@@ -59,8 +57,7 @@ | |||
app:layout_constraintEnd_toEndOf="@+id/tv_score" | |||
app:layout_constraintStart_toStartOf="@+id/tv_score" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_score" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
android:visibility="gone"/> | |||
<TextView | |||
android:id="@+id/tv_title" | |||
@@ -75,8 +72,7 @@ | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_tip" | |||
tools:text="课时学前测试" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
android:visibility="gone"/> | |||
<TextView | |||
android:id="@+id/tv_lesson_name" | |||
@@ -92,8 +88,7 @@ | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_title" | |||
tools:text="[%s%s]" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
android:visibility="gone"/> | |||
<TextView | |||
android:id="@+id/tv_count_time" | |||
@@ -106,8 +101,7 @@ | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_lesson_name" | |||
tools:text="@string/test_count_time_format" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
android:visibility="gone"/> | |||
<TextView | |||
android:id="@+id/tv_tip_1" | |||
@@ -120,8 +114,7 @@ | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_title" | |||
tools:text="@string/test_before_test_over_tip" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
android:visibility="gone"/> | |||
<include | |||
android:id="@+id/inc_statistics_number" | |||
@@ -131,8 +124,36 @@ | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="parent" | |||
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" | |||
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" | |||
/> | |||
@@ -142,12 +163,12 @@ | |||
android:layout_height="wrap_content" | |||
app:barrierDirection="bottom" | |||
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 | |||
android:id="@+id/bottom_top_line" | |||
android:layout_width="wrap_content" | |||
android:layout_height="@dimen/line_height" | |||
android:layout_height="1dp" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="parent" | |||
android:background="@color/gray_3" | |||
@@ -169,15 +190,14 @@ | |||
<View | |||
android:id="@+id/vSplit" | |||
android:layout_width="@dimen/line_height" | |||
android:layout_width="1dp" | |||
android:layout_height="0dp" | |||
app:layout_constraintEnd_toStartOf="@+id/tv_right" | |||
app:layout_constraintStart_toEndOf="@+id/tv_left" | |||
android:background="@color/gray_3" | |||
app:layout_constraintTop_toTopOf="@+id/tv_right" | |||
app:layout_constraintBottom_toBottomOf="@+id/tv_right" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
android:visibility="gone"/> | |||
<!--左边按钮--> | |||
<TextView | |||
@@ -193,15 +213,36 @@ | |||
app:layout_constraintTop_toTopOf="@+id/vSplit" | |||
app:layout_constraintBottom_toBottomOf="@+id/vSplit" | |||
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 | |||
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" | |||
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> |
@@ -77,11 +77,11 @@ | |||
android:layout_marginStart="10dp" | |||
android:layout_marginEnd="10dp" | |||
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" | |||
android:layout_marginTop="12dp" | |||
app:layout_constrainedWidth="true" /> | |||
app:layout_constrainedWidth="true"/> | |||
</androidx.constraintlayout.widget.ConstraintLayout> |
@@ -59,11 +59,14 @@ | |||
<string name="test_spell_tip">请从左至右依次点击选择正确的选项</string> | |||
<string name="pause">暂停</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_tip">退出后系统将不会保存你的本次测试数据</string> | |||
<string name="quit">退出</string> | |||
<string name="cancel">取消</string> | |||
<string name="lesson_relearn_title">重新学习将清空本课时的学习记录</string> | |||
<string name="lesson_relearn_content">但不会清空测试与备忘本数据</string> | |||
<string name="sure">确定</string> | |||
</resources> |
@@ -10,4 +10,7 @@ object AppConfig { | |||
const val INTENT_2 = "intent_2" | |||
const val INTENT_3 = "intent_3" | |||
const val INTENT_4 = "intent_4" | |||
//音量存放地址 | |||
val VOICE : String = "voice" | |||
} |
@@ -20,7 +20,6 @@ abstract class BaseFragmentVM<VB : ViewBinding, VM : BaseViewModel> : BaseFragme | |||
vm.pageEvent.observe(this) { startActivity(it) } | |||
vm.toastEvent.observe(this){ showToast(it) } | |||
vm.loadingEvent.observe(this) { | |||
LogUtil.e("监听到 fragment vm 的 loadingEvent isShow $it") | |||
showHideLoading(it) | |||
} | |||
} |
@@ -11,18 +11,19 @@ import java.lang.reflect.Method | |||
/** | |||
* author suliang | |||
* create 2022/3/15 10:51 | |||
* Describe: 非粘性LiveData | |||
* Describe: 非粘性LiveData ,需要先设置监听再发送数据,即后注册的监听收不到以前的监听 | |||
*/ | |||
class NonStickyMutableLiveData<T> : MutableLiveData<T>() { | |||
private var stickFlag : Boolean = false | |||
// private var stickFlag : Boolean = false | |||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { | |||
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 { |
@@ -18,6 +18,7 @@ import com.suliang.common.util.LogUtil | |||
fun View.click(clickListener : ((view : View) -> Unit)?){ | |||
if (clickListener == null) { | |||
setOnClickListener(null) | |||
isClickable = false | |||
return | |||
} | |||
val minTime = 500L |
@@ -36,7 +36,7 @@ object FileUtil { | |||
file?.let { | |||
if (it.exists()) file.mkdirs() | |||
} | |||
LogUtil.d("应用内置目录文件: ${file?.path}") | |||
// LogUtil.d("应用内置目录文件: ${file?.path}") | |||
return file ?: throw NullPointerException("Expression 'file' must not be null") | |||
} | |||
@@ -16,7 +16,7 @@ interface IMP { | |||
fun play(url: String, position: Int = 0,listener: IMPListener? = null) | |||
/**播放asset资源 */ | |||
fun play(asset:String,listener : IMPListener? = null) | |||
fun playAsset(asset:String, listener : IMPListener? = null) | |||
/** 暂停播放 ,如果没有播放文件则会忽略*/ | |||
fun pausePlay() |
@@ -15,8 +15,8 @@ object MPManager:IMP { | |||
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() { |
@@ -42,6 +42,7 @@ internal class MPUtil : IMP,MediaPlayer.OnPreparedListener,MediaPlayer.OnErrorLi | |||
} | |||
override fun play(url: String, position: Int,listener: IMPListener?) { | |||
isAssetPath = false | |||
listener?.let { | |||
this.listener = it | |||
} | |||
@@ -61,7 +62,8 @@ internal class MPUtil : IMP,MediaPlayer.OnPreparedListener,MediaPlayer.OnErrorLi | |||
* @param asset String asset文件名称 | |||
* @param listener IMPListener? 监听 | |||
*/ | |||
override fun play(asset : String, listener : IMPListener?) { | |||
override fun playAsset(asset : String, listener : IMPListener?) { | |||
isAssetPath = true | |||
if (asset.isEmpty()) { | |||
handleError("asset 参数为null ") | |||
return | |||
@@ -81,7 +83,7 @@ internal class MPUtil : IMP,MediaPlayer.OnPreparedListener,MediaPlayer.OnErrorLi | |||
it.mediaPlayer = MediaPlayer() | |||
} | |||
mediaPlayer?.apply { | |||
setOnPreparedListener(mOnPreparedLister) | |||
setOnPreparedListener(this@MPUtil) | |||
setOnErrorListener(this@MPUtil) | |||
setOnCompletionListener(this@MPUtil) | |||
setOnInfoListener(this@MPUtil) | |||
@@ -141,10 +143,7 @@ internal class MPUtil : IMP,MediaPlayer.OnPreparedListener,MediaPlayer.OnErrorLi | |||
} | |||
} | |||
} | |||
private val mOnPreparedLister: MediaPlayer.OnPreparedListener = MediaPlayer.OnPreparedListener { | |||
} | |||
/** 异步准备完毕 ,开始播放*/ | |||
private fun handlePrepared() { | |||
@@ -158,27 +157,7 @@ internal class MPUtil : IMP,MediaPlayer.OnPreparedListener,MediaPlayer.OnErrorLi | |||
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() { | |||
when (currentState) { | |||
EMediaState.PREPARED -> handlePrepared() |