Browse Source

学习测试的基本流程实现。学习的初步实现

master
suliang 2 years ago
parent
commit
18657fff45
52 changed files with 1486 additions and 694 deletions
  1. 2
    0
      .idea/misc.xml
  2. 2
    1
      README.md
  3. 1
    1
      app/build.gradle
  4. 0
    6
      app/src/main/AndroidManifest.xml
  5. BIN
      app/src/main/assets/885b4531-7e69-4cea-b427-2b059586cb81.db
  6. BIN
      app/src/main/assets/b4e29043-960f-4a5d-b90c-8c9e07f80fb6.db
  7. BIN
      app/src/main/assets/course_spoken.db
  8. 54
    23
      app/src/main/java/com/xkl/cdl/adapter/AdapterSpell.kt
  9. 20
    51
      app/src/main/java/com/xkl/cdl/adapter/itemdecoration/SpellItemDecoration.kt
  10. 30
    7
      app/src/main/java/com/xkl/cdl/data/AppConstants.kt
  11. 0
    12
      app/src/main/java/com/xkl/cdl/data/bean/IntentLearnData.kt
  12. 10
    2
      app/src/main/java/com/xkl/cdl/data/bean/LearnDialogBean.kt
  13. 7
    5
      app/src/main/java/com/xkl/cdl/data/bean/LearnWord.kt
  14. 0
    2
      app/src/main/java/com/xkl/cdl/data/bean/course/CourseDetail.kt
  15. 12
    1
      app/src/main/java/com/xkl/cdl/data/bean/course/ExamBean.kt
  16. 3
    2
      app/src/main/java/com/xkl/cdl/data/bean/course/Lesson.kt
  17. 8
    3
      app/src/main/java/com/xkl/cdl/data/bean/intentdata/ExamData.kt
  18. 15
    0
      app/src/main/java/com/xkl/cdl/data/bean/intentdata/LearnData.kt
  19. 3
    1
      app/src/main/java/com/xkl/cdl/data/event/LearnEventData.kt
  20. 240
    66
      app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt
  21. 1
    0
      app/src/main/java/com/xkl/cdl/data/manager/db/DbControlBase.kt
  22. 1
    1
      app/src/main/java/com/xkl/cdl/data/manager/db/DbCoursePackManager.kt
  23. 78
    18
      app/src/main/java/com/xkl/cdl/data/repository/AudioCache.kt
  24. 56
    0
      app/src/main/java/com/xkl/cdl/data/repository/PhotoCache.kt
  25. 12
    7
      app/src/main/java/com/xkl/cdl/dialog/CommonDialog.kt
  26. 105
    89
      app/src/main/java/com/xkl/cdl/dialog/LearnDialog.kt
  27. 7
    11
      app/src/main/java/com/xkl/cdl/module/learn/LearnBaseViewModel.kt
  28. 274
    98
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamActivity.kt
  29. 0
    12
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamSpellActivity.kt
  30. 146
    106
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamViewModel.kt
  31. 0
    12
      app/src/main/java/com/xkl/cdl/module/learn/LearnSpellActivity.kt
  32. 3
    0
      app/src/main/java/com/xkl/cdl/module/learn/LearnWordActivity.kt
  33. 147
    9
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseLessonFragment.kt
  34. 10
    11
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragment.kt
  35. 40
    39
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragmentViewModel.kt
  36. 2
    0
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseTotalTestFragment.kt
  37. 0
    9
      app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt
  38. 103
    0
      app/src/main/java/com/xkl/cdl/util/LearnRuleUtil.kt
  39. 0
    9
      app/src/main/res/layout/activity_exam_learn_spell.xml
  40. 0
    9
      app/src/main/res/layout/activity_learn_spell.xml
  41. 1
    5
      app/src/main/res/layout/dialog_common.xml
  42. 65
    24
      app/src/main/res/layout/dialog_lesson_learn.xml
  43. 3
    3
      app/src/main/res/layout/inc_exam_spell_content.xml
  44. 4
    1
      app/src/main/res/values/strings.xml
  45. 3
    0
      lib/common/src/main/java/com/suliang/common/AppConfig.kt
  46. 0
    1
      lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragmentVM.kt
  47. 7
    6
      lib/common/src/main/java/com/suliang/common/eventbus/NonStickyMutableLiveData.kt
  48. 1
    0
      lib/common/src/main/java/com/suliang/common/extension/ViewClickExtension.kt
  49. 1
    1
      lib/common/src/main/java/com/suliang/common/util/file/FileUtil.kt
  50. 1
    1
      lib/common/src/main/java/com/suliang/common/util/media/IMP.kt
  51. 2
    2
      lib/common/src/main/java/com/suliang/common/util/media/MPManager.kt
  52. 6
    27
      lib/common/src/main/java/com/suliang/common/util/media/MPUtil.kt

+ 2
- 0
.idea/misc.xml View File

@@ -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" />

+ 2
- 1
README.md View File

@@ -45,7 +45,8 @@ image包: 实现了图片加载的封装
课时后测试将包含 小游戏练习、再测一次、重新学习、下一步(进入学后总测试)或者下一课时(为当前课时的下一课时),
需要考虑的是,下一步或者下一课时的判断为循环判断,如,共10课时,现在点击学习的第5个,如果后面几个课时都学习完后,而1课时没有学习完成,下一课时将直接进入1课时的学习,
而下一步是在所有课时都学习完成的情况下,直接进入学后总测试的界面。
学前总测试 :不让跳过,必须测,用于进行界面判断 和 统计的判断
课时学前测试: 不让跳过,必须测,不然对统计和判断有影响




+ 1
- 1
app/build.gradle View File

@@ -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在构建阶段和打包应用时使用的某些属性*/

+ 0
- 6
app/src/main/AndroidManifest.xml View File

