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