@@ -1,5 +1,29 @@ | |||
<component name="ProjectCodeStyleConfiguration"> | |||
<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> | |||
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" /> | |||
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" /> | |||
@@ -9,7 +33,7 @@ | |||
<JetCodeStyleSettings> | |||
<option name="SPACE_AROUND_RANGE" 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" /> | |||
</JetCodeStyleSettings> | |||
<DBN-PSQL> | |||
@@ -156,6 +180,10 @@ | |||
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> | |||
<option name="ALIGN_MULTILINE_EXTENDS_LIST" 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="ENUM_CONSTANTS_WRAP" value="2" /> | |||
<option name="WRAP_ON_TYPING" value="0" /> |
@@ -45,6 +45,7 @@ | |||
<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_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_lesson_learn.xml" value="0.4144927536231884" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_lesson.xml" value="0.33" /> | |||
@@ -67,6 +68,7 @@ | |||
<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_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_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" /> |
@@ -69,7 +69,7 @@ android { | |||
} | |||
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 project(path: ':lib:common') | |||
// implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' | |||
@@ -80,14 +80,14 @@ dependencies { | |||
// implementation 'androidx.navigation:navigation-fragment-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 | |||
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 | |||
implementation customDependencies.SmartRefreshLayout | |||
implementation 'io.github.scwang90:refresh-header-classics:2.0.5' //经典刷新头 | |||
implementation 'io.github.scwang90:refresh-header-classics:2.0.5' //经典刷新头 | |||
//SqlCipher | |||
implementation customDependencies.SqlCipher | |||
//androidx-sqlite | |||
@@ -98,5 +98,14 @@ dependencies { | |||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | |||
//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 | |||
} |
@@ -2,8 +2,7 @@ package com.xkl.cdl.adapter | |||
import android.view.ViewGroup | |||
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.xkl.cdl.R | |||
import com.xkl.cdl.databinding.DialogItemSelectRepeatBinding | |||
@@ -13,7 +12,7 @@ import com.xkl.cdl.databinding.DialogItemSelectRepeatBinding | |||
* create 2020/6/29 13:44 | |||
* Describe: 自动播放弹窗列表选择适配器 | |||
*/ | |||
class AdapterAutoPlaySelectRepeat : BaseAdapter<Void?>() { | |||
class AdapterAutoPlaySelectRepeat : BaseRVAdapter<Void?>() { | |||
override fun coverEmptyViewHolder(parent: ViewGroup): BaseAdapterViewHolder { | |||
return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_empty)) | |||
} |
@@ -2,7 +2,7 @@ package com.xkl.cdl.adapter | |||
import android.view.View | |||
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.databinding.ItemEmptyBinding | |||
import com.suliang.common.extension.click | |||
@@ -18,7 +18,7 @@ import com.xkl.cdl.module.m_center_learn.CoursePackFragmentViewModel | |||
* Describe: 学习中心课程包适配器 | |||
*/ | |||
class AdapterCoursePackWithLearCenter(vm: CoursePackFragmentViewModel) : | |||
BaseAdapterVM<CoursePack, CoursePackFragmentViewModel>(vm) { | |||
BaseRVAdapterVM<CoursePack, CoursePackFragmentViewModel>(vm) { | |||
override fun onBindEmptyViewHolder(holder: BaseAdapterViewHolder) { | |||
(holder.binding as ItemEmptyBinding).apply { |
@@ -4,7 +4,7 @@ import android.content.res.ColorStateList | |||
import android.view.View | |||
import android.view.ViewGroup | |||
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.extension.click | |||
import com.xkl.cdl.R | |||
@@ -19,7 +19,7 @@ import com.xkl.cdl.module.m_center_learn.coursechildren.CourseMainFragmentViewMo | |||
* create 2022/3/29 16:43 | |||
* Describe: 课程课时列表适配器 | |||
*/ | |||
class AdapterLesson(vm: CourseMainFragmentViewModel) : BaseAdapterVM<Lesson, CourseMainFragmentViewModel>(vm) { | |||
class AdapterLesson(vm: CourseMainFragmentViewModel) : BaseRVAdapterVM<Lesson, CourseMainFragmentViewModel>(vm) { | |||
/** 选中item的位置 */ | |||
var selectPos = -1 | |||
@@ -65,16 +65,15 @@ class AdapterLesson(vm: CourseMainFragmentViewModel) : BaseAdapterVM<Lesson, Cou | |||
//事件: 将章节名称的点击事件取消了 | |||
//事件 | |||
layoutContent.click { | |||
//选中项非当前项,则需要改变选中颜色,直接通知更新,调用对应位置的notfy | |||
if (selectPos != position) { | |||
notifyItemChanged(selectPos) | |||
val temp = selectPos | |||
selectPos = position | |||
notifyItemChanged(temp) | |||
notifyItemChanged(selectPos) | |||
} | |||
notifyItemChanged(position) | |||
notifyItemChanged(position, null) | |||
onItemClick.invoke(it, position, lesson) | |||
} | |||
} |
@@ -0,0 +1,256 @@ | |||
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) | |||
} | |||
} | |||
} |
@@ -0,0 +1,80 @@ | |||
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) | |||
} | |||
} |
@@ -99,7 +99,7 @@ object AppConstants { | |||
/**学后总测试*/ | |||
const val TEST_TYPE_AFTER_TOTAL = 5 | |||
/**备忘录测试*/ | |||
/**备忘本测试*/ | |||
const val TEST_TYPE_MEMO = 6 | |||
/**作文知识点测试*/ | |||
@@ -115,19 +115,14 @@ object AppConstants { | |||
const val TEST_SCORE_LEVEL_1 = 80 | |||
/** 测试通过 >= 太棒了 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 | |||
/**章节测试*/ | |||
@@ -141,6 +136,34 @@ object AppConstants { | |||
const val SOUND_TYPE_UK = 2 //英 | |||
/**中文*/ | |||
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 | |||
} |
@@ -0,0 +1,79 @@ | |||
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) | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
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) |
@@ -13,7 +13,7 @@ class ExamBean { | |||
var error1: String = "" //错误选项1 | |||
var error2: String = "" //错误选项2 | |||
var error3 : String = "" //错误选项3 | |||
var type = 0 //测试题类型 | |||
var type = 0L //测试题类型 | |||
var chapterId : Long = 0 //章节id | |||
var lessonId : Long = 0 //课时id |
@@ -21,19 +21,15 @@ data class ExamData( | |||
val showTitle : String, //去测试本地显示名称,学前学后 为课程名称,课时前、后为课时名称 | |||
val saveTitle : String, //测试试卷上传名称 : 学习中心:课程名称 + “ ” + 学习类型 课程测试:课程名称 + “ ” 学习类型 + (章节拼接名称) 词汇量测试:词汇量测试:阶段名称 | |||
) { | |||
var coursePackId : Long = 0 //课程包id, 测试错误上次数据需要 | |||
var coursePackType:Int = 0 //课程包类型 | |||
var courseId : Long = 0 //课程id | |||
var courseType : Int= 0 //课程类型 | |||
var testData : List<ExamBean>? = null //测试题数据 | |||
var lesson : Lesson? = null //课时测试时必传 | |||
// 课程/课时学前测试正确错误列表 key=> {chapter_id}_{lesson_id}_{entity_id} value=>正确 true;错误 false | |||
var mExamWRMap : HashMap<String, Boolean>? = null | |||
//学前总测试与章节前测试时学习错误集合 | |||
var newErrorMap : HashMap<String, Boolean> = hashMapOf() | |||
//口语对话测试数据: 仅在口语对话课时后测试中使用该课时的学习数据 | |||
var allSentencesList : List<List<DialogueSpokenItem>>? = null | |||
} |
@@ -0,0 +1,19 @@ | |||
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 //新错误单词列表 | |||
} |
@@ -150,4 +150,23 @@ object CourseManager { | |||
} | |||
} | |||
} | |||
/** | |||
* 获取测试类型对应的名称 | |||
* @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 -> "" | |||
} | |||
} | |||
} |
@@ -1,5 +1,8 @@ | |||
package com.xkl.cdl.data.manager | |||
import com.suliang.common.util.SpUtils | |||
import com.xkl.cdl.data.AppConstants | |||
/** | |||
* author suliang | |||
* create 2022/4/8 17:47 | |||
@@ -7,6 +10,13 @@ package com.xkl.cdl.data.manager | |||
*/ | |||
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) | |||
} | |||
} |
@@ -0,0 +1,94 @@ | |||
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 | |||
} | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
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) | |||
} | |||
} | |||
} |
@@ -1,13 +1,17 @@ | |||
package com.xkl.cdl.dialog | |||
import android.os.Bundle | |||
import android.view.Gravity | |||
import android.view.View | |||
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.extension.click | |||
import com.suliang.common.util.LogUtil | |||
import com.suliang.common.util.os.ScreenUtil | |||
import com.xkl.cdl.R | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.data.bean.LearnDialogBean | |||
import com.xkl.cdl.databinding.DialogLessonLearnBinding | |||
/** | |||
@@ -15,24 +19,104 @@ import com.xkl.cdl.databinding.DialogLessonLearnBinding | |||
* create 2022/4/2 15:47 | |||
* 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() { | |||
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() { | |||
binding.tvTitle.text = "课时学前测试" | |||
binding.tvLessonName.text = "章节课时名称" | |||
@@ -40,18 +124,18 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
binding.tvRight.text = "开始测试" | |||
binding.tvLeft.visibility = View.VISIBLE | |||
binding.tvLeft.text = "随便设置" | |||
binding.tvRight.click { | |||
LogUtil.e("Dialog -- > show()") | |||
} | |||
binding.tvLeft.click { | |||
LogUtil.e("Dialog -- > hide()") | |||
} | |||
} | |||
fun initLessonBeforeTestOver() { | |||
binding.tvScore.run { | |||
visibility = View.VISIBLE | |||
@@ -69,13 +153,13 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
binding.tvRight.click { | |||
// TODO: 2022/4/2 开始学习 | |||
} | |||
} | |||
fun initLessonAfterTestOver() { | |||
//未通过、通过和通过已通过,均为一致,只是显示的状态不同 | |||
val score = if (1 + 1 == 2) 100 else 88 | |||
when { | |||
score < AppConstants.TEST_SCORE_LEVEL_1 -> { //小于80 | |||
binding.imgIv.setImageResource(R.mipmap.test_score_level_1) | |||
@@ -90,12 +174,12 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_2)) | |||
} | |||
} | |||
binding.tvScore.run { | |||
visibility = View.VISIBLE | |||
text = "${score}分" | |||
} | |||
binding.tvTip.visibility = View.VISIBLE | |||
binding.tvTitle.text = "恭喜你,完成了课时学前测试" | |||
binding.tvLessonName.visibility = View.GONE | |||
@@ -105,19 +189,14 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
// binding.tvTip1.visibility = View.VISIBLE | |||
// binding.tvTip1.setText(resources.getString(R.string.test_before_test_over_tip)) | |||
binding.tvRight.setText(resources.getString(R.string.start_learn)) | |||
binding.tvRight.click { | |||
// TODO: 2022/4/2 开始学习 | |||
} | |||
} | |||
interface DialogFragmentListener { | |||
fun dialogButtonClick(action: DialogEventAction) | |||
} | |||
} |
@@ -1,12 +1,50 @@ | |||
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 java.util.* | |||
import kotlin.concurrent.timerTask | |||
/** | |||
* author suliang | |||
* create 2022/4/2 14:06 | |||
* Describe: 学习基类: 实现统一流程接口和计时实现 | |||
* Describe: 总计时的实现 | |||
*/ | |||
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 | |||
} | |||
} |
@@ -1,86 +1,504 @@ | |||
package com.xkl.cdl.module.learn | |||
import android.annotation.SuppressLint | |||
import android.app.Activity | |||
import android.content.Context | |||
import android.content.Intent | |||
import androidx.appcompat.app.AppCompatActivity | |||
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.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.UIBaseActivity | |||
import com.suliang.common.eventbus.LiveDataBus | |||
import com.suliang.common.extension.click | |||
import com.suliang.common.util.DateUtil | |||
import com.suliang.common.util.DrawableUti | |||
import com.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.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.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 { | |||
@JvmStatic | |||
fun newInstance(context: Context) { | |||
fun newInstance(context : Context) { | |||
context.startActivity(Intent(context, LearnExamActivity::class.java)) | |||
} | |||
} | |||
//拼写内容binding | |||
private lateinit var spellBinding : IncExamSpellContentBinding | |||
//拼写内容适配器 | |||
private lateinit var spellAdapter : AdapterSpell | |||
//选项内容binding | |||
private lateinit var wordChooseBinding : IncExamWordChooseContentBinding | |||
//当前音频播放的 LottieAnimationView | |||
private var currentPlayView : LottieAnimationView? = null | |||
override fun initViewModel() : LearnExamViewModel { | |||
return ViewModelProvider(this)[LearnExamViewModel::class.java] | |||
} | |||
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") | |||
private fun initTitle(){ | |||
private fun initTitle() { | |||
binding.incLearnTitle.run { | |||
imgBack.click { onBack() } | |||
imgBack.click { onBack() } | |||
//题目 | |||
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() { | |||
//初始获取第一条数据 | |||
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() | |||
} | |||
/** 测试完成 : 弹窗显示 */ | |||
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) | |||
} | |||
} |
@@ -1,24 +1,647 @@ | |||
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.bean.course.DialogueSpokenItem | |||
import com.xkl.cdl.data.bean.SpellItemBean | |||
import com.xkl.cdl.data.bean.course.ExamBean | |||
import com.xkl.cdl.data.bean.intentdata.ExamData | |||
import com.xkl.cdl.data.event.LearnEventData | |||
import com.xkl.cdl.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 testData : List<ExamBean> by lazy { | |||
intentData.testData!! | |||
} | |||
//口语测试题 | |||
val allSentencesList : List<List<DialogueSpokenItem>> by lazy { | |||
intentData.allSentencesList!! | |||
} | |||
//当前测试题下标, 默认为-1,进度设置需要 + 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() | |||
}) | |||
} | |||
} |
@@ -5,12 +5,14 @@ import androidx.lifecycle.ViewModel | |||
import androidx.lifecycle.ViewModelProvider | |||
import com.suliang.common.AppConfig | |||
import com.suliang.common.base.fragment.BaseFragmentVM | |||
import com.suliang.common.eventbus.LiveDataBus | |||
import com.suliang.common.extension.replaceFragment | |||
import com.suliang.common.util.LogUtil | |||
import com.xkl.cdl.R | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.data.DataTransferHolder | |||
import com.xkl.cdl.data.bean.intentdata.ExamData | |||
import com.xkl.cdl.data.event.LearnEventData | |||
import com.xkl.cdl.data.manager.db.DbControlBase | |||
import com.xkl.cdl.databinding.FragmentCourseMainBinding | |||
import com.xkl.cdl.module.learn.LearnExamActivity | |||
@@ -22,44 +24,76 @@ import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | |||
* 用于加载该课程的多种状态: 复习、章节目录、总测试 | |||
*/ | |||
class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainFragmentViewModel>() { | |||
companion object { | |||
@JvmStatic | |||
fun newInstance(courseIndex:Int) = CourseMainFragment().apply { | |||
fun newInstance(courseIndex : Int) = CourseMainFragment().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] | |||
LogUtil.e("CourseMainFragment coursePackMainActivityVM hashCode -> ${coursePackMainActivityVM.hashCode()}") | |||
} | |||
} | |||
override fun initFragment() { | |||
//设置课程 和 需要操作的类型 | |||
vm.course = vm.coursePackMainActivityVM.coursePack.childrenCourses[vm.courseIndex].apply { | |||
vm.dbControlBase = DbControlBase(subjectId,coursePackId,courseId,courseType) | |||
vm.dbControlBase = DbControlBase(subjectId, coursePackId, 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() { | |||
LogUtil.e("CourseMainFragment 加载数据 --》 ${vm.courseIndex}") | |||
vm.loadMain().observe(this){ | |||
vm.loadMain().observe(this) { | |||
changeChildrenFragment() | |||
} | |||
} | |||
/** | |||
* 改变加载的子Fragment | |||
*/ | |||
private fun changeChildrenFragment(){ | |||
private fun changeChildrenFragment() { | |||
// //加载复习 | |||
// replaceFragment(R.id.layout_root, CourseReviewFragment.newInstance()) | |||
@@ -68,7 +102,7 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
//学后总测 | |||
// replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) | |||
//学习目录 | |||
vm.courseDetail.run { | |||
if (st_before == AppConstants.NOT_DOING) { //学前总测未做 | |||
replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_BEFORE_TOTAL)) | |||
@@ -84,27 +118,25 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
* 供其他地方手动调用切换界面 | |||
* @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()) | |||
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) | |||
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 | |||
} | |||
} | |||
} |
@@ -17,6 +17,7 @@ import io.reactivex.rxjava3.core.Observable | |||
import io.reactivex.rxjava3.core.Observer | |||
import io.reactivex.rxjava3.disposables.Disposable | |||
import java.util.* | |||
import kotlin.collections.HashMap | |||
/** | |||
* author suliang | |||
@@ -108,5 +109,27 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
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}) | |||
} | |||
} | |||
} |
@@ -1,12 +1,9 @@ | |||
package com.xkl.cdl.module.m_center_learn.coursechildren | |||
import android.os.Bundle | |||
import androidx.lifecycle.ViewModelProvider | |||
import com.suliang.common.AppConfig | |||
import com.suliang.common.base.fragment.BaseFragmentVM | |||
import com.suliang.common.extension.click | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.databinding.FragmentCourseLessonBinding | |||
import com.xkl.cdl.databinding.FragmentCourseReviewBinding | |||
/** |
@@ -9,12 +9,10 @@ import com.suliang.common.extension.click | |||
import com.suliang.common.extension.setHtml | |||
import com.xkl.cdl.R | |||
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.intentdata.ExamData | |||
import com.xkl.cdl.data.manager.CourseManager | |||
import com.xkl.cdl.databinding.FragmentCourseTotalTestBinding | |||
import com.xkl.cdl.module.learn.LearnExamActivity | |||
/** | |||
* 课程总测试: 学前总,学后总 | |||
@@ -155,10 +153,12 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
private fun startTest(view: View) { | |||
//生成数据 | |||
val examData = ExamData(vm.course.subjectId, totalTestType, vm.course.courseTitle, vm.course.courseTitle).apply { | |||
coursePackId = vm.course.coursePackId | |||
coursePackType = vm.course.coursePackType | |||
courseId = vm.course.courseId | |||
courseType = vm.course.courseType | |||
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) | |||
} |
@@ -50,11 +50,11 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(){ | |||
// show(supportFragmentManager,javaClass.name) | |||
// } | |||
// 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) |
@@ -0,0 +1,5 @@ | |||
<?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> |
@@ -0,0 +1,5 @@ | |||
<?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> |
@@ -57,7 +57,8 @@ | |||
app:layout_constraintLeft_toLeftOf="parent" | |||
android:layout_marginBottom="40dp" | |||
app:cornerRadius="8dp" | |||
android:backgroundTint="@color/gray_4" /> | |||
android:backgroundTint="@color/gray_4" | |||
android:visibility="gone"/> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
</layout> |
@@ -0,0 +1,106 @@ | |||
<?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> |
@@ -74,7 +74,9 @@ | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_tip" | |||
tools:text="课时学前测试" /> | |||
tools:text="课时学前测试" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
<TextView | |||
android:id="@+id/tv_lesson_name" | |||
@@ -89,7 +91,9 @@ | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_title" | |||
tools:text="[%s%s]"/> | |||
tools:text="[%s%s]" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
<TextView | |||
android:id="@+id/tv_count_time" | |||
@@ -101,7 +105,9 @@ | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="parent" | |||
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 | |||
android:id="@+id/tv_tip_1" | |||
@@ -125,7 +131,8 @@ | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_tip_1" | |||
android:visibility="gone"/> | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
@@ -168,7 +175,9 @@ | |||
app:layout_constraintStart_toEndOf="@+id/tv_left" | |||
android:background="@color/gray_3" | |||
app:layout_constraintTop_toTopOf="@+id/tv_right" | |||
app:layout_constraintBottom_toBottomOf="@+id/tv_right" /> | |||
app:layout_constraintBottom_toBottomOf="@+id/tv_right" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
<!--左边按钮--> | |||
<TextView | |||
@@ -184,6 +193,15 @@ | |||
app:layout_constraintTop_toTopOf="@+id/vSplit" | |||
app:layout_constraintBottom_toBottomOf="@+id/vSplit" | |||
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> | |||
@@ -200,5 +218,7 @@ | |||
/> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
</layout> |
@@ -33,9 +33,9 @@ | |||
style="@android:style/Widget.ProgressBar.Horizontal" | |||
android:layout_width="match_parent" | |||
android:layout_height="4dp" | |||
android:layout_marginLeft="88dp" | |||
android:layout_marginStart="88dp" | |||
android:layout_marginTop="16dp" | |||
android:layout_marginRight="87dp" | |||
android:layout_marginEnd="88dp" | |||
android:progressDrawable="@drawable/progressbar_countdown_time" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_explain" | |||
tools:progress="20" /> |
@@ -23,7 +23,8 @@ | |||
app:lottie_repeatMode="restart" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintTop_toTopOf="parent" /> | |||
app:layout_constraintTop_toTopOf="parent" | |||
android:visibility="gone"/> | |||
<androidx.appcompat.widget.AppCompatTextView | |||
android:id="@+id/tv_word" | |||
@@ -32,7 +33,7 @@ | |||
android:layout_marginTop="10dp" | |||
android:gravity="center" | |||
tools:text="gradual" | |||
tools:textColor="@color/num0" | |||
android:textColor="@color/num0" | |||
android:textSize="@dimen/size_max_word" | |||
app:autoSizeMaxTextSize="@dimen/size_max_word" | |||
app:autoSizeMinTextSize="@dimen/miniSize" | |||
@@ -42,6 +43,26 @@ | |||
app:layout_constraintStart_toStartOf="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 | |||
android:id="@+id/tv_rule_tip" | |||
android:layout_width="wrap_content" | |||
@@ -52,7 +73,7 @@ | |||
android:textSize="@dimen/smallerSize" | |||
app:layout_constraintLeft_toLeftOf="parent" | |||
app:layout_constraintRight_toRightOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_word" /> | |||
app:layout_constraintTop_toBottomOf="@+id/progress_word_countdown_time" /> | |||
<include | |||
android:id="@+id/inc_a" |
@@ -71,7 +71,6 @@ | |||
android:layout_marginRight="@dimen/global_spacing" | |||
app:layout_constraintBottom_toBottomOf="parent" | |||
android:singleLine="true" | |||
android:maxEms="6" | |||
android:ellipsize="end" | |||
/> | |||
@@ -87,7 +86,7 @@ | |||
app:layout_constraintLeft_toRightOf="@+id/tv_test_type_name" | |||
app:layout_constraintTop_toBottomOf="@+id/progress_test_number" | |||
app:layout_constraintBottom_toBottomOf="parent" | |||
android:layout_marginStart="@dimen/global_spacing"/> | |||
android:layout_marginStart="@dimen/global_spacing" /> | |||
</androidx.constraintlayout.widget.ConstraintLayout> |
@@ -80,7 +80,8 @@ | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
app:constraint_referenced_ids="layout_choose_area,layout_lottie_voice_area,guide_line" | |||
android:visibility="gone"/> | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
<TextView | |||
@@ -110,7 +111,7 @@ | |||
app:layout_constraintRight_toLeftOf="@id/iv_statu" | |||
app:layout_constraintTop_toTopOf="parent" | |||
app:layout_goneMarginRight="36dp" | |||
tools:text="喜好;爱好;类似的人(或物);(尤指被视为和某人或某事物一样好的)种类,类型 adj.类似的;相似的 adv.大概,可能;" /> | |||
tools:text="喜好" /> | |||
<ImageView | |||
android:id="@+id/iv_statu" | |||
@@ -120,7 +121,7 @@ | |||
app:layout_constraintRight_toRightOf="parent" | |||
app:layout_constraintTop_toTopOf="parent" | |||
tools:src="@drawable/ic_right" | |||
app:tint="@color/green_1"/> | |||
/> | |||
</androidx.constraintlayout.widget.ConstraintLayout> |
@@ -0,0 +1,16 @@ | |||
<?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> |
@@ -58,6 +58,12 @@ | |||
<string name="start_learn">开始学习</string> | |||
<string name="test_spell_tip">请从左至右依次点击选择正确的选项</string> | |||
<string name="pause">暂停</string> | |||
<string name="continue_">继续</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> |
@@ -89,7 +89,16 @@ ext { | |||
//gson https://github.com/google/gson | |||
Gson : "com.google.code.gson:gson:2.9.0", | |||
//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", | |||
] | |||
@@ -1,7 +0,0 @@ | |||
package com.suliang.common.base.adapter | |||
import androidx.lifecycle.ViewModel | |||
abstract class BaseAdapterVM<T,VM:ViewModel>(val vm:VM) : BaseAdapter<T>() { | |||
} |
@@ -10,7 +10,7 @@ import androidx.recyclerview.widget.RecyclerView | |||
import androidx.viewbinding.ViewBinding | |||
import com.suliang.common.R | |||
abstract class BaseAdapter<T> : | |||
abstract class BaseRVAdapter<T> : | |||
RecyclerView.Adapter<BaseAdapterViewHolder>() { | |||
companion object{ | |||
@@ -85,6 +85,9 @@ abstract class BaseAdapter<T> : | |||
fun getItem(position: Int) : T { | |||
return mData.get(position) | |||
} | |||
fun getData(): MutableList<T>{ | |||
return mData | |||
} | |||
/** | |||
@@ -92,7 +95,7 @@ abstract class BaseAdapter<T> : | |||
* @param data List<T>? | |||
*/ | |||
@SuppressLint("NotifyDataSetChanged") | |||
fun setData(data: MutableList<T>?) { | |||
open fun setData(data: MutableList<T>?) { | |||
mData.clear() | |||
data?.let { it -> //不为空 | |||
mData.addAll(data) |