@@ -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" />

BIN
app/src/main/assets/885b4531-7e69-4cea-b427-2b059586cb81.db View File


BIN
app/src/main/assets/b4e29043-960f-4a5d-b90c-8c9e07f80fb6.db View File


BIN
app/src/main/assets/course_spoken.db View File


+ 54
- 23
app/src/main/java/com/xkl/cdl/adapter/AdapterSpell.kt View File

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

+ 20
- 51
app/src/main/java/com/xkl/cdl/adapter/itemdecoration/SpellItemDecoration.kt View File

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


+ 30
- 7
app/src/main/java/com/xkl/cdl/data/AppConstants.kt View File

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

+ 0
- 12
app/src/main/java/com/xkl/cdl/data/bean/IntentLearnData.kt View File

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

}

+ 10
- 2
app/src/main/java/com/xkl/cdl/data/bean/LearnDialogBean.kt View File

@@ -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 = ""

+ 7
- 5
app/src/main/java/com/xkl/cdl/data/bean/LearnWord.kt View File

@@ -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 //口语: 语法


}

+ 0
- 2
app/src/main/java/com/xkl/cdl/data/bean/course/CourseDetail.kt View File

@@ -1,7 +1,5 @@
package com.xkl.cdl.data.bean.course

import android.R.bool


/**
* author suliang

+ 12
- 1
app/src/main/java/com/xkl/cdl/data/bean/course/ExamBean.kt View File

@@ -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 = ""


}

+ 3
- 2
app/src/main/java/com/xkl/cdl/data/bean/course/Lesson.kt View File

@@ -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
/** 课时学后测试 */

+ 8
- 3
app/src/main/java/com/xkl/cdl/data/bean/intentdata/ExamData.kt View File

@@ -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 //课程包类型

+ 15
- 0
app/src/main/java/com/xkl/cdl/data/bean/intentdata/LearnData.kt View File

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

+ 3
- 1
app/src/main/java/com/xkl/cdl/data/event/LearnEventData.kt View File

@@ -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所在位置
}

+ 240
- 66
app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt View File

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

+ 1
- 0
app/src/main/java/com/xkl/cdl/data/manager/db/DbControlBase.kt View File

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

+ 1
- 1
app/src/main/java/com/xkl/cdl/data/manager/db/DbCoursePackManager.kt View File

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

+ 78
- 18
app/src/main/java/com/xkl/cdl/data/repository/AudioCache.kt View File

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

}

+ 56
- 0
app/src/main/java/com/xkl/cdl/data/repository/PhotoCache.kt View File

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

+ 12
- 7
app/src/main/java/com/xkl/cdl/dialog/CommonDialog.kt View File

@@ -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 {

+ 105
- 89
app/src/main/java/com/xkl/cdl/dialog/LearnDialog.kt View File

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

+ 7
- 11
app/src/main/java/com/xkl/cdl/module/learn/LearnBaseViewModel.kt View File

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

}

+ 274
- 98
app/src/main/java/com/xkl/cdl/module/learn/LearnExamActivity.kt View File

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

+ 0
- 12
app/src/main/java/com/xkl/cdl/module/learn/LearnExamSpellActivity.kt View File

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

+ 146
- 106
app/src/main/java/com/xkl/cdl/module/learn/LearnExamViewModel.kt View File

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

+ 0
- 12
app/src/main/java/com/xkl/cdl/module/learn/LearnSpellActivity.kt View File

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

+ 3
- 0
app/src/main/java/com/xkl/cdl/module/learn/LearnWordActivity.kt View File

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

+ 147
- 9
app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseLessonFragment.kt View File

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

}

+ 10
- 11
app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragment.kt View File

@@ -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 {

+ 40
- 39
app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragmentViewModel.kt View File

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

+ 2
- 0
app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseTotalTestFragment.kt View File

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

+ 0
- 9
app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt View File

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

+ 103
- 0
app/src/main/java/com/xkl/cdl/util/LearnRuleUtil.kt View File

@@ -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
}
/** 是否在错误列表中 */
}

+ 0
- 9
app/src/main/res/layout/activity_exam_learn_spell.xml View File

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

+ 0
- 9
app/src/main/res/layout/activity_learn_spell.xml View File

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

+ 1
- 5
app/src/main/res/layout/dialog_common.xml View File

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

+ 65
- 24
app/src/main/res/layout/dialog_lesson_learn.xml View File

@@ -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-->
<!-- &lt;!&ndash;学前总测试控制显示的布局id&ndash;&gt;-->
<!-- <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"-->
<!-- />-->
<!-- &lt;!&ndash;课时学前测试开始控制显示的布局id&ndash;&gt;-->
<!-- <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>

+ 3
- 3
app/src/main/res/layout/inc_exam_spell_content.xml View File

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

+ 4
- 1
app/src/main/res/values/strings.xml View File

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

+ 3
- 0
lib/common/src/main/java/com/suliang/common/AppConfig.kt View File

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

+ 0
- 1
lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragmentVM.kt View File

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

+ 7
- 6
lib/common/src/main/java/com/suliang/common/eventbus/NonStickyMutableLiveData.kt View File

@@ -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 {

+ 1
- 0
lib/common/src/main/java/com/suliang/common/extension/ViewClickExtension.kt View File

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

+ 1
- 1
lib/common/src/main/java/com/suliang/common/util/file/FileUtil.kt View File

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


+ 1
- 1
lib/common/src/main/java/com/suliang/common/util/media/IMP.kt View File

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

+ 2
- 2
lib/common/src/main/java/com/suliang/common/util/media/MPManager.kt View File

@@ -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() {

+ 6
- 27
lib/common/src/main/java/com/suliang/common/util/media/MPUtil.kt View File

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

Loading…
Cancel
Save