<component name="ProjectCodeStyleConfiguration"> | <component name="ProjectCodeStyleConfiguration"> | ||||
<code_scheme name="Project" version="173"> | <code_scheme name="Project" version="173"> | ||||
<DBN-PSQL> | |||||
<case-options enabled="true"> | |||||
<option name="KEYWORD_CASE" value="lower" /> | |||||
<option name="FUNCTION_CASE" value="lower" /> | |||||
<option name="PARAMETER_CASE" value="lower" /> | |||||
<option name="DATATYPE_CASE" value="lower" /> | |||||
<option name="OBJECT_CASE" value="preserve" /> | |||||
</case-options> | |||||
<formatting-settings enabled="false" /> | |||||
</DBN-PSQL> | |||||
<DBN-SQL> | |||||
<case-options enabled="true"> | |||||
<option name="KEYWORD_CASE" value="lower" /> | |||||
<option name="FUNCTION_CASE" value="lower" /> | |||||
<option name="PARAMETER_CASE" value="lower" /> | |||||
<option name="DATATYPE_CASE" value="lower" /> | |||||
<option name="OBJECT_CASE" value="preserve" /> | |||||
</case-options> | |||||
<formatting-settings enabled="false"> | |||||
<option name="STATEMENT_SPACING" value="one_line" /> | |||||
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" /> | |||||
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" /> | |||||
</formatting-settings> | |||||
</DBN-SQL> | |||||
<JavaCodeStyleSettings> | <JavaCodeStyleSettings> | ||||
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" /> | <option name="JD_ALIGN_PARAM_COMMENTS" value="false" /> | ||||
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" /> | <option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" /> | ||||
<JetCodeStyleSettings> | <JetCodeStyleSettings> | ||||
<option name="SPACE_AROUND_RANGE" value="true" /> | <option name="SPACE_AROUND_RANGE" value="true" /> | ||||
<option name="SPACE_BEFORE_TYPE_COLON" value="true" /> | <option name="SPACE_BEFORE_TYPE_COLON" value="true" /> | ||||
<option name="WRAP_ELVIS_EXPRESSIONS" value="2" /> | |||||
<option name="IF_RPAREN_ON_NEW_LINE" value="false" /> | |||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> | ||||
</JetCodeStyleSettings> | </JetCodeStyleSettings> | ||||
<DBN-PSQL> | <DBN-PSQL> | ||||
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> | <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> | ||||
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" /> | <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" /> | ||||
<option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" /> | <option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" /> | ||||
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" /> | |||||
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" /> | |||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" /> | |||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" /> | |||||
<option name="VARIABLE_ANNOTATION_WRAP" value="2" /> | <option name="VARIABLE_ANNOTATION_WRAP" value="2" /> | ||||
<option name="ENUM_CONSTANTS_WRAP" value="2" /> | <option name="ENUM_CONSTANTS_WRAP" value="2" /> | ||||
<option name="WRAP_ON_TYPING" value="0" /> | <option name="WRAP_ON_TYPING" value="0" /> |
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/content_learn_base.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/content_learn_base.xml" value="0.4979166666666667" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_bottom_auto_play_select.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_bottom_auto_play_select.xml" value="0.4979166666666667" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_bottom_course_more.xml" value="0.33" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_bottom_course_more.xml" value="0.33" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_common.xml" value="0.30978260869565216" /> | |||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_item_select_repeat.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_item_select_repeat.xml" value="0.4979166666666667" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_lesson_learn.xml" value="0.4144927536231884" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_lesson_learn.xml" value="0.4144927536231884" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_lesson.xml" value="0.33" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_lesson.xml" value="0.33" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_title_bar.xml" value="0.25052083333333336" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_title_bar.xml" value="0.25052083333333336" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_course_lesson.xml" value="0.4785615491009682" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_course_lesson.xml" value="0.4785615491009682" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_empty.xml" value="0.4979166666666667" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_empty.xml" value="0.4979166666666667" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_spell_single_word.xml" value="0.23632218844984804" /> | |||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_course_progress.xml" value="0.25" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_course_progress.xml" value="0.25" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_coursepack.xml" value="0.43500866551126516" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_coursepack.xml" value="0.43500866551126516" /> | ||||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/merge_recyclerview_smart_refresh_layout.xml" value="0.34427083333333336" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/merge_recyclerview_smart_refresh_layout.xml" value="0.34427083333333336" /> |
} | } | ||||
dependencies { | dependencies { | ||||
implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs') | |||||
implementation fileTree(include: ['*.jar', "*.aar"], dir: 'libs') | |||||
// implementation 'androidx.legacy:legacy-support-v4:1.0.0' | // implementation 'androidx.legacy:legacy-support-v4:1.0.0' | ||||
implementation project(path: ':lib:common') | implementation project(path: ':lib:common') | ||||
// implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' | // implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' | ||||
// implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' | // implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' | ||||
// implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' | // implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' | ||||
rootProject.ext.dependencies_required.each{ k, v -> implementation v} | |||||
rootProject.ext.dependencies_required.each { k, v -> implementation v } | |||||
testImplementation rootProject.ext.dependencies_testImplementation.junit | testImplementation rootProject.ext.dependencies_testImplementation.junit | ||||
rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v} | |||||
rootProject.ext.dependencies_androidTestImplementation.each { k, v -> androidTestImplementation v } | |||||
def customDependencies = rootProject.ext.dependencies_custom | |||||
def customDependencies = rootProject.ext.dependencies_custom | |||||
//SmartRefreshLayout | //SmartRefreshLayout | ||||
implementation customDependencies.SmartRefreshLayout | implementation customDependencies.SmartRefreshLayout | ||||
implementation 'io.github.scwang90:refresh-header-classics:2.0.5' //经典刷新头 | |||||
implementation 'io.github.scwang90:refresh-header-classics:2.0.5' //经典刷新头 | |||||
//SqlCipher | //SqlCipher | ||||
implementation customDependencies.SqlCipher | implementation customDependencies.SqlCipher | ||||
//androidx-sqlite | //androidx-sqlite | ||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | ||||
//Gson | //Gson | ||||
implementation customDependencies.Gson | implementation customDependencies.Gson | ||||
//protobuf | |||||
implementation customDependencies.protobuf_java | |||||
implementation customDependencies.protobuf_java_format | |||||
//grpc | |||||
implementation customDependencies.annotation_api | |||||
implementation customDependencies.grpc_okhttp | |||||
implementation customDependencies.grpc_android | |||||
implementation customDependencies.grpc_protobuf | |||||
implementation customDependencies.grpc_stub | |||||
} | } |
import android.view.ViewGroup | import android.view.ViewGroup | ||||
import com.suliang.common.base.adapter.BaseAdapterViewHolder | import com.suliang.common.base.adapter.BaseAdapterViewHolder | ||||
import androidx.viewbinding.ViewBinding | |||||
import com.suliang.common.base.adapter.BaseAdapter | |||||
import com.suliang.common.base.adapter.BaseRVAdapter | |||||
import com.suliang.common.extension.click | import com.suliang.common.extension.click | ||||
import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
import com.xkl.cdl.databinding.DialogItemSelectRepeatBinding | import com.xkl.cdl.databinding.DialogItemSelectRepeatBinding | ||||
* create 2020/6/29 13:44 | * create 2020/6/29 13:44 | ||||
* Describe: 自动播放弹窗列表选择适配器 | * Describe: 自动播放弹窗列表选择适配器 | ||||
*/ | */ | ||||
class AdapterAutoPlaySelectRepeat : BaseAdapter<Void?>() { | |||||
class AdapterAutoPlaySelectRepeat : BaseRVAdapter<Void?>() { | |||||
override fun coverEmptyViewHolder(parent: ViewGroup): BaseAdapterViewHolder { | override fun coverEmptyViewHolder(parent: ViewGroup): BaseAdapterViewHolder { | ||||
return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_empty)) | return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_empty)) | ||||
} | } |
import android.view.View | import android.view.View | ||||
import android.view.ViewGroup | import android.view.ViewGroup | ||||
import com.suliang.common.base.adapter.BaseAdapterVM | |||||
import com.suliang.common.base.adapter.BaseRVAdapterVM | |||||
import com.suliang.common.base.adapter.BaseAdapterViewHolder | import com.suliang.common.base.adapter.BaseAdapterViewHolder | ||||
import com.suliang.common.databinding.ItemEmptyBinding | import com.suliang.common.databinding.ItemEmptyBinding | ||||
import com.suliang.common.extension.click | import com.suliang.common.extension.click | ||||
* Describe: 学习中心课程包适配器 | * Describe: 学习中心课程包适配器 | ||||
*/ | */ | ||||
class AdapterCoursePackWithLearCenter(vm: CoursePackFragmentViewModel) : | class AdapterCoursePackWithLearCenter(vm: CoursePackFragmentViewModel) : | ||||
BaseAdapterVM<CoursePack, CoursePackFragmentViewModel>(vm) { | |||||
BaseRVAdapterVM<CoursePack, CoursePackFragmentViewModel>(vm) { | |||||
override fun onBindEmptyViewHolder(holder: BaseAdapterViewHolder) { | override fun onBindEmptyViewHolder(holder: BaseAdapterViewHolder) { | ||||
(holder.binding as ItemEmptyBinding).apply { | (holder.binding as ItemEmptyBinding).apply { |
import android.view.View | import android.view.View | ||||
import android.view.ViewGroup | import android.view.ViewGroup | ||||
import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||
import com.suliang.common.base.adapter.BaseAdapterVM | |||||
import com.suliang.common.base.adapter.BaseRVAdapterVM | |||||
import com.suliang.common.base.adapter.BaseAdapterViewHolder | import com.suliang.common.base.adapter.BaseAdapterViewHolder | ||||
import com.suliang.common.extension.click | import com.suliang.common.extension.click | ||||
import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
* create 2022/3/29 16:43 | * create 2022/3/29 16:43 | ||||
* Describe: 课程课时列表适配器 | * Describe: 课程课时列表适配器 | ||||
*/ | */ | ||||
class AdapterLesson(vm: CourseMainFragmentViewModel) : BaseAdapterVM<Lesson, CourseMainFragmentViewModel>(vm) { | |||||
class AdapterLesson(vm: CourseMainFragmentViewModel) : BaseRVAdapterVM<Lesson, CourseMainFragmentViewModel>(vm) { | |||||
/** 选中item的位置 */ | /** 选中item的位置 */ | ||||
var selectPos = -1 | var selectPos = -1 | ||||
//事件: 将章节名称的点击事件取消了 | |||||
//事件 | |||||
layoutContent.click { | layoutContent.click { | ||||
//选中项非当前项,则需要改变选中颜色,直接通知更新,调用对应位置的notfy | //选中项非当前项,则需要改变选中颜色,直接通知更新,调用对应位置的notfy | ||||
if (selectPos != position) { | if (selectPos != position) { | ||||
notifyItemChanged(selectPos) | |||||
val temp = selectPos | |||||
selectPos = position | selectPos = position | ||||
notifyItemChanged(temp) | |||||
notifyItemChanged(selectPos) | notifyItemChanged(selectPos) | ||||
} | } | ||||
notifyItemChanged(position) | |||||
notifyItemChanged(position, null) | |||||
onItemClick.invoke(it, position, lesson) | onItemClick.invoke(it, position, lesson) | ||||
} | } | ||||
} | } |
package com.xkl.cdl.adapter | |||||
import android.annotation.SuppressLint | |||||
import android.text.Spannable | |||||
import android.text.SpannableString | |||||
import android.text.SpannableStringBuilder | |||||
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 | |||||
import com.suliang.common.util.media.MPManager | |||||
import com.xkl.cdl.R | |||||
import com.xkl.cdl.data.AppConstants | |||||
import com.xkl.cdl.data.bean.SpellItemBean | |||||
import com.xkl.cdl.databinding.ItemSpellSingleWordBinding | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/12 15:13 | |||||
* Describe: 拼写适配器 | |||||
*/ | |||||
class AdapterSpell : BaseRVAdapter<SpellItemBean>() { | |||||
//默认发音 | |||||
var defaultSoundWay = 0 | |||||
//当前状态 是否拼写中 | |||||
var isSpelling = true | |||||
@SuppressLint("NotifyDataSetChanged") set(value) { | |||||
field = value | |||||
//初始设置,设置数据的时候就已经将值修改为true,会设置数据的进行notify了 | |||||
if (!value) notifyDataSetChanged() | |||||
} | |||||
//纠错矫正 | |||||
var isErrorRecovery = false | |||||
@SuppressLint("NotifyDataSetChanged") set(value) { | |||||
field = value | |||||
//设置数据时默认为false ,只有后面纠错的时候改为true后才需要全更新 | |||||
if (value) notifyDataSetChanged() | |||||
} | |||||
/** 初始设置数据,则将拼写状态修改为true */ | |||||
override fun setData(data : MutableList<SpellItemBean>?) { | |||||
isSpelling = true | |||||
isErrorRecovery = false | |||||
super.setData(data) | |||||
} | |||||
/** | |||||
* 纠错时点击事件 | |||||
* @param showValue 用于显示的值 | |||||
* @param isOver 纠错是否完成 | |||||
* @param nextPositon 纠错中的下一项 | |||||
*/ | |||||
lateinit var onItemRecoveryClick : (showValue : SpannableStringBuilder, isOver:Boolean, nextPosition:Int) -> Unit | |||||
/** | |||||
* 拼写时的事件 | |||||
* @param selectedValue 选择的值 | |||||
* @param nextPosition 下一个item的位置,拼写未完成时为下一列的位置,拼写完成为错误的第一个位置 | |||||
* @param isOver 拼写是否完成 为true时则判断为拼写完成 | |||||
* @param correctValue 正确的拼写值,但未选中的赋值了颜色 | |||||
* @param errorSize 错误的个数,拼写完成后才会有这个值 | |||||
*/ | |||||
lateinit var onItemSpellingClickListener : (selectedValue : SpannableStringBuilder, nextPosition : Int, isOver : Boolean , correctValue : SpannableStringBuilder, errorSize : Int) -> Unit | |||||
override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder { | |||||
return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_spell_single_word)) | |||||
} | |||||
@SuppressLint("NotifyDataSetChanged") | |||||
override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) { | |||||
val item = getItem(position) | |||||
val binding = holder.binding as ItemSpellSingleWordBinding | |||||
binding.tv.text = item.char.toString() | |||||
if (isSpelling) { //可拼写点击时,选中灰色,未选中白色 | |||||
binding.tv.setBackgroundColor(ContextCompat.getColor(context, if (item.isSelected) R.color.gray_1 else R.color.white)) | |||||
//点击 | |||||
binding.tv.setOnClickListener { | |||||
if (!isSpellingItemCouldClick(position)) { //不可点击 | |||||
clickInvalid() | |||||
} else { //可点击 | |||||
playItem(item.char) | |||||
item.isSelected = true | |||||
getItem(if (position % 2 == 0) position + 1 else position - 1).isSelected = false | |||||
notifyDataSetChanged() | |||||
notifyUIUpdate(position) | |||||
} | |||||
} | |||||
} else { //不可拼写时 正确项未选中为红色,否则为白色 | |||||
binding.tv.setBackgroundColor(ContextCompat.getColor(context, | |||||
if (item.isCorrect && !item.isSelected) R.color.red_2 else R.color.white)) | |||||
binding.tv.setOnClickListener { | |||||
//非纠错校正则点击无效 | |||||
if (!isErrorRecovery) return@setOnClickListener | |||||
//校正点击需要从前向后,先判断前面是否有未点击的,有未点击则判断为不可点击 | |||||
if (!isRecoveryItemCouldClick(position)) { | |||||
clickInvalid() | |||||
} else { | |||||
playItem(item.char) | |||||
item.isSelected = true | |||||
notifyItemChanged(position) | |||||
notifyUIUpdate(position) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/**点击无效 | |||||
* 提示,播放mistake音 | |||||
*/ | |||||
private fun clickInvalid() { | |||||
Toast.makeText(context, "请依次点击", Toast.LENGTH_SHORT).show() | |||||
MPManager.play("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") | |||||
} | |||||
} else { | |||||
MPManager.play("common_voice/konge.mp3") | |||||
} | |||||
} | |||||
/** | |||||
* 当前item是否可点击 | |||||
* @param position Int item中的位置 | |||||
*/ | |||||
private fun isSpellingItemCouldClick(position : Int) : Boolean { | |||||
//第一列 position 为 0 2 4 6 8 | |||||
//第二列 position为 1 3 5 7 9 | |||||
if (position == 0 || position == 1) return true | |||||
//上一列的第一行坐标 | |||||
val previousColumnFirstPosition = if (position % 2 == 0) position - 2 else position - 1 | |||||
if (getItem(previousColumnFirstPosition).isSelected) return true | |||||
//上一列的第二行坐标 | |||||
val previousColumnSecondPosition = previousColumnFirstPosition + 1 | |||||
return getItem(previousColumnSecondPosition).isSelected | |||||
} | |||||
/** | |||||
* 当前item是否可点击 | |||||
* @param position Int item中的位置 | |||||
*/ | |||||
private fun isRecoveryItemCouldClick(position : Int) : Boolean { | |||||
//第一列 position 为 0 2 4 6 8 | |||||
//第二列 position为 1 3 5 7 9 | |||||
if (position == 0 || position == 1) return true | |||||
//本列之前,即上一列的第二行是否有正确项未被点击 | |||||
val previousColumnSecondPosition = if (position % 2 == 0) position - 1 else position - 2 | |||||
(0 .. previousColumnSecondPosition).forEach { | |||||
val item = getItem(it) | |||||
//正确项但未被选中 | |||||
if (item.isCorrect && !item.isSelected) { | |||||
return false | |||||
} | |||||
} | |||||
return true | |||||
} | |||||
/** | |||||
* 获取拼写字用于去更新ui | |||||
* @param position Int 点击项 | |||||
* @return SpannableStringBuilder 拼写的值 | |||||
*/ | |||||
private fun notifyUIUpdate(position : Int) { | |||||
//记录拼写的值 | |||||
val builder = SpannableStringBuilder() | |||||
if (isSpelling) { | |||||
//是否是最后一列 | |||||
val isOver = (position == itemCount - 1) || (position == itemCount - 2) | |||||
var errorSize = 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 | |||||
} | |||||
} else { //拼写完成 | |||||
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 | |||||
//下一个位置定义到第一个错误的位置 | |||||
if (nextPosition == 0 ){ | |||||
nextPosition = index | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//回调出去 | |||||
onItemSpellingClickListener(builder,nextPosition,isOver,correctValue,errorSize) | |||||
} | |||||
//纠错: 拼接所有正确的内容,未选中的需要设置颜色 | |||||
if (isErrorRecovery) { | |||||
//是否完成的标记 | |||||
var isOver = true | |||||
//下一个错误的位置 | |||||
var nextPosition = 0 | |||||
//当前列的第二行坐标 | |||||
val currentSecondPosition = if (position % 2 == 0) position + 1 else position | |||||
getData().forEachIndexed { index, it -> | |||||
when { | |||||
it.isCorrect -> when { | |||||
it.isSelected -> builder.append(it.char) | |||||
else -> { | |||||
builder.append(SpannableString(it.char.toString()).apply { | |||||
setSpan(ForegroundColorSpan(ContextCompat.getColor(context, R.color.theme_color)), | |||||
0, | |||||
1, | |||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | |||||
}) | |||||
//判断是否还有大于当前列第二行坐标的未选,如果有,则为纠错未完成 | |||||
if (index > currentSecondPosition) { | |||||
isOver = false | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
onItemRecoveryClick(builder, isOver,nextPosition) | |||||
} | |||||
} | |||||
} |
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 | |||||
/** | |||||
* Created by su on 2018/11/26. | |||||
* Des 分割线 | |||||
*/ | |||||
class SpellItemDecoration : ItemDecoration() { | |||||
private var 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 | |||||
var left = 0 | |||||
var top = 0 | |||||
var right = 0 | |||||
var bottom = 0 | |||||
when{ | |||||
position % 2 == 0 -> { //第一行 | |||||
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 | |||||
} | |||||
} | |||||
} | |||||
else -> { //第二行 | |||||
top = space | |||||
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 | |||||
} | |||||
} | |||||
} | |||||
} | |||||
outRect.set(left,top,right, bottom) | |||||
} | |||||
} |
/**学后总测试*/ | /**学后总测试*/ | ||||
const val TEST_TYPE_AFTER_TOTAL = 5 | const val TEST_TYPE_AFTER_TOTAL = 5 | ||||
/**备忘录测试*/ | |||||
/**备忘本测试*/ | |||||
const val TEST_TYPE_MEMO = 6 | const val TEST_TYPE_MEMO = 6 | ||||
/**作文知识点测试*/ | /**作文知识点测试*/ | ||||
const val TEST_SCORE_LEVEL_1 = 80 | const val TEST_SCORE_LEVEL_1 = 80 | ||||
/** 测试通过 >= 太棒了 90 */ | /** 测试通过 >= 太棒了 90 */ | ||||
const val TEST_SCORE_LEVEL_2 = 90 | const val TEST_SCORE_LEVEL_2 = 90 | ||||
const val ACTION_START_BEFORE_TEST = 1 | |||||
const val ACTION_START_AFTER_TEST = 2 | |||||
const val ACTION_START_TOTAL_TEST = 3 | |||||
const val ACTION_START_TOTAL_AFTER_TEST = 4 | |||||
//测试的题目类型 | //测试的题目类型 | ||||
/** 选择题类型 */ | /** 选择题类型 */ | ||||
const val TEST_QUEST_TYPE_CHOICE = 1 | |||||
const val TEST_QUEST_TYPE_CHOICE = 1L | |||||
/** 口语对话测试 */ | /** 口语对话测试 */ | ||||
const val TEST_QUEST_TYPE_SPOKEN_DIALOGUE = 4 | |||||
const val TEST_QUEST_TYPE_GAP_FILLING = 2 //填空题 | |||||
const val TEST_QUEST_TYPE_JUDGE = 3 //判断题 | |||||
const val TEST_QUEST_TYPE_SPOKEN_DIALOGUE = 4L | |||||
const val TEST_QUEST_TYPE_GAP_FILLING = 2L //填空题 | |||||
const val TEST_QUEST_TYPE_JUDGE = 3L //判断题 | |||||
/**总测试 */ | /**总测试 */ | ||||
const val TEST_COUNT_TOTAL = 25 | const val TEST_COUNT_TOTAL = 25 | ||||
/**章节测试*/ | /**章节测试*/ | ||||
const val SOUND_TYPE_UK = 2 //英 | const val SOUND_TYPE_UK = 2 //英 | ||||
/**中文*/ | /**中文*/ | ||||
const val SOUND_TYPE_CN = 3 //中文 | const val SOUND_TYPE_CN = 3 //中文 | ||||
// 拼写的最小时间 1.6 秒 | |||||
const val SPELL_TEST_MIN_TIME = 1600 | |||||
//单个单词的最小计时 6秒 | |||||
const val TEST_MIN_TIME = 6000 | |||||
const val TEST_CORRECT = 1L //答题正确 | |||||
const val TEST_ERROR = -1L //答题错误 | |||||
const val TEST_UN_ANSWER = 0L //答题未答 | |||||
/**测试正确,到下一题的时间*/ | |||||
const val TEST_TO_NEXT_CORRECT_TIME = 500L | |||||
/**测试错误: 未答到一题的时间*/ | |||||
const val TEST_TO_NEXT_ERROR_TIME = 2000L | |||||
/** 对话框类型: 测试 */ | |||||
const val DIALOG_TYPE_EXAM = 1 | |||||
/** 对话框类型: 学习 */ | |||||
const val DIALOG_TYPE_LEARN = 2 | |||||
/**--- 总线动作 --------------------------------- */ | |||||
/**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 DIALOG_START_LEARN = 1 | |||||
} | } |
package com.xkl.cdl.data.bean | |||||
import android.os.Parcel | |||||
import android.os.Parcelable | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/14 9:37 | |||||
* Describe: 学习测试对话框--传参实体 | |||||
* 所有的测试必传测试类型 [examType] | |||||
* | |||||
* 学前总测 :[examType],[score],[correctNumber],[errorNumber] | |||||
* | |||||
* 课时学前测试 | |||||
* | |||||
* 课时学后测试 | |||||
* | |||||
* 课时学后总测试 | |||||
* | |||||
* 课程测试(服务中心) | |||||
* | |||||
* 备忘本测试 | |||||
* | |||||
* 作文知识点测试 | |||||
* | |||||
* 词汇量测试 | |||||
* | |||||
*/ | |||||
class LearnDialogBean(val dialogType:Int) : Parcelable { | |||||
/*---------------测试使用------------------------------------*/ | |||||
/** 测试类型 */ | |||||
var examType : Int = 0 | |||||
/** 测试分数 */ | |||||
var score : Int = 0 | |||||
/**正确数*/ | |||||
var correctNumber :Int = 0 | |||||
/** 错误数据 */ | |||||
var errorNumber : Int = 0 | |||||
constructor(parcel : Parcel) : this(parcel.readInt()) { | |||||
examType = parcel.readInt() | |||||
score = parcel.readInt() | |||||
correctNumber = parcel.readInt() | |||||
errorNumber = parcel.readInt() | |||||
} | |||||
override fun writeToParcel(parcel : Parcel, flags : Int) { | |||||
parcel.writeInt(dialogType) | |||||
parcel.writeInt(examType) | |||||
parcel.writeInt(score) | |||||
parcel.writeInt(correctNumber) | |||||
parcel.writeInt(errorNumber) | |||||
} | |||||
override fun describeContents() : Int { | |||||
return 0 | |||||
} | |||||
companion object CREATOR : Parcelable.Creator<LearnDialogBean> { | |||||
override fun createFromParcel(parcel : Parcel) : LearnDialogBean { | |||||
return LearnDialogBean(parcel) | |||||
} | |||||
override fun newArray(size : Int) : Array<LearnDialogBean?> { | |||||
return arrayOfNulls(size) | |||||
} | |||||
} | |||||
} |
package com.xkl.cdl.data.bean | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/11 17:40 | |||||
* Describe: 拼写item | |||||
* @property char Char 当前字母 | |||||
* @property isCorrect Boolean 是否正确选项 | |||||
* @property isSelected Boolean 是否选中 | |||||
*/ | |||||
data class SpellItemBean(val char : Char , val isCorrect : Boolean ,var isSelected: Boolean = false) |
var error1: String = "" //错误选项1 | var error1: String = "" //错误选项1 | ||||
var error2: String = "" //错误选项2 | var error2: String = "" //错误选项2 | ||||
var error3 : String = "" //错误选项3 | var error3 : String = "" //错误选项3 | ||||
var type = 0 //测试题类型 | |||||
var type = 0L //测试题类型 | |||||
var chapterId : Long = 0 //章节id | var chapterId : Long = 0 //章节id | ||||
var lessonId : Long = 0 //课时id | var lessonId : Long = 0 //课时id |
val showTitle : String, //去测试本地显示名称,学前学后 为课程名称,课时前、后为课时名称 | val showTitle : String, //去测试本地显示名称,学前学后 为课程名称,课时前、后为课时名称 | ||||
val saveTitle : String, //测试试卷上传名称 : 学习中心:课程名称 + “ ” + 学习类型 课程测试:课程名称 + “ ” 学习类型 + (章节拼接名称) 词汇量测试:词汇量测试:阶段名称 | val saveTitle : String, //测试试卷上传名称 : 学习中心:课程名称 + “ ” + 学习类型 课程测试:课程名称 + “ ” 学习类型 + (章节拼接名称) 词汇量测试:词汇量测试:阶段名称 | ||||
) { | ) { | ||||
var coursePackId : Long = 0 //课程包id, 测试错误上次数据需要 | |||||
var coursePackType:Int = 0 //课程包类型 | var coursePackType:Int = 0 //课程包类型 | ||||
var courseId : Long = 0 //课程id | |||||
var courseType : Int= 0 //课程类型 | var courseType : Int= 0 //课程类型 | ||||
var testData : List<ExamBean>? = null //测试题数据 | var testData : List<ExamBean>? = null //测试题数据 | ||||
var lesson : Lesson? = null //课时测试时必传 | var lesson : Lesson? = null //课时测试时必传 | ||||
// 课程/课时学前测试正确错误列表 key=> {chapter_id}_{lesson_id}_{entity_id} value=>正确 true;错误 false | // 课程/课时学前测试正确错误列表 key=> {chapter_id}_{lesson_id}_{entity_id} value=>正确 true;错误 false | ||||
var mExamWRMap : HashMap<String, Boolean>? = null | var mExamWRMap : HashMap<String, Boolean>? = null | ||||
//学前总测试与章节前测试时学习错误集合 | |||||
var newErrorMap : HashMap<String, Boolean> = hashMapOf() | |||||
//口语对话测试数据: 仅在口语对话课时后测试中使用该课时的学习数据 | |||||
var allSentencesList : List<List<DialogueSpokenItem>>? = null | |||||
} | } |
package com.xkl.cdl.data.event | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/14 15:50 | |||||
* Describe: 事件通知更新实体封装 | |||||
* @param subjectId 项目id | |||||
* @param courseId 课程id | |||||
* @param actionFlag 动作id 动作 或 传递数据 | |||||
*/ | |||||
class LearnEventData(val subjectId : Int, var courseId : Long, val actionFlag : Int) { | |||||
//学前总测结束传递数据 | |||||
var scoreValue = 0 //分数 | |||||
var newErrorMap : HashMap<String, Boolean>? = null //新错误单词列表 | |||||
} |
} | } | ||||
} | } | ||||
} | } | ||||
/** | |||||
* 获取测试类型对应的名称 | |||||
* @param examType Int 测试类型 | |||||
* @return String 测试类型名称 | |||||
*/ | |||||
fun getExamTypeName(examType: Int): String{ | |||||
return when(examType){ | |||||
AppConstants.TEST_TYPE_BEFORE_TOTAL -> "学前总测试" | |||||
AppConstants.TEST_TYPE_BEFORE -> "课时学前测试" | |||||
AppConstants.TEST_TYPE_AFTER -> "课时学后测试" | |||||
AppConstants.TEST_TYPE_AFTER_TOTAL -> "学后总测试" | |||||
AppConstants.TEST_TYPE_MEMO -> "备忘本测试" | |||||
AppConstants.TEST_TYPE_COMPOSITION -> "作文知识点测试" | |||||
AppConstants.TEST_TYPE_SERVICE_CENTER -> "课程测试" | |||||
AppConstants.TEST_TYPE_NORMAL -> "词汇量测试" | |||||
else -> "" | |||||
} | |||||
} | |||||
} | } |
package com.xkl.cdl.data.manager | package com.xkl.cdl.data.manager | ||||
import com.suliang.common.util.SpUtils | |||||
import com.xkl.cdl.data.AppConstants | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
* create 2022/4/8 17:47 | * create 2022/4/8 17:47 | ||||
*/ | */ | ||||
object UserInfoManager { | object UserInfoManager { | ||||
/** 获取默认发音方式 */ | |||||
fun getDefaultSoundWay() : Int{ | |||||
return SpUtils.instance.decode("defaultSoundWay",Int::class.java,AppConstants.SOUND_TYPE_UK) | |||||
} | |||||
/** 存储默认发音方式 */ | |||||
fun putDefaultSoundWay(soundWay : Int){ | |||||
SpUtils.instance.encode("defaultSoundWay",soundWay) | |||||
} | |||||
} | } |
package com.xkl.cdl.dialog | |||||
import android.graphics.drawable.Drawable | |||||
import android.os.Bundle | |||||
import android.view.View | |||||
import androidx.core.content.ContextCompat | |||||
import com.suliang.common.AppConfig | |||||
import com.suliang.common.base.BaseDialogFragment | |||||
import com.suliang.common.util.DrawableUti | |||||
import com.xkl.cdl.databinding.DialogCommonBinding | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/15 10:22 | |||||
* Describe: 通用提示弹窗 | |||||
*/ | |||||
class CommonDialog private constructor() : BaseDialogFragment<DialogCommonBinding>() { | |||||
companion object { | |||||
@JvmStatic | |||||
fun newInstance(dialogBean : CommonDialogBean) : CommonDialog { | |||||
val args = Bundle() | |||||
args.putParcelable(AppConfig.INTENT_1, dialogBean) | |||||
val fragment = CommonDialog() | |||||
fragment.arguments = args | |||||
return fragment | |||||
} | |||||
@JvmStatic | |||||
fun newInstance(dialogBean : CommonDialogBean, img : Drawable) : CommonDialog { | |||||
val args = Bundle() | |||||
args.putParcelable(AppConfig.INTENT_1, dialogBean) | |||||
val byteArray = DrawableUti.bitmapToByteArray(DrawableUti.drawableToBitmap(img)) | |||||
args.putByteArray(AppConfig.INTENT_2, byteArray) | |||||
val fragment = CommonDialog() | |||||
fragment.arguments = args | |||||
return fragment | |||||
} | |||||
} | |||||
lateinit var onCommonDialogButtonClickListener : (dialog : CommonDialog, isRightClick : Boolean) -> Unit | |||||
override fun initFragment() { | |||||
(requireArguments()[AppConfig.INTENT_1] as CommonDialogBean).run { | |||||
titleColor?.let { | |||||
binding.tvTitle.setTextColor(ContextCompat.getColor(requireContext(),it)) | |||||
} | |||||
titleText?.let { | |||||
binding.tvTitle.setText(it) | |||||
binding.tvTitle.visibility = View.VISIBLE | |||||
} | |||||
contentText?.let { | |||||
binding.tvContent.setTextColor(ContextCompat.getColor(requireContext(),it)) | |||||
binding.tvContent.visibility = View.VISIBLE | |||||
} | |||||
contentColor?.let { | |||||
binding.tvContent.setText(it) | |||||
} | |||||
leftColor?.let { | |||||
binding.tvLeft.setTextColor(ContextCompat.getColor(requireContext(),it)) | |||||
} | |||||
leftText?.let { | |||||
binding.tvLeft.setText(it) | |||||
onCommonDialogButtonClickListener(this@CommonDialog,false) | |||||
}?:let { | |||||
binding.tvLeft.visibility = View.GONE | |||||
binding.vSpace.visibility = View.GONE | |||||
} | |||||
rightText?.let { | |||||
binding.tvRight.setTextColor(ContextCompat.getColor(requireContext(),it)) | |||||
onCommonDialogButtonClickListener(this@CommonDialog,true) | |||||
} | |||||
rightColor?.let { | |||||
binding.tvRight.setText(it) | |||||
} | |||||
imgFlag?.let { | |||||
binding.img.setImageResource(it) | |||||
binding.img.visibility = View.VISIBLE | |||||
} | |||||
} | |||||
(requireArguments()[AppConfig.INTENT_2] as? ByteArray)?.let { | |||||
binding.img.setImageBitmap(DrawableUti.byteArrayToBitmap(it)) | |||||
binding.img.visibility = View.VISIBLE | |||||
} | |||||
} | |||||
} |
package com.xkl.cdl.dialog | |||||
import android.os.Parcel | |||||
import android.os.Parcelable | |||||
import androidx.annotation.ColorRes | |||||
import androidx.annotation.StringRes | |||||
/** | |||||
* author suliang | |||||
* create 2022/4/15 10:25 | |||||
* Describe: 通用弹窗设置实体 | |||||
*/ | |||||
data class CommonDialogBean(@StringRes val titleText : Int? = null, | |||||
@StringRes val contentText : Int? = null, | |||||
@StringRes val leftText : Int? = null, | |||||
@StringRes val rightText : Int? = null, | |||||
@StringRes val imgFlag : Int? = null, | |||||
@ColorRes val titleColor : Int? = null, | |||||
@ColorRes val contentColor : Int? = null, | |||||
@ColorRes val leftColor : Int? = null, | |||||
@ColorRes val rightColor : Int? = null) : Parcelable { | |||||
constructor(parcel : Parcel) : this(parcel.readValue(Int::class.java.classLoader) as? Int, | |||||
parcel.readValue(Int::class.java.classLoader) as? Int, | |||||
parcel.readValue(Int::class.java.classLoader) as? Int, | |||||
parcel.readValue(Int::class.java.classLoader) as? Int, | |||||
parcel.readValue(Int::class.java.classLoader) as? Int, | |||||
parcel.readValue(Int::class.java.classLoader) as? Int, | |||||
parcel.readValue(Int::class.java.classLoader) as? Int, | |||||
parcel.readValue(Int::class.java.classLoader) as? Int, | |||||
parcel.readValue(Int::class.java.classLoader) as? Int, | |||||
) { | |||||
} | |||||
override fun writeToParcel(parcel : Parcel, flags : Int) { | |||||
parcel.writeValue(titleText) | |||||
parcel.writeValue(contentText) | |||||
parcel.writeValue(leftText) | |||||
parcel.writeValue(rightText) | |||||
parcel.writeValue(imgFlag) | |||||
parcel.writeValue(titleColor) | |||||
parcel.writeValue(contentColor) | |||||
parcel.writeValue(leftColor) | |||||
parcel.writeValue(rightColor) | |||||
} | |||||
override fun describeContents() : Int { | |||||
return 0 | |||||
} | |||||
companion object CREATOR : Parcelable.Creator<CommonDialogBean> { | |||||
override fun createFromParcel(parcel : Parcel) : CommonDialogBean { | |||||
return CommonDialogBean(parcel) | |||||
} | |||||
override fun newArray(size : Int) : Array<CommonDialogBean?> { | |||||
return arrayOfNulls(size) | |||||
} | |||||
} | |||||
} |
package com.xkl.cdl.dialog | package com.xkl.cdl.dialog | ||||
import android.os.Bundle | |||||
import android.view.Gravity | |||||
import android.view.View | import android.view.View | ||||
import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||
import androidx.fragment.app.FragmentManager | |||||
import com.suliang.common.AppConfig | |||||
import com.suliang.common.base.BaseDialogFragment | import com.suliang.common.base.BaseDialogFragment | ||||
import com.suliang.common.extension.click | import com.suliang.common.extension.click | ||||
import com.suliang.common.util.LogUtil | import com.suliang.common.util.LogUtil | ||||
import com.suliang.common.util.os.ScreenUtil | |||||
import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
import com.xkl.cdl.data.bean.LearnDialogBean | |||||
import com.xkl.cdl.databinding.DialogLessonLearnBinding | import com.xkl.cdl.databinding.DialogLessonLearnBinding | ||||
/** | /** | ||||
* create 2022/4/2 15:47 | * create 2022/4/2 15:47 | ||||
* Describe: 学习的通用弹窗 | * Describe: 学习的通用弹窗 | ||||
*/ | */ | ||||
class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||||
private lateinit var dialogListener: DialogFragmentListener | |||||
class LearnDialog private constructor(): BaseDialogFragment<DialogLessonLearnBinding>() { | |||||
companion object { | |||||
fun newInstance(params : LearnDialogBean) : LearnDialog { | |||||
val args = Bundle() | |||||
args.putParcelable(AppConfig.INTENT_1, params) | |||||
val fragment = LearnDialog() | |||||
fragment.arguments = args | |||||
return fragment | |||||
} | |||||
} | |||||
/** 事件动作 */ | |||||
lateinit var onDialogListener : (action : Int, dialog : LearnDialog) -> Unit | |||||
//参数 | |||||
private lateinit var learnDialogBean : LearnDialogBean | |||||
override fun onCreate(savedInstanceState : Bundle?) { | |||||
super.onCreate(savedInstanceState) | |||||
//取值获参 | |||||
learnDialogBean = requireArguments().getParcelable(AppConfig.INTENT_1)!! | |||||
} | |||||
override fun initFragment() { | override fun initFragment() { | ||||
initClick() | |||||
// initLessonBeforeTest() | |||||
initLessonBeforeTestOver() | |||||
when (learnDialogBean.dialogType) { | |||||
AppConstants.DIALOG_TYPE_EXAM -> when (learnDialogBean.examType) { | |||||
AppConstants.TEST_TYPE_BEFORE_TOTAL -> initCourseBeforeTotalTestOver() | |||||
} | |||||
AppConstants.DIALOG_TYPE_LEARN -> { | |||||
} | |||||
} | |||||
} | } | ||||
fun initClick() { | |||||
dialogListener = if (parentFragment != null) { | |||||
parentFragment as DialogFragmentListener | |||||
} else { | |||||
activity as DialogFragmentListener | |||||
override fun onStart() { | |||||
super.onStart() | |||||
//设置dialog位置 | |||||
requireDialog().window?.let { | |||||
it.attributes.run { | |||||
y = ScreenUtil.getScreenHeight() / 5 | |||||
it.attributes = this | |||||
} | |||||
it.setGravity(Gravity.TOP) | |||||
} | } | ||||
} | } | ||||
/** 初始化分数 */ | |||||
private fun initScore() { | |||||
//插画 | |||||
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)) | |||||
} | |||||
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)) | |||||
} | |||||
else -> { | |||||
binding.imgIv.setImageResource(R.mipmap.test_score_level_3) | |||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(),R.color.red_1)) | |||||
} | |||||
} | |||||
//分数 | |||||
binding.tvScore.run { | |||||
visibility = View.VISIBLE | |||||
text = "${learnDialogBean.score}分" | |||||
} | |||||
//本次测试成绩显示 | |||||
binding.tvTip.visibility = View.VISIBLE | |||||
} | |||||
/** 设置成绩数量 */ | |||||
private fun initNumber(){ | |||||
binding.incStatisticsNumber.run { | |||||
tvCorrectNumber.text = "${learnDialogBean.correctNumber}" | |||||
tvErrorNumber.text = "${learnDialogBean.errorNumber}" | |||||
} | |||||
} | |||||
/** 学前总测试 结束 */ | |||||
private fun initCourseBeforeTotalTestOver() { | |||||
binding.groupTotalTest.visibility = View.VISIBLE | |||||
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() { | fun initLessonBeforeTest() { | ||||
binding.tvTitle.text = "课时学前测试" | binding.tvTitle.text = "课时学前测试" | ||||
binding.tvLessonName.text = "章节课时名称" | binding.tvLessonName.text = "章节课时名称" | ||||
binding.tvRight.text = "开始测试" | binding.tvRight.text = "开始测试" | ||||
binding.tvLeft.visibility = View.VISIBLE | binding.tvLeft.visibility = View.VISIBLE | ||||
binding.tvLeft.text = "随便设置" | binding.tvLeft.text = "随便设置" | ||||
binding.tvRight.click { | binding.tvRight.click { | ||||
LogUtil.e("Dialog -- > show()") | LogUtil.e("Dialog -- > show()") | ||||
} | } | ||||
binding.tvLeft.click { | binding.tvLeft.click { | ||||
LogUtil.e("Dialog -- > hide()") | LogUtil.e("Dialog -- > hide()") | ||||
} | } | ||||
} | } | ||||
fun initLessonBeforeTestOver() { | fun initLessonBeforeTestOver() { | ||||
binding.tvScore.run { | binding.tvScore.run { | ||||
visibility = View.VISIBLE | visibility = View.VISIBLE | ||||
binding.tvRight.click { | binding.tvRight.click { | ||||
// TODO: 2022/4/2 开始学习 | // TODO: 2022/4/2 开始学习 | ||||
} | } | ||||
} | } | ||||
fun initLessonAfterTestOver() { | fun initLessonAfterTestOver() { | ||||
//未通过、通过和通过已通过,均为一致,只是显示的状态不同 | //未通过、通过和通过已通过,均为一致,只是显示的状态不同 | ||||
val score = if (1 + 1 == 2) 100 else 88 | val score = if (1 + 1 == 2) 100 else 88 | ||||
when { | when { | ||||
score < AppConstants.TEST_SCORE_LEVEL_1 -> { //小于80 | score < AppConstants.TEST_SCORE_LEVEL_1 -> { //小于80 | ||||
binding.imgIv.setImageResource(R.mipmap.test_score_level_1) | binding.imgIv.setImageResource(R.mipmap.test_score_level_1) | ||||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_2)) | binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_2)) | ||||
} | } | ||||
} | } | ||||
binding.tvScore.run { | binding.tvScore.run { | ||||
visibility = View.VISIBLE | visibility = View.VISIBLE | ||||
text = "${score}分" | text = "${score}分" | ||||
} | } | ||||
binding.tvTip.visibility = View.VISIBLE | binding.tvTip.visibility = View.VISIBLE | ||||
binding.tvTitle.text = "恭喜你,完成了课时学前测试" | binding.tvTitle.text = "恭喜你,完成了课时学前测试" | ||||
binding.tvLessonName.visibility = View.GONE | binding.tvLessonName.visibility = View.GONE | ||||
// binding.tvTip1.visibility = View.VISIBLE | // binding.tvTip1.visibility = View.VISIBLE | ||||
// binding.tvTip1.setText(resources.getString(R.string.test_before_test_over_tip)) | // binding.tvTip1.setText(resources.getString(R.string.test_before_test_over_tip)) | ||||
binding.tvRight.setText(resources.getString(R.string.start_learn)) | binding.tvRight.setText(resources.getString(R.string.start_learn)) | ||||
binding.tvRight.click { | binding.tvRight.click { | ||||
// TODO: 2022/4/2 开始学习 | // TODO: 2022/4/2 开始学习 | ||||
} | } | ||||
} | |||||
interface DialogFragmentListener { | |||||
fun dialogButtonClick(action: DialogEventAction) | |||||
} | } | ||||
} | } |
package com.xkl.cdl.module.learn | package com.xkl.cdl.module.learn | ||||
import android.os.Handler | |||||
import android.os.Looper | |||||
import androidx.lifecycle.LifecycleOwner | |||||
import androidx.lifecycle.MutableLiveData | |||||
import com.suliang.common.base.viewmodel.BaseViewModel | import com.suliang.common.base.viewmodel.BaseViewModel | ||||
import java.util.* | |||||
import kotlin.concurrent.timerTask | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
* create 2022/4/2 14:06 | * create 2022/4/2 14:06 | ||||
* Describe: 学习基类: 实现统一流程接口和计时实现 | |||||
* Describe: 总计时的实现 | |||||
*/ | */ | ||||
abstract class LearnBaseViewModel : BaseViewModel() { | abstract class LearnBaseViewModel : BaseViewModel() { | ||||
//标记是否测试完成 | |||||
var isAllOver = false | |||||
protected var mHandler = Handler(Looper.getMainLooper()) | |||||
//记录总消耗时间 | |||||
var totalUseTime : MutableLiveData<Long> = MutableLiveData(0) | |||||
private var totalTimer : Timer = Timer() | |||||
private var timeTask : TimerTask? = null | |||||
//是否进行计时,默认计时,为false不计时,更改为false的条件时,出现弹窗或者当前页不在前台 | |||||
var countingEnable = true | |||||
/** 开始计时 */ | |||||
fun startTotalCounting(){ | |||||
timeTask = timerTask { | |||||
if (countingEnable) { | |||||
totalUseTime.postValue(totalUseTime.value?.plus(1000)) | |||||
} | |||||
}.apply { | |||||
if (!isAllOver) { | |||||
totalTimer.schedule(this, 1000, 1000) | |||||
} | |||||
} | |||||
} | |||||
/** 停止计时 */ | |||||
fun stopTotalCountTing(){ | |||||
totalTimer.cancel() | |||||
timeTask = null | |||||
} | |||||
} | } |
package com.xkl.cdl.module.learn | package com.xkl.cdl.module.learn | ||||
import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||
import android.app.Activity | |||||
import android.content.Context | import android.content.Context | ||||
import android.content.Intent | import android.content.Intent | ||||
import androidx.appcompat.app.AppCompatActivity | |||||
import android.os.Bundle | import android.os.Bundle | ||||
import android.text.SpannableStringBuilder | |||||
import android.view.View | |||||
import android.widget.TextView | |||||
import androidx.core.content.ContextCompat | |||||
import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
import androidx.viewbinding.ViewBinding | |||||
import com.suliang.common.AppConfig | |||||
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.base.activity.BaseActivityVM | ||||
import com.suliang.common.base.activity.UIBaseActivity | |||||
import com.suliang.common.eventbus.LiveDataBus | |||||
import com.suliang.common.extension.click | import com.suliang.common.extension.click | ||||
import com.suliang.common.util.DateUtil | |||||
import com.suliang.common.util.DrawableUti | |||||
import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
import com.xkl.cdl.adapter.AdapterSpell | |||||
import com.xkl.cdl.adapter.itemdecoration.SpellItemDecoration | |||||
import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
import com.xkl.cdl.data.bean.LearnDialogBean | |||||
import com.xkl.cdl.data.bean.course.ExamBean | |||||
import com.xkl.cdl.data.event.LearnEventData | |||||
import com.xkl.cdl.data.manager.CourseManager | |||||
import com.xkl.cdl.data.manager.UserInfoManager | |||||
import com.xkl.cdl.databinding.* | import com.xkl.cdl.databinding.* | ||||
import com.xkl.cdl.module.m_center_learn.CoursePackMainActivity | |||||
import com.xkl.cdl.dialog.CommonDialog | |||||
import com.xkl.cdl.dialog.CommonDialogBean | |||||
import com.xkl.cdl.dialog.LearnDialog | |||||
/** | /** | ||||
* author suliang | |||||
* create 2022/4/6 19:18 | |||||
* Describe: 测试界面 总测 课时测 | |||||
*/ | |||||
class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding,LearnExamViewModel>() { | |||||
* author suliang | |||||
* create 2022/4/6 19:18 | |||||
* Describe: 测试界面 总测 课时测 | |||||
*/ | |||||
class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamViewModel>() { | |||||
companion object { | companion object { | ||||
@JvmStatic | @JvmStatic | ||||
fun newInstance(context: Context) { | |||||
fun newInstance(context : Context) { | |||||
context.startActivity(Intent(context, LearnExamActivity::class.java)) | context.startActivity(Intent(context, LearnExamActivity::class.java)) | ||||
} | } | ||||
} | } | ||||
//拼写内容binding | //拼写内容binding | ||||
private lateinit var spellBinding : IncExamSpellContentBinding | private lateinit var spellBinding : IncExamSpellContentBinding | ||||
//拼写内容适配器 | |||||
private lateinit var spellAdapter : AdapterSpell | |||||
//选项内容binding | //选项内容binding | ||||
private lateinit var wordChooseBinding : IncExamWordChooseContentBinding | private lateinit var wordChooseBinding : IncExamWordChooseContentBinding | ||||
//当前音频播放的 LottieAnimationView | |||||
private var currentPlayView : LottieAnimationView? = null | |||||
override fun initViewModel() : LearnExamViewModel { | override fun initViewModel() : LearnExamViewModel { | ||||
return ViewModelProvider(this)[LearnExamViewModel::class.java] | return ViewModelProvider(this)[LearnExamViewModel::class.java] | ||||
} | } | ||||
override fun initActivity(savedInstanceState : Bundle?) { | override fun initActivity(savedInstanceState : Bundle?) { | ||||
when(vm.intentData.courseType){ | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { | |||||
spellBinding = IncExamSpellContentBinding.inflate(layoutInflater,binding.containerLayout,true) | |||||
} | |||||
else -> { | |||||
wordChooseBinding = IncExamWordChooseContentBinding.inflate(layoutInflater,binding.containerLayout,true) | |||||
initTitle() | |||||
initStatistics() | |||||
initContent() | |||||
//暂停 继续事件 | |||||
binding.tvErrorState.setOnClickListener { | |||||
with(it as TextView) { | |||||
when (text) { | |||||
resources.getString(R.string.pause) -> { //暂停点击 | |||||
vm.pauseToNext() | |||||
setText(R.string.continue_) | |||||
} | |||||
resources.getString(R.string.continue_) -> { | |||||
vm.continueToNext() | |||||
setText(R.string.pause) | |||||
visibility = View.GONE | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
initTitle() | |||||
} | } | ||||
//标题 | |||||
@SuppressLint("SetTextI18n") | @SuppressLint("SetTextI18n") | ||||
private fun initTitle(){ | |||||
private fun initTitle() { | |||||
binding.incLearnTitle.run { | binding.incLearnTitle.run { | ||||
imgBack.click { onBack() } | |||||
imgBack.click { onBack() } | |||||
//题目 | //题目 | ||||
tvTitle.text = vm.intentData.showTitle | tvTitle.text = vm.intentData.showTitle | ||||
//题目进度 | //题目进度 | ||||
tvNumProgress.text = "${vm.currentIndex+1}/${vm.testData.size}" | |||||
binding.incLearnTitle.tvNumProgress.text = "${vm.currentIndex + 1}/${vm.testData.size}" | |||||
//默认发音 | //默认发音 | ||||
vm.defaultSoundWay = UserInfoManager.getDefaultSoundWay() | |||||
voiceSwitch.setSoundWay(vm.defaultSoundWay) | |||||
voiceSwitch.soundWayChange.observe(this@LearnExamActivity) { | |||||
vm.defaultSoundWay = it | |||||
UserInfoManager.putDefaultSoundWay(it) | |||||
if (this@LearnExamActivity::spellAdapter.isInitialized) { | |||||
spellAdapter.defaultSoundWay = it | |||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
private fun setNumberProgress(){ | |||||
//统计 | |||||
@SuppressLint("SetTextI18n") | |||||
private fun initStatistics() { | |||||
binding.incStatistics.run { | |||||
progressTestNumber.max = vm.testData.size | |||||
tvTestTypeName.text = CourseManager.getExamTypeName(vm.intentData.examType) | |||||
//监听总时间的改变 | |||||
vm.totalUseTime.observe(this@LearnExamActivity) { | |||||
tvTestTime.text = DateUtil.formatGMT(it, DateUtil.FORMAT_2) | |||||
} | |||||
//监听正确错误数的变化 | |||||
vm.correctLiveData.observe(this@LearnExamActivity) { | |||||
tvCorrectNumber.text = "正确: $it" | |||||
} | |||||
vm.errorLiveData.observe(this@LearnExamActivity) { | |||||
tvErrorNumber.text = "错误: $it" | |||||
} | |||||
} | |||||
} | |||||
//内容初始 | |||||
private fun initContent() { | |||||
when (vm.intentData.courseType) { | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { | |||||
spellBinding = IncExamSpellContentBinding.inflate(layoutInflater, binding.containerLayout, true) | |||||
spellBinding.spellRecyclerView.run { | |||||
layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.HORIZONTAL) | |||||
addItemDecoration(SpellItemDecoration()) | |||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false | |||||
spellAdapter = AdapterSpell().apply { | |||||
onItemSpellingClickListener = itemSpellingClick | |||||
defaultSoundWay = vm.defaultSoundWay | |||||
} | |||||
adapter = spellAdapter | |||||
} | |||||
//初始化事件 | |||||
initChooseListener() | |||||
} | |||||
else -> { | |||||
wordChooseBinding = IncExamWordChooseContentBinding.inflate(layoutInflater, binding.containerLayout, true) | |||||
//content init | |||||
vm.optionList.add(wordChooseBinding.incA.apply { | |||||
tvLable.text = "A" | |||||
}) | |||||
vm.optionList.add(wordChooseBinding.incB.apply { | |||||
tvLable.text = "B" | |||||
}) | |||||
vm.optionList.add(wordChooseBinding.incC.apply { | |||||
tvLable.text = "C" | |||||
}) | |||||
vm.optionList.add(wordChooseBinding.incD.apply { | |||||
tvLable.text = "D" | |||||
}) | |||||
//初始化事件 | |||||
initChooseListener() | |||||
//正常、正确、错误 文本和背景颜色 | |||||
vm.normalTextColor = ContextCompat.getColor(this, R.color.main_text_color) | |||||
vm.correctTextColor = ContextCompat.getColor(this, R.color.green_1) | |||||
vm.errorTextColor = ContextCompat.getColor(this, R.color.red_1) | |||||
vm.normalBg = R.drawable.shape_rounder_8_stroke_gray1 | |||||
vm.correctBg = R.drawable.shape_rounder_8_stroke_green1 | |||||
vm.errorBg = R.drawable.shape_rounder_8_stroke_red1 | |||||
vm.correctIv = DrawableUti.changeSvgColor(resources, R.drawable.ic_right, R.color.green_1) | |||||
// DrawableUti.tintDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_right, null)!!, R.color.green_1) | |||||
vm.errorIv = DrawableUti.changeSvgColor(resources, R.drawable.ic_wrong, R.color.red_1) | |||||
// DrawableUti.tintDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_wrong, null)!!, R.color.red_1) | |||||
} | |||||
} | |||||
} | |||||
/** 初始四选一的点击事件 只处理问题和单词的事件,选项事件,每个新的单词开始时设置,答题后再取消设置 | |||||
* 拼写,释义、单词都不发音,只有拼写和拼写完成发音 | |||||
* 作文知识点测试,没有发音 | |||||
* 辨音、口语单词与音频均可发音, | |||||
* 其他单词可以发音 | |||||
* */ | |||||
private fun initChooseListener() { | |||||
when (vm.intentData.courseType) { | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL, AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> { | |||||
} | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_ENGLISH_VOICE -> { | |||||
wordChooseBinding.tvWord.setOnClickListener(ivVoiceClick) | |||||
wordChooseBinding.ivVoice.setOnClickListener(ivVoiceClick) | |||||
} | |||||
else -> { //四选一题目可以发音 | |||||
wordChooseBinding.tvWord.setOnClickListener(ivVoiceClick) | |||||
} | |||||
} | |||||
} | |||||
@SuppressLint("SetTextI18n") | |||||
private fun setNumberProgress() { | |||||
(vm.currentIndex + 1).let { | |||||
binding.incLearnTitle.tvNumProgress.text = "${vm.currentIndex + 1}/${vm.testData.size}" | |||||
binding.incStatistics.progressTestNumber.progress = it | |||||
} | |||||
} | } | ||||
override fun loadData() { | override fun loadData() { | ||||
//初始获取第一条数据 | |||||
vm.loadNext() | |||||
//监听倒计时 | |||||
vm.currentSuplusTime.observe(this) { | |||||
when (vm.intentData.courseType) { | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> spellBinding.progressWordCountdownTime.progress = it | |||||
else -> wordChooseBinding.progressWordCountdownTime.progress = it | |||||
} | |||||
} | |||||
//监听到数据的到来 | |||||
vm.currentExamBean.observe(this) { | |||||
binding.tvErrorState.visibility = View.GONE | |||||
it?.let { | |||||
//更新进度 | |||||
setNumberProgress() | |||||
//更新内容ui | |||||
initContentUI(it) | |||||
//设置初始事件 | |||||
initChooseContentLister(it) | |||||
vm.startCurrentCountingTime() | |||||
} ?: testOver() | |||||
} | |||||
//监听到四选一答案到来 | |||||
vm.currentChooseResultLiveData.observe(this) { | |||||
//取消事件 | |||||
initChooseContentLister(null) | |||||
//未答和正确都更新显示正确的项 错误同时显示正确和错误的项 | |||||
wordChooseBinding.run { | |||||
ivVoice.visibility = View.GONE | |||||
tvWord.visibility = View.VISIBLE | |||||
//显示正确项 | |||||
vm.optionList[vm.currentCorrectPosition].run { | |||||
root.setBackgroundResource(vm.correctBg) | |||||
tvLable.setTextColor(vm.correctTextColor) | |||||
tvOption.setTextColor(vm.correctTextColor) | |||||
ivStatu.visibility = View.VISIBLE | |||||
ivStatu.setImageDrawable(vm.correctIv) | |||||
} | |||||
//显示错误项 | |||||
if (it == AppConstants.TEST_ERROR) { | |||||
vm.optionList[vm.currentClickPosition].run { | |||||
root.setBackgroundResource(vm.errorBg) | |||||
tvLable.setTextColor(vm.errorTextColor) | |||||
tvOption.setTextColor(vm.errorTextColor) | |||||
ivStatu.visibility = View.VISIBLE | |||||
ivStatu.setImageDrawable(vm.errorIv) | |||||
} | |||||
} | |||||
if (it != AppConstants.TEST_CORRECT) { | |||||
binding.tvErrorState.visibility = View.VISIBLE | |||||
} | |||||
//获取下一题 | |||||
vm.loadNext() | |||||
} | |||||
} | |||||
//拼写结果监听 | |||||
vm.currentSpellResultLiveData.observe(this) { | |||||
if (it != AppConstants.TEST_CORRECT) { | |||||
binding.tvErrorState.visibility = View.VISIBLE | |||||
} | |||||
//获取下一题 | |||||
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) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private fun initContentUI(examBean : ExamBean) { | |||||
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) | |||||
} | |||||
} | |||||
private fun initChooseUIData(examBean : ExamBean) { | |||||
wordChooseBinding.tvWord.text = examBean.word | |||||
wordChooseBinding.progressWordCountdownTime.run { | |||||
max = vm.currentMaxTime | |||||
progress = vm.currentMaxTime | |||||
} | |||||
vm.optionList.forEachIndexed { index, optionBinding -> | |||||
optionBinding.run { | |||||
root.setBackgroundResource(vm.normalBg) | |||||
tvLable.setTextColor(vm.normalTextColor) | |||||
tvOption.setTextColor(vm.normalTextColor) | |||||
tvOption.text = vm.currentOption[index] | |||||
ivStatu.visibility = View.GONE | |||||
} | |||||
} | |||||
} | |||||
private fun initVoiceUIData(examBean : ExamBean) { | |||||
wordChooseBinding.tvWord.run { | |||||
text = examBean.word | |||||
visibility = View.GONE | |||||
} | |||||
wordChooseBinding.progressWordCountdownTime.visibility = View.GONE | |||||
wordChooseBinding.ivVoice.visibility = View.VISIBLE | |||||
vm.optionList.forEachIndexed { index, optionBinding -> | |||||
optionBinding.run { | |||||
root.setBackgroundResource(vm.normalBg) | |||||
tvLable.setTextColor(vm.normalTextColor) | |||||
tvOption.setTextColor(vm.normalTextColor) | |||||
tvOption.text = vm.currentOption[index] | |||||
ivStatu.visibility = View.GONE | |||||
} | |||||
} | |||||
} | |||||
private fun initSpokenUIData(examBean : ExamBean) { | |||||
wordChooseBinding.tvWord.text = examBean.word | |||||
if (examBean.questionIsAudio) { | |||||
wordChooseBinding.tvWord.visibility = View.GONE | |||||
wordChooseBinding.ivVoice.visibility = View.VISIBLE | |||||
} else { | |||||
wordChooseBinding.tvWord.visibility = View.VISIBLE | |||||
wordChooseBinding.ivVoice.visibility = View.GONE | |||||
} | |||||
vm.optionList.forEachIndexed { index, optionBinding -> | |||||
optionBinding.run { | |||||
root.setBackgroundResource(vm.normalBg) | |||||
tvLable.setTextColor(vm.normalTextColor) | |||||
tvOption.setTextColor(vm.normalTextColor) | |||||
tvOption.text = vm.currentOption[index] | |||||
ivStatu.visibility = View.GONE | |||||
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 | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private fun initSpellUIData(examBean : ExamBean) { | |||||
spellBinding.tvExplain.text = examBean.correct //释义 | |||||
//初始显示word的时候,文字置空,颜色修改为黑色 | |||||
spellBinding.tvWord.run { | |||||
text = "" | |||||
setTextColor(ContextCompat.getColor(this@LearnExamActivity, R.color.main_text_color)) | |||||
} | |||||
//进度 | |||||
wordChooseBinding.progressWordCountdownTime.run { | |||||
max = vm.currentMaxTime | |||||
progress = vm.currentMaxTime | |||||
} | |||||
//设置数据 | |||||
spellAdapter.setData(vm.currentSpellOption) | |||||
} | } | ||||
/** | |||||
* 拼写时的点击事件 | |||||
* @param selectedValue 选择的值 | |||||
* @param nextPosition 下一个item的位置,拼写未完成时为下一列的位置,拼写完成为错误的第一个位置 | |||||
* @param isOver 拼写是否完成 为true时则判断为拼写完成 | |||||
* @param correctValue 正确的拼写值,但未选中的赋值了颜色 | |||||
* @param errorSize 错误的个数,拼写完成后才会有这个值 | |||||
*/ | |||||
private val itemSpellingClick = | |||||
{ selectedValue : SpannableStringBuilder, nextPosition : Int, isOver : Boolean, correctValue : SpannableStringBuilder, errorSize : Int -> | |||||
when { | |||||
isOver -> { //拼写结束 | |||||
//设置正确的值 | |||||
spellBinding.tvWord.setTextColor(ContextCompat.getColor(this, R.color.red_1)) | |||||
spellBinding.tvWord.text = correctValue | |||||
//拼写结束 | |||||
vm.spellOver(selectedValue.toString(), errorSize) | |||||
} | |||||
else -> { | |||||
//设置为选中的值 | |||||
spellBinding.tvWord.text = selectedValue | |||||
} | |||||
} | |||||
spellBinding.spellRecyclerView.scrollToPosition(nextPosition) | |||||
} | |||||
/** 点击发音事件 */ | |||||
private val ivVoiceClick : (v : View) -> Unit = { | |||||
} | |||||
override fun onBackPressed() { | |||||
onBack() | |||||
} | |||||
/** 返回: 未结束提示弹窗 */ | /** 返回: 未结束提示弹窗 */ | ||||
private fun 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() | |||||
} | |||||
} | |||||
} | |||||
finish() | finish() | ||||
} | } | ||||
/** 测试完成 : 弹窗显示 */ | |||||
private fun testOver() { | |||||
//对话框信息实体 | |||||
val learnDialogBean = LearnDialogBean(AppConstants.DIALOG_TYPE_EXAM).apply { | |||||
examType = vm.intentData.examType | |||||
score = vm.scoreValue | |||||
correctNumber = vm.correctLiveData.value!! | |||||
errorNumber = vm.errorLiveData.value!! | |||||
} | |||||
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() | |||||
} | |||||
} | |||||
} | |||||
}.show(supportFragmentManager, javaClass.name) | |||||
} | |||||
} | } |
package com.xkl.cdl.module.learn | package com.xkl.cdl.module.learn | ||||
import com.suliang.common.base.viewmodel.BaseViewModel | |||||
import android.graphics.drawable.Drawable | |||||
import androidx.lifecycle.LifecycleOwner | |||||
import androidx.lifecycle.MutableLiveData | |||||
import androidx.lifecycle.viewModelScope | |||||
import com.suliang.common.eventbus.LiveDataBus | |||||
import com.suliang.common.extension.createRandomNewChar | |||||
import com.suliang.common.extension.diskIo2Main | |||||
import com.suliang.common.util.DateUtil | |||||
import com.suliang.common.util.LogUtil | |||||
import com.xkl.cdl.data.AppConstants | |||||
import com.xkl.cdl.data.DataTransferHolder | import com.xkl.cdl.data.DataTransferHolder | ||||
import com.xkl.cdl.data.bean.course.DialogueSpokenItem | |||||
import com.xkl.cdl.data.bean.SpellItemBean | |||||
import com.xkl.cdl.data.bean.course.ExamBean | import com.xkl.cdl.data.bean.course.ExamBean | ||||
import com.xkl.cdl.data.bean.intentdata.ExamData | import com.xkl.cdl.data.bean.intentdata.ExamData | ||||
import com.xkl.cdl.data.event.LearnEventData | |||||
import com.xkl.cdl.databinding.IncludTestOptionItemBinding | |||||
import io.reactivex.rxjava3.core.Observable | |||||
import kotlinx.coroutines.delay | |||||
import kotlinx.coroutines.launch | |||||
import mqComsumerV1.Struct.* | |||||
import org.json.JSONArray | |||||
import org.json.JSONException | |||||
import org.json.JSONObject | |||||
class LearnExamViewModel : BaseViewModel() { | |||||
class LearnExamViewModel : LearnBaseViewModel() { | |||||
//默认发音方式 | |||||
var defaultSoundWay : Int = 0 | |||||
//传递过来的测试数据 | //传递过来的测试数据 | ||||
val intentData : ExamData = DataTransferHolder.instance.getData() | val intentData : ExamData = DataTransferHolder.instance.getData() | ||||
//测试题数据 | //测试题数据 | ||||
val testData : List<ExamBean> by lazy { | val testData : List<ExamBean> by lazy { | ||||
intentData.testData!! | intentData.testData!! | ||||
} | } | ||||
//口语测试题 | |||||
val allSentencesList : List<List<DialogueSpokenItem>> by lazy { | |||||
intentData.allSentencesList!! | |||||
} | |||||
//当前测试题下标, 默认为-1,进度设置需要 + 1 ,以数字显示为当前正在做的题数,而非已完成数 | //当前测试题下标, 默认为-1,进度设置需要 + 1 ,以数字显示为当前正在做的题数,而非已完成数 | ||||
var currentIndex = -1 | var currentIndex = -1 | ||||
//错误数 | |||||
val errorLiveData = MutableLiveData<Int>(0) | |||||
//正确数 | |||||
val correctLiveData = MutableLiveData<Int>(0) | |||||
//未答数 | |||||
var unAnswerNumber = 0 | |||||
/** 当前需要显示的题 */ | |||||
val currentExamBean = MutableLiveData<ExamBean?>() | |||||
//获取下一题时的间隔时间,初始为0秒 | |||||
var toNextTime = 0L | |||||
//当前最大进度值 | |||||
var currentMaxTime = 0 | |||||
//当前剩余进度时间 | |||||
var currentSuplusTime = MutableLiveData<Int>(0) | |||||
//单个计时的间隔时间 | |||||
val currentCountingIntervalTime = 20L | |||||
//当前四选一选项 | |||||
var currentOption = mutableListOf<String>() | |||||
//当前四选一正确选项 | |||||
var currentCorrectPosition = 0 | |||||
//当前四选一的点击项 | |||||
var currentClickPosition = -1 | |||||
//当前四选一结果完成 | |||||
val currentChooseResultLiveData = MutableLiveData<Long>() | |||||
//当前拼写选项 | |||||
var currentSpellOption = mutableListOf<SpellItemBean>() | |||||
//四个选项布局view | |||||
val optionList = mutableListOf<IncludTestOptionItemBinding>() | |||||
//当前拼写结果 | |||||
val currentSpellResultLiveData = MutableLiveData<Long>() | |||||
//正常、正确、错误 文本和背景颜色 | |||||
var normalTextColor : Int = 0 | |||||
var correctTextColor : Int = 0 | |||||
var errorTextColor : Int = 0 | |||||
var normalBg : Int = 0 | |||||
var correctBg : Int = 0 | |||||
var errorBg : Int = 0 | |||||
lateinit var correctIv : Drawable | |||||
lateinit var errorIv : Drawable | |||||
var scoreValue = 0 //分数/词汇数 | |||||
var vocabularyCoverange = 0 //词汇量测试的覆盖率 | |||||
//测试结果试卷 | |||||
private var learnExam : LearnExam.Builder? = null | |||||
//当前测试的单词 | |||||
private var currentExamRecord : ExamRecord.Builder? = null | |||||
//最终测试题集合,用于上传 | |||||
private val mExamRecordList : MutableList<ExamRecord> = mutableListOf() | |||||
//错误集合 | |||||
private var mLearnEntities : MutableList<LearnEntity> = mutableListOf() | |||||
//学前总测试与章节前测试时新的学习错误集合 | |||||
var newErrorMap : HashMap<String, Boolean> = hashMapOf() | |||||
//标记是否获取下一题 | |||||
var isGetNextIng = false | |||||
//标记是否为当前错误,暂停获取下一题 | |||||
var isErrorPauseToNext = false | |||||
//标记当前是否倒计时中 | |||||
var isCurrentCounting = false | |||||
//标记当前是否是否返回后台 | |||||
private var isInBackground = false | |||||
//标记是否显示返回弹窗 : 停止计时 | |||||
private var isShowBackDialog = false | |||||
/** 获取下一题数据 */ | |||||
fun loadNext() { | |||||
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){ | |||||
isShowBackDialog = isShow | |||||
when{ | |||||
isShow -> { //显示返回弹窗 | |||||
//关闭总计时 | |||||
stopTotalCountTing() | |||||
//停止单题的倒计时 | |||||
mHandler.removeCallbacks(currentCountingTimeRunable) | |||||
} | |||||
else -> { //关闭返回弹窗 | |||||
//开启总计时 | |||||
startTotalCounting() | |||||
when{ | |||||
!isErrorPauseToNext ->{ // 暂停标志,不处理单题,非暂停 | |||||
when { | |||||
isGetNextIng -> { //是否获取下一题中 | |||||
//本来该获取下一题,现在获取下一题 | |||||
continueToNext() | |||||
} | |||||
else -> { //不是,进行当前题的计时 | |||||
mHandler.post(currentCountingTimeRunable) //现在恢复单题的计时 | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
override fun onResume(owner : LifecycleOwner) { | |||||
super.onResume(owner) | |||||
when{ | |||||
!isShowBackDialog -> { //没有显示返回弹窗,判断是否完成测试,没有完成,则进行计时,需要注意是否第一次进入,避免对第一题进行重复计时 | |||||
} | |||||
} | |||||
} | |||||
override fun onPause(owner : LifecycleOwner) { | |||||
super.onPause(owner) | |||||
//关闭所有计时,如果弹窗显示,就只需要关闭总计时就可以了,否则关闭所有计时 | |||||
} | |||||
// /** | |||||
// * 暂停或恢复计时 | |||||
// * @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) //现在恢复单题的计时 | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
/** 获取下一题的线程,同时判断是否完成 */ | |||||
private val toNextRunable = object : Runnable { | |||||
override fun run() { | |||||
isGetNextIng = false | |||||
currentIndex++ | |||||
if (currentIndex >= testData.size) { | |||||
// TODO: 2022/4/11 标记学习已完成 停止计时 计算分数 设置总消费时间 | |||||
if (isAllOver) return | |||||
isAllOver = true | |||||
calculateScore() | |||||
//封装数据 | |||||
packUpRecord() | |||||
//保存数据 后再使用 currentExamBean.value = null 都在saveData中 | |||||
saveData() | |||||
} else { | |||||
val nextExam = testData[currentIndex] | |||||
when (intentData.courseType) { | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { | |||||
initSpellOption(nextExam) | |||||
currentMaxTime = | |||||
Math.max(nextExam.word.length * 2 * AppConstants.SPELL_TEST_MIN_TIME, AppConstants.TEST_MIN_TIME) | |||||
currentSuplusTime.value = currentMaxTime | |||||
} | |||||
else -> { | |||||
initChooseOption(nextExam) | |||||
currentMaxTime = if (intentData.courseType == AppConstants.COURSE_TYPE_ENGLISH_VOICE) 0 else Math.max( | |||||
AppConstants.TEST_MIN_TIME, | |||||
(nextExam.word.length + nextExam.correct.length) * 200) | |||||
LogUtil.e("------currentMaxTime = $currentMaxTime") | |||||
currentSuplusTime.value = currentMaxTime | |||||
} | |||||
} | |||||
currentExamBean.value = nextExam | |||||
} | |||||
} | |||||
} | |||||
/** 初始化四选一选选项 */ | |||||
private fun initChooseOption(examBean : ExamBean) { | |||||
currentOption.clear() | |||||
currentOption.add(examBean.correct) | |||||
currentOption.add(examBean.error1) | |||||
currentOption.add(examBean.error2) | |||||
currentOption.add(examBean.error3) | |||||
currentCorrectPosition = 0 | |||||
for (i in 4 downTo 1) { | |||||
val currentPosition = i - 1 | |||||
val swapPosition = (0 until i).random() | |||||
if (currentPosition != swapPosition) { | |||||
currentOption[currentPosition] = currentOption.set(swapPosition, currentOption[currentPosition]) | |||||
if (currentCorrectPosition == swapPosition) { | |||||
currentCorrectPosition = currentPosition | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** 初始化拼写选项集合 */ | |||||
private fun initSpellOption(examBean : ExamBean) { | |||||
currentSpellOption.clear() | |||||
examBean.word.forEachIndexed { index, c -> | |||||
val newChar = c.createRandomNewChar() | |||||
if ((0 .. 1).random() == 0) { | |||||
currentSpellOption.add(SpellItemBean(c, true)) | |||||
currentSpellOption.add(SpellItemBean(newChar, false)) | |||||
} else { | |||||
currentSpellOption.add(SpellItemBean(newChar, false)) | |||||
currentSpellOption.add(SpellItemBean(c, true)) | |||||
} | |||||
} | |||||
} | |||||
/** 当前单词的计时 */ | |||||
private val currentCountingTimeRunable = object : Runnable { | |||||
override fun run() { | |||||
when (intentData.courseType) { | |||||
AppConstants.COURSE_TYPE_ENGLISH_VOICE, AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { // 累加时间 | |||||
currentMaxTime += currentCountingIntervalTime.toInt() | |||||
mHandler.postDelayed(this, currentCountingIntervalTime) | |||||
} | |||||
else -> { //倒计时 | |||||
val surplusTime = currentSuplusTime.value!! - currentCountingIntervalTime | |||||
currentSuplusTime.value = surplusTime.toInt() | |||||
if (surplusTime > 0) { | |||||
mHandler.postDelayed(this, currentCountingIntervalTime) | |||||
} else { | |||||
chooseResult(-1) //-1为未答 | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** 当前单词开始计时 */ | |||||
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) | |||||
//创建单题的测试记录 | |||||
currentExamRecord = ExamRecord.newBuilder().apply { | |||||
questionId = currentExamBean.value!!.word_id | |||||
questionType = currentExamBean.value!!.type | |||||
userAnswer = when (position) { //答案选项 | |||||
0 -> "A" | |||||
1 -> "B" | |||||
2 -> "C" | |||||
3 -> "D" | |||||
else -> "" | |||||
} | |||||
duration = when (intentData.courseType) { | |||||
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_ENGLISH_VOICE -> currentMaxTime.toLong() | |||||
else -> currentMaxTime.toLong() - currentSuplusTime.value!! | |||||
} | |||||
} | |||||
//问题和选项 | |||||
createQuestion() | |||||
currentClickPosition = position | |||||
var chooseResult = -1L | |||||
//添加到记录集合 | |||||
mExamRecordList.add(currentExamRecord!!.build()) | |||||
//进行结果判断,并为记录赋值 | |||||
if (position == currentCorrectPosition) { //正确 | |||||
correctLiveData.value = correctLiveData.value!!.plus(1) | |||||
currentExamRecord!!.answerStatus = AppConstants.TEST_CORRECT | |||||
chooseResult = AppConstants.TEST_CORRECT | |||||
toNextTime = AppConstants.TEST_TO_NEXT_CORRECT_TIME | |||||
} else { | |||||
errorLiveData.value = errorLiveData.value!!.plus(1) //错误 | |||||
currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR | |||||
chooseResult = AppConstants.TEST_ERROR | |||||
toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME | |||||
//未答 | |||||
if (position == -1) { | |||||
unAnswerNumber += 1 | |||||
currentExamRecord!!.answerStatus = AppConstants.TEST_UN_ANSWER | |||||
chooseResult = AppConstants.TEST_UN_ANSWER | |||||
} | |||||
//创建错误记录 | |||||
createErrorRecord() | |||||
} | |||||
currentChooseResultLiveData.value = chooseResult | |||||
} | |||||
/** 拼写结束 | |||||
* @param selectedValue 用户拼写的值,如果为空,则为未答 | |||||
* @param errorSize 错误数 | |||||
* */ | |||||
fun spellOver(selectedValue : String, errorSize : Int) { | |||||
//移除计时 | |||||
mHandler.removeCallbacks(currentCountingTimeRunable) | |||||
//创建测试记录 | |||||
currentExamRecord = ExamRecord.newBuilder().apply { | |||||
questionId = currentExamBean.value!!.word_id | |||||
questionType = currentExamBean.value!!.type | |||||
userAnswer = selectedValue | |||||
duration = currentMaxTime.toLong() - currentSuplusTime.value!! | |||||
} | |||||
createQuestionSpell() | |||||
val result : Long | |||||
when { | |||||
selectedValue.isEmpty() -> { //未答 | |||||
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 | |||||
} | |||||
errorSize > 0 -> { //错误 | |||||
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 | |||||
} | |||||
else -> { //正确 | |||||
correctLiveData.value = correctLiveData.value!!.plus(1) | |||||
currentExamRecord!!.answerStatus = AppConstants.TEST_CORRECT | |||||
toNextTime = AppConstants.TEST_CORRECT | |||||
result = AppConstants.TEST_CORRECT | |||||
} | |||||
} | |||||
//通知结果 | |||||
currentSpellResultLiveData.value = result | |||||
} | |||||
/** 创建错误记录 并将错误记录加入错误集合 | |||||
* 学前、学前总的数据记录进入学习的课时中,口语学前总不需要记录错误 | |||||
* */ | |||||
fun createErrorRecord() { | |||||
if (intentData.examType == AppConstants.TEST_TYPE_BEFORE || (intentData.examType == AppConstants.TEST_TYPE_BEFORE_TOTAL && intentData.courseType != AppConstants.COURSE_TYPE_ENGLISH_SPOKEN)) { | |||||
val builder = LearnEntity.newBuilder().apply { | |||||
projectId = intentData.subjectId.toLong() | |||||
packId = intentData.coursePackId | |||||
courseId = intentData.courseId | |||||
currentExamBean.value!!.let { | |||||
chapterId = it.chapterId | |||||
lessonId = it.lessonId | |||||
entityId = it.word_id | |||||
tag = "android" | |||||
isFromExam = true | |||||
isError = true | |||||
created = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1) | |||||
lastReviewDate = created | |||||
} | |||||
} | |||||
//这个错误单词是否为新的错误或者是就的错,根据key判断错误集合中是否有此单词 | |||||
val key = "${builder.chapterId}_${builder.lessonId}_${builder.entityId}" | |||||
if (intentData.mExamWRMap!!.contains(key)) { | |||||
//错误再错误,则恢复复习此数为1 | |||||
builder.reviewNum = 1 | |||||
} else { //相当于新错 | |||||
if (!newErrorMap.containsKey(key)) { | |||||
newErrorMap[key] = true | |||||
//课时前错误 ,则更新课时错误数量 | |||||
if (intentData.examType == AppConstants.TEST_TYPE_BEFORE) { | |||||
intentData.lesson!!.errorNumber = intentData.lesson!!.errorNumber + 1 | |||||
} | |||||
} | |||||
} | |||||
mLearnEntities.add(builder.build()) | |||||
} | |||||
} | |||||
/** | |||||
* 计算测试分数 | |||||
*/ | |||||
private fun calculateScore() { | |||||
when (intentData.examType) { | |||||
//词汇量测试 | |||||
AppConstants.TEST_TYPE_NORMAL -> { | |||||
val calculateCorrectNumber : Double = correctLiveData.value!! + unAnswerNumber * 0.25 | |||||
//测试是否成功,大于35题才算成功 | |||||
val isVocablularySuccess = calculateCorrectNumber > 35 | |||||
scoreValue = 0 //词汇数 | |||||
vocabularyCoverange = 0 //覆盖率 | |||||
//分数 与 覆盖率 只有测试成功才进行计算 | |||||
if (isVocablularySuccess) { | |||||
val converange = (calculateCorrectNumber - 25) / 75 //被测试题的占比重 | |||||
// TODO: 2022/4/14 16000应该为该词汇量测试对应的词汇数 | |||||
scoreValue = (16000 * converange).toInt() | |||||
vocabularyCoverange = (converange * 100).toInt() //测试类型的覆盖率 | |||||
} | |||||
} | |||||
//其他测试 | |||||
else -> { | |||||
val calculateCorrectNumber : Double = correctLiveData.value!! + unAnswerNumber * 0.25 | |||||
val scoreValue = ((calculateCorrectNumber - testData.size * 0.25) / (testData.size * 0.25) * 100).toInt() | |||||
this.scoreValue = when { | |||||
scoreValue < 0 -> 0 | |||||
scoreValue > 100 -> 100 | |||||
else -> scoreValue | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//创建四选一的问题和选项 | |||||
fun createQuestion() { | |||||
try { | |||||
val jsonObject = JSONObject() | |||||
jsonObject.put("question_describe", currentExamBean.value!!.word) | |||||
val array = JSONArray() | |||||
val a = JSONObject() | |||||
a.put("text", currentOption[0]) | |||||
a.put("isCorrect", currentCorrectPosition == 0) | |||||
val b = JSONObject() | |||||
b.put("text", currentOption[1]) | |||||
b.put("isCorrect", currentCorrectPosition == 1) | |||||
val c = JSONObject() | |||||
c.put("text", currentOption[2]) | |||||
c.put("isCorrect", currentCorrectPosition == 2) | |||||
val d = JSONObject() | |||||
d.put("text", currentOption[3]) | |||||
d.put("isCorrect", currentCorrectPosition == 3) | |||||
array.put(a) | |||||
array.put(b) | |||||
array.put(c) | |||||
array.put(d) | |||||
jsonObject.put("sequence", array) | |||||
currentExamRecord!!.question = jsonObject.toString() | |||||
} catch (e : JSONException) { | |||||
e.printStackTrace() | |||||
} | |||||
} | |||||
//创建当前问题的Question | |||||
fun createQuestionSpell() { | |||||
try { | |||||
val jsonObject = JSONObject() | |||||
jsonObject.put("question_describe", currentExamBean.value!!.correct) | |||||
val array = JSONArray() | |||||
val a = JSONObject() | |||||
a.put("text", currentExamBean.value!!.word) | |||||
a.put("isCorrect", false) | |||||
array.put(a) | |||||
jsonObject.put("sequence", array) | |||||
currentExamRecord!!.question = jsonObject.toString() | |||||
} catch (e : JSONException) { | |||||
e.printStackTrace() | |||||
} | |||||
} | |||||
/** 封装试卷 */ | |||||
private fun packUpRecord() : Record.Builder { | |||||
val examType = intentData.examType | |||||
//生成试卷 | |||||
learnExam = LearnExam.newBuilder().apply { | |||||
projectId = intentData.subjectId.toLong() | |||||
packId = intentData.coursePackId | |||||
courseId = intentData.courseId | |||||
chapterId = intentData.lesson?.chapterId ?: 0 //章节id,章节测试必传 | |||||
lessonId = intentData.lesson?.lessonId ?: 0 //课时id | |||||
if (examType == AppConstants.TEST_TYPE_COMPOSITION) { //作文类型,作文必传 | |||||
typeId = intentData.courseType.toLong() | |||||
} | |||||
title = intentData.saveTitle //测试试卷名称 | |||||
type = examType.toLong() //测试类型 | |||||
score = scoreValue.toFloat() //成绩(如果为词汇量测试则为词汇数) | |||||
totalNum = testData.size.toLong() //题目总数 | |||||
correctNum = correctLiveData.value!!.toLong() //正确题目数 | |||||
errorNum = errorLiveData.value!!.toLong() //错误题目数 | |||||
unanswerNum = unAnswerNumber.toLong() //未答题目数 | |||||
duration = totalUseTime.value!! //测试所用时间(单位为毫秒) | |||||
tag = "android" | |||||
created = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1) //数据创建时间 | |||||
//courseType | |||||
val ext = "{\"courseType\": ${intentData.courseType} }" | |||||
//词汇量测试: 覆盖率、阶段 | |||||
if (examType == AppConstants.TEST_TYPE_NORMAL) { | |||||
// TODO: 2022/4/14 设置词汇量测试的范围题目和覆盖率 | |||||
stage = "小学" | |||||
coverRate = vocabularyCoverange.toFloat() | |||||
} | |||||
//添加测试题 | |||||
addAllRecord(mExamRecordList) | |||||
} | |||||
// //是否完成 | |||||
// if (examType == SyncStateContract.Constants.TEST_TYPE_AFTER_TOTAL && mData.getScore() >= SyncStateContract.Constants.SCORE_1 && !mData.isCourseFinished()) { | |||||
// learnExam.setIsFinished(true) | |||||
// } | |||||
//最终上传数据 | |||||
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) { | |||||
record.addDuration(saveCurrentLearnDuration()) | |||||
} | |||||
if (mLearnEntities.size != 0) { | |||||
record.addAllEntity(mLearnEntities) | |||||
} | |||||
return record | |||||
} | |||||
/** | |||||
* 保存当前的学习时长 | |||||
*/ | |||||
private fun saveCurrentLearnDuration() : LearnDuration.Builder { | |||||
return LearnDuration.newBuilder().apply { | |||||
projectId = intentData.subjectId.toLong() | |||||
packId = intentData.coursePackId | |||||
courseId = intentData.courseId | |||||
created = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1) | |||||
tag = "android" | |||||
timeFrame = DateUtil.getTimeFrame(System.currentTimeMillis()) //设置时段 | |||||
isReview = false //是否复习时长 | |||||
duration = totalUseTime.value!! //时长,毫秒 | |||||
totalDuration = totalUseTime.value!! //总时间,包含空闲部分 | |||||
categoryId = intentData.courseType.toLong() | |||||
} | |||||
} | |||||
/** 保存处理数据 */ | |||||
private fun saveData() { | |||||
showHideLoading(true) | |||||
Observable.create<Boolean> { | |||||
viewModelScope.launch { | |||||
delay(2000) | |||||
it.onNext(true) | |||||
} | |||||
// TODO: 2022/4/14 传递保存record信息 | |||||
}.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 | |||||
} | |||||
currentExamBean.value = null | |||||
}, { | |||||
showHideLoading(false) | |||||
it.printStackTrace() | |||||
}) | |||||
} | |||||
} | } |
import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
import com.suliang.common.AppConfig | import com.suliang.common.AppConfig | ||||
import com.suliang.common.base.fragment.BaseFragmentVM | import com.suliang.common.base.fragment.BaseFragmentVM | ||||
import com.suliang.common.eventbus.LiveDataBus | |||||
import com.suliang.common.extension.replaceFragment | import com.suliang.common.extension.replaceFragment | ||||
import com.suliang.common.util.LogUtil | import com.suliang.common.util.LogUtil | ||||
import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
import com.xkl.cdl.data.DataTransferHolder | import com.xkl.cdl.data.DataTransferHolder | ||||
import com.xkl.cdl.data.bean.intentdata.ExamData | import com.xkl.cdl.data.bean.intentdata.ExamData | ||||
import com.xkl.cdl.data.event.LearnEventData | |||||
import com.xkl.cdl.data.manager.db.DbControlBase | import com.xkl.cdl.data.manager.db.DbControlBase | ||||
import com.xkl.cdl.databinding.FragmentCourseMainBinding | import com.xkl.cdl.databinding.FragmentCourseMainBinding | ||||
import com.xkl.cdl.module.learn.LearnExamActivity | import com.xkl.cdl.module.learn.LearnExamActivity | ||||
* 用于加载该课程的多种状态: 复习、章节目录、总测试 | * 用于加载该课程的多种状态: 复习、章节目录、总测试 | ||||
*/ | */ | ||||
class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainFragmentViewModel>() { | class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainFragmentViewModel>() { | ||||
companion object { | companion object { | ||||
@JvmStatic | @JvmStatic | ||||
fun newInstance(courseIndex:Int) = CourseMainFragment().apply { | |||||
fun newInstance(courseIndex : Int) = CourseMainFragment().apply { | |||||
arguments = Bundle().apply { | arguments = Bundle().apply { | ||||
putInt(AppConfig.INTENT_1,courseIndex) | |||||
putInt(AppConfig.INTENT_1, courseIndex) | |||||
} | } | ||||
} | } | ||||
} | } | ||||
override fun initViewModel(): CourseMainFragmentViewModel { | |||||
return ViewModelProvider(this,ViewModelFactory(requireArguments().getInt(AppConfig.INTENT_1)))[CourseMainFragmentViewModel::class.java].apply { | |||||
override fun initViewModel() : CourseMainFragmentViewModel { | |||||
return ViewModelProvider(this, | |||||
ViewModelFactory(requireArguments().getInt(AppConfig.INTENT_1)))[CourseMainFragmentViewModel::class.java].apply { | |||||
coursePackMainActivityVM = ViewModelProvider(requireActivity())[CoursePackMainActivityViewModel::class.java] | coursePackMainActivityVM = ViewModelProvider(requireActivity())[CoursePackMainActivityViewModel::class.java] | ||||
LogUtil.e("CourseMainFragment coursePackMainActivityVM hashCode -> ${coursePackMainActivityVM.hashCode()}") | LogUtil.e("CourseMainFragment coursePackMainActivityVM hashCode -> ${coursePackMainActivityVM.hashCode()}") | ||||
} | } | ||||
} | } | ||||
override fun initFragment() { | override fun initFragment() { | ||||
//设置课程 和 需要操作的类型 | //设置课程 和 需要操作的类型 | ||||
vm.course = vm.coursePackMainActivityVM.coursePack.childrenCourses[vm.courseIndex].apply { | vm.course = vm.coursePackMainActivityVM.coursePack.childrenCourses[vm.courseIndex].apply { | ||||
vm.dbControlBase = DbControlBase(subjectId,coursePackId,courseId,courseType) | |||||
vm.dbControlBase = DbControlBase(subjectId, coursePackId, courseId, courseType) | |||||
} | } | ||||
//监听动作 总测完成,切换到目录页 | |||||
LiveDataBus.with<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 -> { | |||||
vm.courseDetail.st_before = it.scoreValue.toDouble() //学前总分 | |||||
it.newErrorMap?.run { | |||||
when { | |||||
this.size > 0 -> { | |||||
//新错误加入错误列表集合 | |||||
vm.courseDetail.exam_w_r_list.putAll(this) | |||||
// 如果有新错误则需更新列表数据 | |||||
vm.modifyLessonErrorNumber() | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
override fun loadData() { | override fun loadData() { | ||||
LogUtil.e("CourseMainFragment 加载数据 --》 ${vm.courseIndex}") | LogUtil.e("CourseMainFragment 加载数据 --》 ${vm.courseIndex}") | ||||
vm.loadMain().observe(this){ | |||||
vm.loadMain().observe(this) { | |||||
changeChildrenFragment() | changeChildrenFragment() | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* 改变加载的子Fragment | * 改变加载的子Fragment | ||||
*/ | */ | ||||
private fun changeChildrenFragment(){ | |||||
private fun changeChildrenFragment() { | |||||
// //加载复习 | // //加载复习 | ||||
// replaceFragment(R.id.layout_root, CourseReviewFragment.newInstance()) | // replaceFragment(R.id.layout_root, CourseReviewFragment.newInstance()) | ||||
//学后总测 | //学后总测 | ||||
// replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) | // replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) | ||||
//学习目录 | //学习目录 | ||||
vm.courseDetail.run { | vm.courseDetail.run { | ||||
if (st_before == AppConstants.NOT_DOING) { //学前总测未做 | if (st_before == AppConstants.NOT_DOING) { //学前总测未做 | ||||
replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_BEFORE_TOTAL)) | replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_BEFORE_TOTAL)) | ||||
* 供其他地方手动调用切换界面 | * 供其他地方手动调用切换界面 | ||||
* @param position Int 1目录页 2学后总测 | * @param position Int 1目录页 2学后总测 | ||||
*/ | */ | ||||
fun changeFragment(position: Int){ | |||||
when(position){ | |||||
fun changeFragment(position : Int) { | |||||
when (position) { | |||||
1 -> replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance()) | 1 -> replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance()) | ||||
2 -> replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) | |||||
2 -> replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) | |||||
} | } | ||||
} | } | ||||
/** 跳转测试 */ | /** 跳转测试 */ | ||||
fun startExam(examData : ExamData){ | |||||
fun startExam(examData : ExamData) { | |||||
DataTransferHolder.instance.putData(value = examData) | DataTransferHolder.instance.putData(value = examData) | ||||
LearnExamActivity.newInstance(requireContext()) | LearnExamActivity.newInstance(requireContext()) | ||||
} | } | ||||
inner class ViewModelFactory(private val courseIndex: Int) : ViewModelProvider.Factory{ | |||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T { | |||||
inner class ViewModelFactory(private val courseIndex : Int) : ViewModelProvider.Factory { | |||||
override fun <T : ViewModel?> create(modelClass : Class<T>) : T { | |||||
return CourseMainFragmentViewModel(courseIndex) as T | return CourseMainFragmentViewModel(courseIndex) as T | ||||
} | } | ||||
} | } | ||||
} | } |
import io.reactivex.rxjava3.core.Observer | import io.reactivex.rxjava3.core.Observer | ||||
import io.reactivex.rxjava3.disposables.Disposable | import io.reactivex.rxjava3.disposables.Disposable | ||||
import java.util.* | import java.util.* | ||||
import kotlin.collections.HashMap | |||||
/** | /** | ||||
* author suliang | * author suliang | ||||
return result | return result | ||||
} | } | ||||
/** | |||||
* 更新列表数据错误数,学前总测结束才进行修改 | |||||
*/ | |||||
fun modifyLessonErrorNumber() { | |||||
//重设detail中的错误数, exam_w_r_list为所有的测试前错误,因为时学前总测,所以,可以根据这个字段来计算每个课时的错误数 | |||||
//记录错误数: key :chapterId_lessonId value :错误数 | |||||
val errorNumber = hashMapOf<String,Int>() | |||||
courseDetail.exam_w_r_list.keys.forEach { | |||||
val split = it.split("_") | |||||
if (split.size == 3){ | |||||
val errorKey = "${split[0]}_${split[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}) | |||||
} | |||||
} | |||||
} | } |
package com.xkl.cdl.module.m_center_learn.coursechildren | package com.xkl.cdl.module.m_center_learn.coursechildren | ||||
import android.os.Bundle | |||||
import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
import com.suliang.common.AppConfig | |||||
import com.suliang.common.base.fragment.BaseFragmentVM | import com.suliang.common.base.fragment.BaseFragmentVM | ||||
import com.suliang.common.extension.click | import com.suliang.common.extension.click | ||||
import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
import com.xkl.cdl.databinding.FragmentCourseLessonBinding | |||||
import com.xkl.cdl.databinding.FragmentCourseReviewBinding | import com.xkl.cdl.databinding.FragmentCourseReviewBinding | ||||
/** | /** |
import com.suliang.common.extension.setHtml | import com.suliang.common.extension.setHtml | ||||
import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
import com.xkl.cdl.data.DataTransferHolder | |||||
import com.xkl.cdl.data.bean.course.ExamBean | import com.xkl.cdl.data.bean.course.ExamBean | ||||
import com.xkl.cdl.data.bean.intentdata.ExamData | import com.xkl.cdl.data.bean.intentdata.ExamData | ||||
import com.xkl.cdl.data.manager.CourseManager | import com.xkl.cdl.data.manager.CourseManager | ||||
import com.xkl.cdl.databinding.FragmentCourseTotalTestBinding | import com.xkl.cdl.databinding.FragmentCourseTotalTestBinding | ||||
import com.xkl.cdl.module.learn.LearnExamActivity | |||||
/** | /** | ||||
* 课程总测试: 学前总,学后总 | * 课程总测试: 学前总,学后总 | ||||
private fun startTest(view: View) { | private fun startTest(view: View) { | ||||
//生成数据 | //生成数据 | ||||
val examData = ExamData(vm.course.subjectId, totalTestType, vm.course.courseTitle, vm.course.courseTitle).apply { | val examData = ExamData(vm.course.subjectId, totalTestType, vm.course.courseTitle, vm.course.courseTitle).apply { | ||||
coursePackId = vm.course.coursePackId | |||||
coursePackType = vm.course.coursePackType | coursePackType = vm.course.coursePackType | ||||
courseId = vm.course.courseId | |||||
courseType = vm.course.courseType | courseType = vm.course.courseType | ||||
this.testData = this@CourseTotalTestFragment.testData | this.testData = this@CourseTotalTestFragment.testData | ||||
mExamWRMap = if (examType == AppConstants.TEST_TYPE_AFTER_TOTAL) vm.courseDetail.exam_w_r_list else null | |||||
mExamWRMap = if (examType == AppConstants.TEST_TYPE_BEFORE_TOTAL) vm.courseDetail.exam_w_r_list else null | |||||
} | } | ||||
(parentFragment as CourseMainFragment).startExam(examData) | (parentFragment as CourseMainFragment).startExam(examData) | ||||
} | } |
// show(supportFragmentManager,javaClass.name) | // show(supportFragmentManager,javaClass.name) | ||||
// } | // } | ||||
// LogUtil.e("Dialog -- > ${learnDialog.hashCode()}") | // LogUtil.e("Dialog -- > ${learnDialog.hashCode()}") | ||||
SpUtils.instance.encode("my","abcdefgxxxxx") | |||||
SpUtils.instance.remove("my") | |||||
println(SpUtils.instance.decode("my",String::class.java)) | |||||
if (true) return | |||||
// SpUtils.instance.encode("my","abcdefgxxxxx") | |||||
// SpUtils.instance.remove("my") | |||||
// println("---------------" + SpUtils.instance.decode("my",String::class.java)) | |||||
// | |||||
// if (true) return | |||||
showHideLoading(true) | showHideLoading(true) |
<?xml version="1.0" encoding="utf-8"?> | |||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
<stroke android:color="@color/green_1" android:width="1dp"/> | |||||
<corners android:radius="8dp"/> | |||||
</shape> |
<?xml version="1.0" encoding="utf-8"?> | |||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
<stroke android:color="@color/red_1" android:width="1dp"/> | |||||
<corners android:radius="8dp"/> | |||||
</shape> |
app:layout_constraintLeft_toLeftOf="parent" | app:layout_constraintLeft_toLeftOf="parent" | ||||
android:layout_marginBottom="40dp" | android:layout_marginBottom="40dp" | ||||
app:cornerRadius="8dp" | app:cornerRadius="8dp" | ||||
android:backgroundTint="@color/gray_4" /> | |||||
android:backgroundTint="@color/gray_4" | |||||
android:visibility="gone"/> | |||||
</androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> | ||||
</layout> | </layout> |
<?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" | |||||
android:layout_width="300dp" | |||||
android:layout_height="wrap_content" | |||||
android:background="@drawable/shape_rounder_12_white" | |||||
xmlns:tools="http://schemas.android.com/tools"> | |||||
<ImageView | |||||
android:id="@+id/img" | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
android:layout_marginTop="24dp" | |||||
app:layout_constraintLeft_toLeftOf="parent" | |||||
app:layout_constraintRight_toRightOf="parent" | |||||
app:layout_constraintTop_toTopOf="parent" | |||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
<TextView | |||||
android:id="@+id/tv_title" | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
android:layout_marginTop="24dp" | |||||
android:paddingLeft="16dp" | |||||
android:paddingRight="16dp" | |||||
tools:text="您确认要删除该标签吗?" | |||||
android:textColor="#323233" | |||||
android:textSize="16dp" | |||||
app:layout_constraintLeft_toLeftOf="parent" | |||||
app:layout_constraintRight_toRightOf="parent" | |||||
app:layout_constraintTop_toBottomOf="@+id/img" | |||||
android:visibility="gone" | |||||
tools:visibility="visible" | |||||
/> | |||||
<TextView | |||||
android:id="@+id/tv_content" | |||||
tools:text="删除后将不可再恢复标签中的内容" | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
app:layout_constraintTop_toBottomOf="@+id/tv_title" | |||||
android:layout_marginTop="10dp" | |||||
app:layout_goneMarginTop="24dp" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
android:textColor="#8A8A99" | |||||
android:textSize="14dp" | |||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
<View | |||||
android:id="@+id/v_space" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="1dp" | |||||
android:layout_marginTop="24dp" | |||||
android:background="#E6E6E6" | |||||
app:layout_constraintLeft_toLeftOf="parent" | |||||
app:layout_constraintRight_toRightOf="parent" | |||||
app:layout_constraintTop_toBottomOf="@+id/tv_content" | |||||
app:layout_goneMarginTop="24dp" /> | |||||
<TextView | |||||
android:id="@+id/tv_right" | |||||
android:layout_width="0dp" | |||||
android:layout_height="40dp" | |||||
android:gravity="center" | |||||
android:text="确定" | |||||
android:textColor="@color/theme_color" | |||||
android:textSize="16dp" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintTop_toBottomOf="@+id/v_space" | |||||
app:layout_constraintStart_toEndOf="@id/vSplit" | |||||
/> | |||||
<View | |||||
android:id="@+id/vSplit" | |||||
android:layout_width="@dimen/line_height" | |||||
android:layout_height="0dp" | |||||
app:layout_constraintEnd_toStartOf="@+id/tv_right" | |||||
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"/> | |||||
<!--左边按钮--> | |||||
<TextView | |||||
android:id="@+id/tv_left" | |||||
android:layout_width="0dp" | |||||
android:layout_height="40dp" | |||||
android:gravity="center" | |||||
android:text="取消" | |||||
android:textColor="@color/main_text_color" | |||||
android:textSize="16dp" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
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> |
app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_tip" | app:layout_constraintTop_toBottomOf="@+id/tv_tip" | ||||
tools:text="课时学前测试" /> | |||||
tools:text="课时学前测试" | |||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
<TextView | <TextView | ||||
android:id="@+id/tv_lesson_name" | android:id="@+id/tv_lesson_name" | ||||
app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_title" | app:layout_constraintTop_toBottomOf="@+id/tv_title" | ||||
tools:text="[%s%s]"/> | |||||
tools:text="[%s%s]" | |||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
<TextView | <TextView | ||||
android:id="@+id/tv_count_time" | android:id="@+id/tv_count_time" | ||||
app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_lesson_name" | app:layout_constraintTop_toBottomOf="@+id/tv_lesson_name" | ||||
tools:text="@string/test_count_time_format"/> | |||||
tools:text="@string/test_count_time_format" | |||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
<TextView | <TextView | ||||
android:id="@+id/tv_tip_1" | android:id="@+id/tv_tip_1" | ||||
app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_tip_1" | app:layout_constraintTop_toBottomOf="@+id/tv_tip_1" | ||||
android:visibility="gone"/> | |||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
app:layout_constraintStart_toEndOf="@+id/tv_left" | app:layout_constraintStart_toEndOf="@+id/tv_left" | ||||
android:background="@color/gray_3" | android:background="@color/gray_3" | ||||
app:layout_constraintTop_toTopOf="@+id/tv_right" | app:layout_constraintTop_toTopOf="@+id/tv_right" | ||||
app:layout_constraintBottom_toBottomOf="@+id/tv_right" /> | |||||
app:layout_constraintBottom_toBottomOf="@+id/tv_right" | |||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
<!--左边按钮--> | <!--左边按钮--> | ||||
<TextView | <TextView | ||||
app:layout_constraintTop_toTopOf="@+id/vSplit" | app:layout_constraintTop_toTopOf="@+id/vSplit" | ||||
app:layout_constraintBottom_toBottomOf="@+id/vSplit" | app:layout_constraintBottom_toBottomOf="@+id/vSplit" | ||||
android:visibility="gone" | android:visibility="gone" | ||||
tools:visibility="visible" | |||||
/> | |||||
<!--学前总测试控制显示的布局id--> | |||||
<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" | |||||
/> | /> | ||||
</androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> | ||||
/> | /> | ||||
</androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> | ||||
</layout> | </layout> |
style="@android:style/Widget.ProgressBar.Horizontal" | style="@android:style/Widget.ProgressBar.Horizontal" | ||||
android:layout_width="match_parent" | android:layout_width="match_parent" | ||||
android:layout_height="4dp" | android:layout_height="4dp" | ||||
android:layout_marginLeft="88dp" | |||||
android:layout_marginStart="88dp" | |||||
android:layout_marginTop="16dp" | android:layout_marginTop="16dp" | ||||
android:layout_marginRight="87dp" | |||||
android:layout_marginEnd="88dp" | |||||
android:progressDrawable="@drawable/progressbar_countdown_time" | android:progressDrawable="@drawable/progressbar_countdown_time" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_explain" | app:layout_constraintTop_toBottomOf="@+id/tv_explain" | ||||
tools:progress="20" /> | tools:progress="20" /> |
app:lottie_repeatMode="restart" | app:lottie_repeatMode="restart" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
app:layout_constraintTop_toTopOf="parent" /> | |||||
app:layout_constraintTop_toTopOf="parent" | |||||
android:visibility="gone"/> | |||||
<androidx.appcompat.widget.AppCompatTextView | <androidx.appcompat.widget.AppCompatTextView | ||||
android:id="@+id/tv_word" | android:id="@+id/tv_word" | ||||
android:layout_marginTop="10dp" | android:layout_marginTop="10dp" | ||||
android:gravity="center" | android:gravity="center" | ||||
tools:text="gradual" | tools:text="gradual" | ||||
tools:textColor="@color/num0" | |||||
android:textColor="@color/num0" | |||||
android:textSize="@dimen/size_max_word" | android:textSize="@dimen/size_max_word" | ||||
app:autoSizeMaxTextSize="@dimen/size_max_word" | app:autoSizeMaxTextSize="@dimen/size_max_word" | ||||
app:autoSizeMinTextSize="@dimen/miniSize" | app:autoSizeMinTextSize="@dimen/miniSize" | ||||
app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
app:layout_constraintEnd_toEndOf="parent" /> | app:layout_constraintEnd_toEndOf="parent" /> | ||||
<androidx.constraintlayout.widget.Barrier | |||||
android:id="@+id/barrier" | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
app:barrierDirection="bottom" | |||||
app:constraint_referenced_ids="tv_word,iv_voice"/> | |||||
<!--单词测试时间--> | |||||
<ProgressBar | |||||
android:id="@+id/progress_word_countdown_time" | |||||
style="@android:style/Widget.ProgressBar.Horizontal" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="4dp" | |||||
android:layout_marginStart="88dp" | |||||
android:layout_marginTop="16dp" | |||||
android:layout_marginEnd="88dp" | |||||
android:progressDrawable="@drawable/progressbar_countdown_time" | |||||
app:layout_constraintTop_toBottomOf="@+id/barrier" | |||||
tools:progress="20"/> | |||||
<TextView | <TextView | ||||
android:id="@+id/tv_rule_tip" | android:id="@+id/tv_rule_tip" | ||||
android:layout_width="wrap_content" | android:layout_width="wrap_content" | ||||
android:textSize="@dimen/smallerSize" | android:textSize="@dimen/smallerSize" | ||||
app:layout_constraintLeft_toLeftOf="parent" | app:layout_constraintLeft_toLeftOf="parent" | ||||
app:layout_constraintRight_toRightOf="parent" | app:layout_constraintRight_toRightOf="parent" | ||||
app:layout_constraintTop_toBottomOf="@+id/tv_word" /> | |||||
app:layout_constraintTop_toBottomOf="@+id/progress_word_countdown_time" /> | |||||
<include | <include | ||||
android:id="@+id/inc_a" | android:id="@+id/inc_a" |
android:layout_marginRight="@dimen/global_spacing" | android:layout_marginRight="@dimen/global_spacing" | ||||
app:layout_constraintBottom_toBottomOf="parent" | app:layout_constraintBottom_toBottomOf="parent" | ||||
android:singleLine="true" | android:singleLine="true" | ||||
android:maxEms="6" | |||||
android:ellipsize="end" | android:ellipsize="end" | ||||
/> | /> | ||||
app:layout_constraintLeft_toRightOf="@+id/tv_test_type_name" | app:layout_constraintLeft_toRightOf="@+id/tv_test_type_name" | ||||
app:layout_constraintTop_toBottomOf="@+id/progress_test_number" | app:layout_constraintTop_toBottomOf="@+id/progress_test_number" | ||||
app:layout_constraintBottom_toBottomOf="parent" | app:layout_constraintBottom_toBottomOf="parent" | ||||
android:layout_marginStart="@dimen/global_spacing"/> | |||||
android:layout_marginStart="@dimen/global_spacing" /> | |||||
</androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> |
android:layout_width="wrap_content" | android:layout_width="wrap_content" | ||||
android:layout_height="wrap_content" | android:layout_height="wrap_content" | ||||
app:constraint_referenced_ids="layout_choose_area,layout_lottie_voice_area,guide_line" | app:constraint_referenced_ids="layout_choose_area,layout_lottie_voice_area,guide_line" | ||||
android:visibility="gone"/> | |||||
android:visibility="gone" | |||||
tools:visibility="visible"/> | |||||
<TextView | <TextView | ||||
app:layout_constraintRight_toLeftOf="@id/iv_statu" | app:layout_constraintRight_toLeftOf="@id/iv_statu" | ||||
app:layout_constraintTop_toTopOf="parent" | app:layout_constraintTop_toTopOf="parent" | ||||
app:layout_goneMarginRight="36dp" | app:layout_goneMarginRight="36dp" | ||||
tools:text="喜好;爱好;类似的人(或物);(尤指被视为和某人或某事物一样好的)种类,类型 adj.类似的;相似的 adv.大概,可能;" /> | |||||
tools:text="喜好" /> | |||||
<ImageView | <ImageView | ||||
android:id="@+id/iv_statu" | android:id="@+id/iv_statu" | ||||
app:layout_constraintRight_toRightOf="parent" | app:layout_constraintRight_toRightOf="parent" | ||||
app:layout_constraintTop_toTopOf="parent" | app:layout_constraintTop_toTopOf="parent" | ||||
tools:src="@drawable/ic_right" | tools:src="@drawable/ic_right" | ||||
app:tint="@color/green_1"/> | |||||
/> | |||||
</androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> |
<?xml version="1.0" encoding="utf-8"?> | |||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"> | |||||
<data> | |||||
</data> | |||||
<TextView | |||||
android:id="@+id/tv" | |||||
android:layout_width="44dp" | |||||
android:layout_height="44dp" | |||||
android:textSize="26dp" | |||||
android:gravity="center" | |||||
android:textColor="@color/main_text_color" | |||||
android:background="@color/white"/> | |||||
</layout> |
<string name="start_learn">开始学习</string> | <string name="start_learn">开始学习</string> | ||||
<string name="test_spell_tip">请从左至右依次点击选择正确的选项</string> | <string name="test_spell_tip">请从左至右依次点击选择正确的选项</string> | ||||
<string name="pause">暂停</string> | <string name="pause">暂停</string> | ||||
<string name="continue_">继续</string> | |||||
<string name="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> | |||||
</resources> | </resources> |
//gson https://github.com/google/gson | //gson https://github.com/google/gson | ||||
Gson : "com.google.code.gson:gson:2.9.0", | Gson : "com.google.code.gson:gson:2.9.0", | ||||
//MMKV https://github.com/Tencent/MMKV/wiki/android_tutorial_cn | //MMKV https://github.com/Tencent/MMKV/wiki/android_tutorial_cn | ||||
MMKV : "com.tencent:mmkv:1.2.13" | |||||
MMKV : "com.tencent:mmkv:1.2.13", | |||||
//protobuf | |||||
protobuf_java: "com.google.protobuf:protobuf-java:3.11.0", | |||||
protobuf_java_format: "com.googlecode.protobuf-java-format:protobuf-java-format:1.2", | |||||
//grpc | |||||
annotation_api: "javax.annotation:javax.annotation-api:1.3.2", | |||||
grpc_okhttp: "io.grpc:grpc-okhttp:1.27.0", | |||||
grpc_android: "io.grpc:grpc-android:1.27.0", | |||||
grpc_protobuf: "io.grpc:grpc-protobuf:1.27.0", | |||||
grpc_stub: "io.grpc:grpc-stub:1.27.0", | |||||
] | ] | ||||
package com.suliang.common.base.adapter | |||||
import androidx.lifecycle.ViewModel | |||||
abstract class BaseAdapterVM<T,VM:ViewModel>(val vm:VM) : BaseAdapter<T>() { | |||||
} |
import androidx.viewbinding.ViewBinding | import androidx.viewbinding.ViewBinding | ||||
import com.suliang.common.R | import com.suliang.common.R | ||||
abstract class BaseAdapter<T> : | |||||
abstract class BaseRVAdapter<T> : | |||||
RecyclerView.Adapter<BaseAdapterViewHolder>() { | RecyclerView.Adapter<BaseAdapterViewHolder>() { | ||||
companion object{ | companion object{ | ||||
fun getItem(position: Int) : T { | fun getItem(position: Int) : T { | ||||
return mData.get(position) | return mData.get(position) | ||||
} | } | ||||
fun getData(): MutableList<T>{ | |||||
return mData | |||||
} | |||||
/** | /** | ||||
* @param data List<T>? | * @param data List<T>? | ||||
*/ | */ | ||||
@SuppressLint("NotifyDataSetChanged") | @SuppressLint("NotifyDataSetChanged") | ||||
fun setData(data: MutableList<T>?) { | |||||
open fun setData(data: MutableList<T>?) { | |||||
mData.clear() | mData.clear() | ||||
data?.let { it -> //不为空 | data?.let { it -> //不为空 | ||||
mData.addAll(data) | mData.addAll(data) |