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