Browse Source

主测试功能

master
suliang 2 years ago
parent
commit
2de3d44b77
100 changed files with 124521 additions and 170 deletions
  1. 29
    1
      .idea/codeStyles/Project.xml
  2. 2
    0
      .idea/misc.xml
  3. 14
    5
      app/build.gradle
  4. BIN
      app/src/main/assets/common_voice/di.mp3
  5. BIN
      app/src/main/assets/common_voice/kongge.mp3
  6. BIN
      app/src/main/assets/common_voice/mistake.mp3
  7. BIN
      app/src/main/assets/common_voice_uk/a_uk.mp3
  8. BIN
      app/src/main/assets/common_voice_uk/b_uk.mp3
  9. BIN
      app/src/main/assets/common_voice_uk/c_uk.mp3
  10. BIN
      app/src/main/assets/common_voice_uk/d_uk.mp3
  11. BIN
      app/src/main/assets/common_voice_uk/e_uk.mp3
  12. BIN
      app/src/main/assets/common_voice_uk/f_uk.mp3
  13. BIN
      app/src/main/assets/common_voice_uk/g_uk.mp3
  14. BIN
      app/src/main/assets/common_voice_uk/h_uk.mp3
  15. BIN
      app/src/main/assets/common_voice_uk/i_uk.mp3
  16. BIN
      app/src/main/assets/common_voice_uk/j_uk.mp3
  17. BIN
      app/src/main/assets/common_voice_uk/k_uk.mp3
  18. BIN
      app/src/main/assets/common_voice_uk/l_uk.mp3
  19. BIN
      app/src/main/assets/common_voice_uk/m_uk.mp3
  20. BIN
      app/src/main/assets/common_voice_uk/n_uk.mp3
  21. BIN
      app/src/main/assets/common_voice_uk/o_uk.mp3
  22. BIN
      app/src/main/assets/common_voice_uk/p_uk.mp3
  23. BIN
      app/src/main/assets/common_voice_uk/q_uk.mp3
  24. BIN
      app/src/main/assets/common_voice_uk/r_uk.mp3
  25. BIN
      app/src/main/assets/common_voice_uk/s_uk.mp3
  26. BIN
      app/src/main/assets/common_voice_uk/t_uk.mp3
  27. BIN
      app/src/main/assets/common_voice_uk/u_uk.mp3
  28. BIN
      app/src/main/assets/common_voice_uk/v_uk.mp3
  29. BIN
      app/src/main/assets/common_voice_uk/w_uk.mp3
  30. BIN
      app/src/main/assets/common_voice_uk/x_uk.mp3
  31. BIN
      app/src/main/assets/common_voice_uk/y_uk.mp3
  32. BIN
      app/src/main/assets/common_voice_uk/z_uk.mp3
  33. BIN
      app/src/main/assets/common_voice_us/a_us.mp3
  34. BIN
      app/src/main/assets/common_voice_us/b_us.mp3
  35. BIN
      app/src/main/assets/common_voice_us/c_us.mp3
  36. BIN
      app/src/main/assets/common_voice_us/d_us.mp3
  37. BIN
      app/src/main/assets/common_voice_us/e_us.mp3
  38. BIN
      app/src/main/assets/common_voice_us/f_us.mp3
  39. BIN
      app/src/main/assets/common_voice_us/g_us.mp3
  40. BIN
      app/src/main/assets/common_voice_us/h_us.mp3
  41. BIN
      app/src/main/assets/common_voice_us/i_us.mp3
  42. BIN
      app/src/main/assets/common_voice_us/j_us.mp3
  43. BIN
      app/src/main/assets/common_voice_us/k_us.mp3
  44. BIN
      app/src/main/assets/common_voice_us/l_us.mp3
  45. BIN
      app/src/main/assets/common_voice_us/m_us.mp3
  46. BIN
      app/src/main/assets/common_voice_us/n_us.mp3
  47. BIN
      app/src/main/assets/common_voice_us/o_us.mp3
  48. BIN
      app/src/main/assets/common_voice_us/p_us.mp3
  49. BIN
      app/src/main/assets/common_voice_us/q_us.mp3
  50. BIN
      app/src/main/assets/common_voice_us/r_us.mp3
  51. BIN
      app/src/main/assets/common_voice_us/s_us.mp3
  52. BIN
      app/src/main/assets/common_voice_us/t_us.mp3
  53. BIN
      app/src/main/assets/common_voice_us/u_us.mp3
  54. BIN
      app/src/main/assets/common_voice_us/v_us.mp3
  55. BIN
      app/src/main/assets/common_voice_us/w_us.mp3
  56. BIN
      app/src/main/assets/common_voice_us/x_us.mp3
  57. BIN
      app/src/main/assets/common_voice_us/y_us.mp3
  58. BIN
      app/src/main/assets/common_voice_us/z_us.mp3
  59. 106695
    0
      app/src/main/java/appApi/AppApi.java
  60. 3588
    0
      app/src/main/java/appApi/EngineGrpc.java
  61. 2
    3
      app/src/main/java/com/xkl/cdl/adapter/AdapterAutoPlaySelectRepeat.kt
  62. 2
    2
      app/src/main/java/com/xkl/cdl/adapter/AdapterCoursePackWithLearCenter.kt
  63. 5
    6
      app/src/main/java/com/xkl/cdl/adapter/AdapterLesson.kt
  64. 256
    0
      app/src/main/java/com/xkl/cdl/adapter/AdapterSpell.kt
  65. 80
    0
      app/src/main/java/com/xkl/cdl/adapter/itemdecoration/SpellItemDecoration.kt
  66. 34
    11
      app/src/main/java/com/xkl/cdl/data/AppConstants.kt
  67. 79
    0
      app/src/main/java/com/xkl/cdl/data/bean/LearnDialogBean.kt
  68. 11
    0
      app/src/main/java/com/xkl/cdl/data/bean/SpellItemBean.kt
  69. 1
    1
      app/src/main/java/com/xkl/cdl/data/bean/course/ExamBean.kt
  70. 2
    6
      app/src/main/java/com/xkl/cdl/data/bean/intentdata/ExamData.kt
  71. 19
    0
      app/src/main/java/com/xkl/cdl/data/event/LearnEventData.kt
  72. 19
    0
      app/src/main/java/com/xkl/cdl/data/manager/CourseManager.kt
  73. 11
    1
      app/src/main/java/com/xkl/cdl/data/manager/UserInfoManager.kt
  74. 94
    0
      app/src/main/java/com/xkl/cdl/dialog/CommonDialog.kt
  75. 60
    0
      app/src/main/java/com/xkl/cdl/dialog/CommonDialogBean.kt
  76. 113
    34
      app/src/main/java/com/xkl/cdl/dialog/LearnDialog.kt
  77. 39
    1
      app/src/main/java/com/xkl/cdl/module/learn/LearnBaseViewModel.kt
  78. 446
    28
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamActivity.kt
  79. 630
    7
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamViewModel.kt
  80. 59
    27
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragment.kt
  81. 23
    0
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragmentViewModel.kt
  82. 0
    3
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseReviewFragment.kt
  83. 3
    3
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseTotalTestFragment.kt
  84. 5
    5
      app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt
  85. 11989
    0
      app/src/main/java/mqComsumerV1/Struct.java
  86. 5
    0
      app/src/main/res/drawable/shape_rounder_8_stroke_green1.xml
  87. 5
    0
      app/src/main/res/drawable/shape_rounder_8_stroke_red1.xml
  88. 2
    1
      app/src/main/res/layout/activity_learn_exam.xml
  89. 106
    0
      app/src/main/res/layout/dialog_common.xml
  90. 25
    5
      app/src/main/res/layout/dialog_lesson_learn.xml
  91. 2
    2
      app/src/main/res/layout/inc_exam_spell_content.xml
  92. 24
    3
      app/src/main/res/layout/inc_exam_word_choose_content.xml
  93. 1
    2
      app/src/main/res/layout/inc_learn_test_statistic.xml
  94. 4
    3
      app/src/main/res/layout/includ_test_option_item.xml
  95. 16
    0
      app/src/main/res/layout/item_spell_single_word.xml
  96. 6
    0
      app/src/main/res/values/strings.xml
  97. 10
    1
      build.gradle
  98. 0
    7
      lib/common/src/main/java/com/suliang/common/base/adapter/BaseAdapterVM.kt
  99. 5
    2
      lib/common/src/main/java/com/suliang/common/base/adapter/BaseRVAdapter.kt
  100. 0
    0
      lib/common/src/main/java/com/suliang/common/base/adapter/BaseRVAdapterVM.kt

+ 29
- 1
.idea/codeStyles/Project.xml View File

<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
<JavaCodeStyleSettings> <JavaCodeStyleSettings>
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" /> <option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" /> <option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="SPACE_AROUND_RANGE" value="true" /> <option name="SPACE_AROUND_RANGE" value="true" />
<option name="SPACE_BEFORE_TYPE_COLON" value="true" /> <option name="SPACE_BEFORE_TYPE_COLON" value="true" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="2" />
<option name="IF_RPAREN_ON_NEW_LINE" value="false" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<DBN-PSQL> <DBN-PSQL>
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" /> <option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
<option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" /> <option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" />
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
<option name="VARIABLE_ANNOTATION_WRAP" value="2" /> <option name="VARIABLE_ANNOTATION_WRAP" value="2" />
<option name="ENUM_CONSTANTS_WRAP" value="2" /> <option name="ENUM_CONSTANTS_WRAP" value="2" />
<option name="WRAP_ON_TYPING" value="0" /> <option name="WRAP_ON_TYPING" value="0" />

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

<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/content_learn_base.xml" value="0.4979166666666667" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/content_learn_base.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_bottom_auto_play_select.xml" value="0.4979166666666667" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_bottom_auto_play_select.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_bottom_course_more.xml" value="0.33" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_bottom_course_more.xml" value="0.33" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_common.xml" value="0.30978260869565216" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_item_select_repeat.xml" value="0.4979166666666667" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_item_select_repeat.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_lesson_learn.xml" value="0.4144927536231884" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/dialog_lesson_learn.xml" value="0.4144927536231884" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_lesson.xml" value="0.33" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_lesson.xml" value="0.33" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_title_bar.xml" value="0.25052083333333336" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_title_bar.xml" value="0.25052083333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_course_lesson.xml" value="0.4785615491009682" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_course_lesson.xml" value="0.4785615491009682" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_empty.xml" value="0.4979166666666667" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_empty.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_spell_single_word.xml" value="0.23632218844984804" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_course_progress.xml" value="0.25" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_course_progress.xml" value="0.25" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_coursepack.xml" value="0.43500866551126516" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_coursepack.xml" value="0.43500866551126516" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/merge_recyclerview_smart_refresh_layout.xml" value="0.34427083333333336" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/merge_recyclerview_smart_refresh_layout.xml" value="0.34427083333333336" />

+ 14
- 5
app/build.gradle View File

} }


dependencies { dependencies {
implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs')
implementation fileTree(include: ['*.jar', "*.aar"], dir: 'libs')
// implementation 'androidx.legacy:legacy-support-v4:1.0.0' // implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation project(path: ':lib:common') implementation project(path: ':lib:common')
// implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' // implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
// implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' // implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
// implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' // implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'


rootProject.ext.dependencies_required.each{ k, v -> implementation v}
rootProject.ext.dependencies_required.each { k, v -> implementation v }
testImplementation rootProject.ext.dependencies_testImplementation.junit testImplementation rootProject.ext.dependencies_testImplementation.junit
rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v}
rootProject.ext.dependencies_androidTestImplementation.each { k, v -> androidTestImplementation v }


def customDependencies = rootProject.ext.dependencies_custom
def customDependencies = rootProject.ext.dependencies_custom
//SmartRefreshLayout //SmartRefreshLayout
implementation customDependencies.SmartRefreshLayout implementation customDependencies.SmartRefreshLayout
implementation 'io.github.scwang90:refresh-header-classics:2.0.5' //经典刷新头
implementation 'io.github.scwang90:refresh-header-classics:2.0.5' //经典刷新头
//SqlCipher //SqlCipher
implementation customDependencies.SqlCipher implementation customDependencies.SqlCipher
//androidx-sqlite //androidx-sqlite
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
//Gson //Gson
implementation customDependencies.Gson implementation customDependencies.Gson
//protobuf
implementation customDependencies.protobuf_java
implementation customDependencies.protobuf_java_format
//grpc
implementation customDependencies.annotation_api
implementation customDependencies.grpc_okhttp
implementation customDependencies.grpc_android
implementation customDependencies.grpc_protobuf
implementation customDependencies.grpc_stub


} }

BIN
app/src/main/assets/common_voice/di.mp3 View File


BIN
app/src/main/assets/common_voice/kongge.mp3 View File


BIN
app/src/main/assets/common_voice/mistake.mp3 View File


BIN
app/src/main/assets/common_voice_uk/a_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/b_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/c_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/d_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/e_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/f_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/g_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/h_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/i_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/j_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/k_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/l_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/m_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/n_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/o_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/p_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/q_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/r_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/s_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/t_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/u_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/v_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/w_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/x_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/y_uk.mp3 View File


BIN
app/src/main/assets/common_voice_uk/z_uk.mp3 View File


BIN
app/src/main/assets/common_voice_us/a_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/b_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/c_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/d_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/e_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/f_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/g_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/h_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/i_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/j_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/k_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/l_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/m_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/n_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/o_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/p_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/q_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/r_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/s_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/t_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/u_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/v_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/w_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/x_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/y_us.mp3 View File


BIN
app/src/main/assets/common_voice_us/z_us.mp3 View File


+ 106695
- 0
app/src/main/java/appApi/AppApi.java
File diff suppressed because it is too large
View File


+ 3588
- 0
app/src/main/java/appApi/EngineGrpc.java
File diff suppressed because it is too large
View File


+ 2
- 3
app/src/main/java/com/xkl/cdl/adapter/AdapterAutoPlaySelectRepeat.kt View File



import android.view.ViewGroup import android.view.ViewGroup
import com.suliang.common.base.adapter.BaseAdapterViewHolder import com.suliang.common.base.adapter.BaseAdapterViewHolder
import androidx.viewbinding.ViewBinding
import com.suliang.common.base.adapter.BaseAdapter
import com.suliang.common.base.adapter.BaseRVAdapter
import com.suliang.common.extension.click import com.suliang.common.extension.click
import com.xkl.cdl.R import com.xkl.cdl.R
import com.xkl.cdl.databinding.DialogItemSelectRepeatBinding import com.xkl.cdl.databinding.DialogItemSelectRepeatBinding
* create 2020/6/29 13:44 * create 2020/6/29 13:44
* Describe: 自动播放弹窗列表选择适配器 * Describe: 自动播放弹窗列表选择适配器
*/ */
class AdapterAutoPlaySelectRepeat : BaseAdapter<Void?>() {
class AdapterAutoPlaySelectRepeat : BaseRVAdapter<Void?>() {
override fun coverEmptyViewHolder(parent: ViewGroup): BaseAdapterViewHolder { override fun coverEmptyViewHolder(parent: ViewGroup): BaseAdapterViewHolder {
return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_empty)) return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_empty))
} }

+ 2
- 2
app/src/main/java/com/xkl/cdl/adapter/AdapterCoursePackWithLearCenter.kt View File



import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.suliang.common.base.adapter.BaseAdapterVM
import com.suliang.common.base.adapter.BaseRVAdapterVM
import com.suliang.common.base.adapter.BaseAdapterViewHolder import com.suliang.common.base.adapter.BaseAdapterViewHolder
import com.suliang.common.databinding.ItemEmptyBinding import com.suliang.common.databinding.ItemEmptyBinding
import com.suliang.common.extension.click import com.suliang.common.extension.click
* Describe: 学习中心课程包适配器 * Describe: 学习中心课程包适配器
*/ */
class AdapterCoursePackWithLearCenter(vm: CoursePackFragmentViewModel) : class AdapterCoursePackWithLearCenter(vm: CoursePackFragmentViewModel) :
BaseAdapterVM<CoursePack, CoursePackFragmentViewModel>(vm) {
BaseRVAdapterVM<CoursePack, CoursePackFragmentViewModel>(vm) {


override fun onBindEmptyViewHolder(holder: BaseAdapterViewHolder) { override fun onBindEmptyViewHolder(holder: BaseAdapterViewHolder) {
(holder.binding as ItemEmptyBinding).apply { (holder.binding as ItemEmptyBinding).apply {

+ 5
- 6
app/src/main/java/com/xkl/cdl/adapter/AdapterLesson.kt View File

import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.suliang.common.base.adapter.BaseAdapterVM
import com.suliang.common.base.adapter.BaseRVAdapterVM
import com.suliang.common.base.adapter.BaseAdapterViewHolder import com.suliang.common.base.adapter.BaseAdapterViewHolder
import com.suliang.common.extension.click import com.suliang.common.extension.click
import com.xkl.cdl.R import com.xkl.cdl.R
* create 2022/3/29 16:43 * create 2022/3/29 16:43
* Describe: 课程课时列表适配器 * Describe: 课程课时列表适配器
*/ */
class AdapterLesson(vm: CourseMainFragmentViewModel) : BaseAdapterVM<Lesson, CourseMainFragmentViewModel>(vm) {
class AdapterLesson(vm: CourseMainFragmentViewModel) : BaseRVAdapterVM<Lesson, CourseMainFragmentViewModel>(vm) {


/** 选中item的位置 */ /** 选中item的位置 */
var selectPos = -1 var selectPos = -1






//事件: 将章节名称的点击事件取消了
//事件
layoutContent.click { layoutContent.click {
//选中项非当前项,则需要改变选中颜色,直接通知更新,调用对应位置的notfy //选中项非当前项,则需要改变选中颜色,直接通知更新,调用对应位置的notfy
if (selectPos != position) { if (selectPos != position) {
notifyItemChanged(selectPos)
val temp = selectPos
selectPos = position selectPos = position
notifyItemChanged(temp)
notifyItemChanged(selectPos) notifyItemChanged(selectPos)
} }
notifyItemChanged(position)
notifyItemChanged(position, null)
onItemClick.invoke(it, position, lesson) onItemClick.invoke(it, position, lesson)
} }
} }

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

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

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

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

}

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

/**学后总测试*/ /**学后总测试*/
const val TEST_TYPE_AFTER_TOTAL = 5 const val TEST_TYPE_AFTER_TOTAL = 5


/**备忘测试*/
/**备忘测试*/
const val TEST_TYPE_MEMO = 6 const val TEST_TYPE_MEMO = 6


/**作文知识点测试*/ /**作文知识点测试*/
const val TEST_SCORE_LEVEL_1 = 80 const val TEST_SCORE_LEVEL_1 = 80
/** 测试通过 >= 太棒了 90 */ /** 测试通过 >= 太棒了 90 */
const val TEST_SCORE_LEVEL_2 = 90 const val TEST_SCORE_LEVEL_2 = 90

const val ACTION_START_BEFORE_TEST = 1
const val ACTION_START_AFTER_TEST = 2
const val ACTION_START_TOTAL_TEST = 3
const val ACTION_START_TOTAL_AFTER_TEST = 4

//测试的题目类型 //测试的题目类型
/** 选择题类型 */ /** 选择题类型 */
const val TEST_QUEST_TYPE_CHOICE = 1
const val TEST_QUEST_TYPE_CHOICE = 1L
/** 口语对话测试 */ /** 口语对话测试 */
const val TEST_QUEST_TYPE_SPOKEN_DIALOGUE = 4
const val TEST_QUEST_TYPE_GAP_FILLING = 2 //填空题
const val TEST_QUEST_TYPE_JUDGE = 3 //判断题
const val TEST_QUEST_TYPE_SPOKEN_DIALOGUE = 4L
const val TEST_QUEST_TYPE_GAP_FILLING = 2L //填空题
const val TEST_QUEST_TYPE_JUDGE = 3L //判断题
/**总测试 */ /**总测试 */
const val TEST_COUNT_TOTAL = 25 const val TEST_COUNT_TOTAL = 25
/**章节测试*/ /**章节测试*/
const val SOUND_TYPE_UK = 2 //英 const val SOUND_TYPE_UK = 2 //英
/**中文*/ /**中文*/
const val SOUND_TYPE_CN = 3 //中文 const val SOUND_TYPE_CN = 3 //中文
// 拼写的最小时间 1.6 秒
const val SPELL_TEST_MIN_TIME = 1600
//单个单词的最小计时 6秒
const val TEST_MIN_TIME = 6000
const val TEST_CORRECT = 1L //答题正确
const val TEST_ERROR = -1L //答题错误
const val TEST_UN_ANSWER = 0L //答题未答
/**测试正确,到下一题的时间*/
const val TEST_TO_NEXT_CORRECT_TIME = 500L
/**测试错误: 未答到一题的时间*/
const val TEST_TO_NEXT_ERROR_TIME = 2000L
/** 对话框类型: 测试 */
const val DIALOG_TYPE_EXAM = 1
/** 对话框类型: 学习 */
const val DIALOG_TYPE_LEARN = 2
/**--- 总线动作 --------------------------------- */
/**action key 改变界面 到目录页 */
const val EVENT_COURSE = "action_change_page"
/** 动作:学前总测之 开始学习 */
const val ACTION_COURSE_TEST_START_LEARN = 1
/** 数据动作:学前总测结束传递数据 */
const val DATA_COURSE_TEST_BEFORE = 2
/**--- 弹窗动作 --------------------------------- */
/** 学前总测弹窗: 开始学习 */
const val DIALOG_START_LEARN = 1
} }

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

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

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

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)

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

var error1: String = "" //错误选项1 var error1: String = "" //错误选项1
var error2: String = "" //错误选项2 var error2: String = "" //错误选项2
var error3 : String = "" //错误选项3 var error3 : String = "" //错误选项3
var type = 0 //测试题类型
var type = 0L //测试题类型


var chapterId : Long = 0 //章节id var chapterId : Long = 0 //章节id
var lessonId : Long = 0 //课时id var lessonId : Long = 0 //课时id

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

val showTitle : String, //去测试本地显示名称,学前学后 为课程名称,课时前、后为课时名称 val showTitle : String, //去测试本地显示名称,学前学后 为课程名称,课时前、后为课时名称
val saveTitle : String, //测试试卷上传名称 : 学习中心:课程名称 + “ ” + 学习类型 课程测试:课程名称 + “ ” 学习类型 + (章节拼接名称) 词汇量测试:词汇量测试:阶段名称 val saveTitle : String, //测试试卷上传名称 : 学习中心:课程名称 + “ ” + 学习类型 课程测试:课程名称 + “ ” 学习类型 + (章节拼接名称) 词汇量测试:词汇量测试:阶段名称
) { ) {
var coursePackId : Long = 0 //课程包id, 测试错误上次数据需要
var coursePackType:Int = 0 //课程包类型 var coursePackType:Int = 0 //课程包类型
var courseId : Long = 0 //课程id
var courseType : Int= 0 //课程类型 var courseType : Int= 0 //课程类型
var testData : List<ExamBean>? = null //测试题数据 var testData : List<ExamBean>? = null //测试题数据
var lesson : Lesson? = null //课时测试时必传 var lesson : Lesson? = null //课时测试时必传
// 课程/课时学前测试正确错误列表 key=> {chapter_id}_{lesson_id}_{entity_id} value=>正确 true;错误 false // 课程/课时学前测试正确错误列表 key=> {chapter_id}_{lesson_id}_{entity_id} value=>正确 true;错误 false
var mExamWRMap : HashMap<String, Boolean>? = null var mExamWRMap : HashMap<String, Boolean>? = null
//学前总测试与章节前测试时学习错误集合
var newErrorMap : HashMap<String, Boolean> = hashMapOf()
//口语对话测试数据: 仅在口语对话课时后测试中使用该课时的学习数据
var allSentencesList : List<List<DialogueSpokenItem>>? = null
} }

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

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 //新错误单词列表
}

+ 19
- 0
app/src/main/java/com/xkl/cdl/data/manager/CourseManager.kt View File

} }
} }
} }
/**
* 获取测试类型对应的名称
* @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 -> ""
}
}
} }

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

package com.xkl.cdl.data.manager package com.xkl.cdl.data.manager


import com.suliang.common.util.SpUtils
import com.xkl.cdl.data.AppConstants

/** /**
* author suliang * author suliang
* create 2022/4/8 17:47 * create 2022/4/8 17:47
*/ */
object UserInfoManager { object UserInfoManager {



/** 获取默认发音方式 */
fun getDefaultSoundWay() : Int{
return SpUtils.instance.decode("defaultSoundWay",Int::class.java,AppConstants.SOUND_TYPE_UK)
}
/** 存储默认发音方式 */
fun putDefaultSoundWay(soundWay : Int){
SpUtils.instance.encode("defaultSoundWay",soundWay)
}


} }

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

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

+ 60
- 0
app/src/main/java/com/xkl/cdl/dialog/CommonDialogBean.kt View File

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

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

package com.xkl.cdl.dialog package com.xkl.cdl.dialog


import android.os.Bundle
import android.view.Gravity
import android.view.View import android.view.View
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentManager
import com.suliang.common.AppConfig
import com.suliang.common.base.BaseDialogFragment import com.suliang.common.base.BaseDialogFragment
import com.suliang.common.extension.click import com.suliang.common.extension.click
import com.suliang.common.util.LogUtil import com.suliang.common.util.LogUtil
import com.suliang.common.util.os.ScreenUtil
import com.xkl.cdl.R import com.xkl.cdl.R
import com.xkl.cdl.data.AppConstants import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.bean.LearnDialogBean
import com.xkl.cdl.databinding.DialogLessonLearnBinding import com.xkl.cdl.databinding.DialogLessonLearnBinding


/** /**
* create 2022/4/2 15:47 * create 2022/4/2 15:47
* Describe: 学习的通用弹窗 * Describe: 学习的通用弹窗
*/ */
class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() {

private lateinit var dialogListener: DialogFragmentListener

class LearnDialog private constructor(): BaseDialogFragment<DialogLessonLearnBinding>() {
companion object {
fun newInstance(params : LearnDialogBean) : LearnDialog {
val args = Bundle()
args.putParcelable(AppConfig.INTENT_1, params)
val fragment = LearnDialog()
fragment.arguments = args
return fragment
}
}
/** 事件动作 */
lateinit var onDialogListener : (action : Int, dialog : LearnDialog) -> Unit
//参数
private lateinit var learnDialogBean : LearnDialogBean
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
//取值获参
learnDialogBean = requireArguments().getParcelable(AppConfig.INTENT_1)!!
}
override fun initFragment() { override fun initFragment() {
initClick()
// initLessonBeforeTest()
initLessonBeforeTestOver()
when (learnDialogBean.dialogType) {
AppConstants.DIALOG_TYPE_EXAM -> when (learnDialogBean.examType) {
AppConstants.TEST_TYPE_BEFORE_TOTAL -> initCourseBeforeTotalTestOver()
}
AppConstants.DIALOG_TYPE_LEARN -> {
}
}
} }

fun initClick() {
dialogListener = if (parentFragment != null) {
parentFragment as DialogFragmentListener
} else {
activity as DialogFragmentListener
override fun onStart() {
super.onStart()
//设置dialog位置
requireDialog().window?.let {
it.attributes.run {
y = ScreenUtil.getScreenHeight() / 5
it.attributes = this
}
it.setGravity(Gravity.TOP)
} }
} }

/** 初始化分数 */
private fun initScore() {
//插画
when {
learnDialogBean.score < AppConstants.TEST_SCORE_LEVEL_1 -> {
binding.imgIv.setImageResource(R.mipmap.test_score_level_1)
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(),R.color.gray_2))
}
learnDialogBean.score < AppConstants.TEST_SCORE_LEVEL_2 -> {
binding.imgIv.setImageResource(R.mipmap.test_score_level_2)
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(),R.color.red_2))
}
else -> {
binding.imgIv.setImageResource(R.mipmap.test_score_level_3)
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(),R.color.red_1))
}
}
//分数
binding.tvScore.run {
visibility = View.VISIBLE
text = "${learnDialogBean.score}分"
}
//本次测试成绩显示
binding.tvTip.visibility = View.VISIBLE
}
/** 设置成绩数量 */
private fun initNumber(){
binding.incStatisticsNumber.run {
tvCorrectNumber.text = "${learnDialogBean.correctNumber}"
tvErrorNumber.text = "${learnDialogBean.errorNumber}"
}
}
/** 学前总测试 结束 */
private fun initCourseBeforeTotalTestOver() {
binding.groupTotalTest.visibility = View.VISIBLE
initScore()
binding.tvTitle.text = "恭喜你,完成了学前总测试!"
binding.tvTip1.text = "学考乐已为您智能生成了个性化学习计划"
initNumber()
binding.tvRight.text = resources.getString(R.string.start_learn)
binding.tvRight.click {
onDialogListener(AppConstants.ACTION_COURSE_TEST_START_LEARN, this)
}
}
fun initLessonBeforeTest() { fun initLessonBeforeTest() {
binding.tvTitle.text = "课时学前测试" binding.tvTitle.text = "课时学前测试"
binding.tvLessonName.text = "章节课时名称" binding.tvLessonName.text = "章节课时名称"
binding.tvRight.text = "开始测试" binding.tvRight.text = "开始测试"
binding.tvLeft.visibility = View.VISIBLE binding.tvLeft.visibility = View.VISIBLE
binding.tvLeft.text = "随便设置" binding.tvLeft.text = "随便设置"
binding.tvRight.click { binding.tvRight.click {
LogUtil.e("Dialog -- > show()") LogUtil.e("Dialog -- > show()")
} }
binding.tvLeft.click { binding.tvLeft.click {
LogUtil.e("Dialog -- > hide()") LogUtil.e("Dialog -- > hide()")
} }
} }
fun initLessonBeforeTestOver() { fun initLessonBeforeTestOver() {
binding.tvScore.run { binding.tvScore.run {
visibility = View.VISIBLE visibility = View.VISIBLE
binding.tvRight.click { binding.tvRight.click {
// TODO: 2022/4/2 开始学习 // TODO: 2022/4/2 开始学习
} }
} }
fun initLessonAfterTestOver() { fun initLessonAfterTestOver() {
//未通过、通过和通过已通过,均为一致,只是显示的状态不同 //未通过、通过和通过已通过,均为一致,只是显示的状态不同
val score = if (1 + 1 == 2) 100 else 88 val score = if (1 + 1 == 2) 100 else 88
when { when {
score < AppConstants.TEST_SCORE_LEVEL_1 -> { //小于80 score < AppConstants.TEST_SCORE_LEVEL_1 -> { //小于80
binding.imgIv.setImageResource(R.mipmap.test_score_level_1) binding.imgIv.setImageResource(R.mipmap.test_score_level_1)
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_2)) binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_2))
} }
} }
binding.tvScore.run { binding.tvScore.run {
visibility = View.VISIBLE visibility = View.VISIBLE
text = "${score}分" text = "${score}分"
} }
binding.tvTip.visibility = View.VISIBLE binding.tvTip.visibility = View.VISIBLE
binding.tvTitle.text = "恭喜你,完成了课时学前测试" binding.tvTitle.text = "恭喜你,完成了课时学前测试"
binding.tvLessonName.visibility = View.GONE binding.tvLessonName.visibility = View.GONE


// binding.tvTip1.visibility = View.VISIBLE // binding.tvTip1.visibility = View.VISIBLE
// binding.tvTip1.setText(resources.getString(R.string.test_before_test_over_tip)) // binding.tvTip1.setText(resources.getString(R.string.test_before_test_over_tip))
binding.tvRight.setText(resources.getString(R.string.start_learn)) binding.tvRight.setText(resources.getString(R.string.start_learn))
binding.tvRight.click { binding.tvRight.click {
// TODO: 2022/4/2 开始学习 // TODO: 2022/4/2 开始学习
} }

}


interface DialogFragmentListener {
fun dialogButtonClick(action: DialogEventAction)
} }
} }

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

package com.xkl.cdl.module.learn package com.xkl.cdl.module.learn


import android.os.Handler
import android.os.Looper
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.suliang.common.base.viewmodel.BaseViewModel import com.suliang.common.base.viewmodel.BaseViewModel
import java.util.*
import kotlin.concurrent.timerTask


/** /**
* author suliang * author suliang
* create 2022/4/2 14:06 * create 2022/4/2 14:06
* Describe: 学习基类: 实现统一流程接口和计时实现
* Describe: 总计时的实现
*/ */
abstract class LearnBaseViewModel : BaseViewModel() { abstract class LearnBaseViewModel : BaseViewModel() {
//标记是否测试完成
var isAllOver = false
protected var mHandler = Handler(Looper.getMainLooper())
//记录总消耗时间
var totalUseTime : MutableLiveData<Long> = MutableLiveData(0)
private var totalTimer : Timer = Timer()
private var timeTask : TimerTask? = null
//是否进行计时,默认计时,为false不计时,更改为false的条件时,出现弹窗或者当前页不在前台
var countingEnable = true
/** 开始计时 */
fun startTotalCounting(){
timeTask = timerTask {
if (countingEnable) {
totalUseTime.postValue(totalUseTime.value?.plus(1000))
}
}.apply {
if (!isAllOver) {
totalTimer.schedule(this, 1000, 1000)
}
}
}
/** 停止计时 */
fun stopTotalCountTing(){
totalTimer.cancel()
timeTask = null
}


} }

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

package com.xkl.cdl.module.learn package com.xkl.cdl.module.learn


import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.text.SpannableStringBuilder
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.suliang.common.AppConfig
import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.airbnb.lottie.LottieAnimationView
import com.suliang.common.base.activity.BaseActivityVM import com.suliang.common.base.activity.BaseActivityVM
import com.suliang.common.base.activity.UIBaseActivity
import com.suliang.common.eventbus.LiveDataBus
import com.suliang.common.extension.click import com.suliang.common.extension.click
import com.suliang.common.util.DateUtil
import com.suliang.common.util.DrawableUti
import com.xkl.cdl.R import com.xkl.cdl.R
import com.xkl.cdl.adapter.AdapterSpell
import com.xkl.cdl.adapter.itemdecoration.SpellItemDecoration
import com.xkl.cdl.data.AppConstants import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.bean.LearnDialogBean
import com.xkl.cdl.data.bean.course.ExamBean
import com.xkl.cdl.data.event.LearnEventData
import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.data.manager.UserInfoManager
import com.xkl.cdl.databinding.* import com.xkl.cdl.databinding.*
import com.xkl.cdl.module.m_center_learn.CoursePackMainActivity
import com.xkl.cdl.dialog.CommonDialog
import com.xkl.cdl.dialog.CommonDialogBean
import com.xkl.cdl.dialog.LearnDialog


/** /**
* author suliang
* create 2022/4/6 19:18
* Describe: 测试界面 总测 课时测
*/
class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding,LearnExamViewModel>() {
* author suliang
* create 2022/4/6 19:18
* Describe: 测试界面 总测 课时测
*/
class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamViewModel>() {
companion object { companion object {
@JvmStatic @JvmStatic
fun newInstance(context: Context) {
fun newInstance(context : Context) {
context.startActivity(Intent(context, LearnExamActivity::class.java)) context.startActivity(Intent(context, LearnExamActivity::class.java))
} }
} }

//拼写内容binding //拼写内容binding
private lateinit var spellBinding : IncExamSpellContentBinding private lateinit var spellBinding : IncExamSpellContentBinding
//拼写内容适配器
private lateinit var spellAdapter : AdapterSpell
//选项内容binding //选项内容binding
private lateinit var wordChooseBinding : IncExamWordChooseContentBinding private lateinit var wordChooseBinding : IncExamWordChooseContentBinding
//当前音频播放的 LottieAnimationView
private var currentPlayView : LottieAnimationView? = null
override fun initViewModel() : LearnExamViewModel { override fun initViewModel() : LearnExamViewModel {
return ViewModelProvider(this)[LearnExamViewModel::class.java] return ViewModelProvider(this)[LearnExamViewModel::class.java]
} }
override fun initActivity(savedInstanceState : Bundle?) { override fun initActivity(savedInstanceState : Bundle?) {
when(vm.intentData.courseType){
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> {
spellBinding = IncExamSpellContentBinding.inflate(layoutInflater,binding.containerLayout,true)
}
else -> {
wordChooseBinding = IncExamWordChooseContentBinding.inflate(layoutInflater,binding.containerLayout,true)
initTitle()
initStatistics()
initContent()
//暂停 继续事件
binding.tvErrorState.setOnClickListener {
with(it as TextView) {
when (text) {
resources.getString(R.string.pause) -> { //暂停点击
vm.pauseToNext()
setText(R.string.continue_)
}
resources.getString(R.string.continue_) -> {
vm.continueToNext()
setText(R.string.pause)
visibility = View.GONE
}
}
} }
} }
initTitle()
} }
//标题
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun initTitle(){
private fun initTitle() {
binding.incLearnTitle.run { binding.incLearnTitle.run {
imgBack.click { onBack() }
imgBack.click { onBack() }
//题目 //题目
tvTitle.text = vm.intentData.showTitle tvTitle.text = vm.intentData.showTitle
//题目进度 //题目进度
tvNumProgress.text = "${vm.currentIndex+1}/${vm.testData.size}"
binding.incLearnTitle.tvNumProgress.text = "${vm.currentIndex + 1}/${vm.testData.size}"
//默认发音 //默认发音
vm.defaultSoundWay = UserInfoManager.getDefaultSoundWay()
voiceSwitch.setSoundWay(vm.defaultSoundWay)
voiceSwitch.soundWayChange.observe(this@LearnExamActivity) {
vm.defaultSoundWay = it
UserInfoManager.putDefaultSoundWay(it)
if (this@LearnExamActivity::spellAdapter.isInitialized) {
spellAdapter.defaultSoundWay = it
}
}
} }
} }
private fun setNumberProgress(){
//统计
@SuppressLint("SetTextI18n")
private fun initStatistics() {
binding.incStatistics.run {
progressTestNumber.max = vm.testData.size
tvTestTypeName.text = CourseManager.getExamTypeName(vm.intentData.examType)
//监听总时间的改变
vm.totalUseTime.observe(this@LearnExamActivity) {
tvTestTime.text = DateUtil.formatGMT(it, DateUtil.FORMAT_2)
}
//监听正确错误数的变化
vm.correctLiveData.observe(this@LearnExamActivity) {
tvCorrectNumber.text = "正确: $it"
}
vm.errorLiveData.observe(this@LearnExamActivity) {
tvErrorNumber.text = "错误: $it"
}
}
}
//内容初始
private fun initContent() {
when (vm.intentData.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> {
spellBinding = IncExamSpellContentBinding.inflate(layoutInflater, binding.containerLayout, true)
spellBinding.spellRecyclerView.run {
layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.HORIZONTAL)
addItemDecoration(SpellItemDecoration())
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
spellAdapter = AdapterSpell().apply {
onItemSpellingClickListener = itemSpellingClick
defaultSoundWay = vm.defaultSoundWay
}
adapter = spellAdapter
}
//初始化事件
initChooseListener()
}
else -> {
wordChooseBinding = IncExamWordChooseContentBinding.inflate(layoutInflater, binding.containerLayout, true)
//content init
vm.optionList.add(wordChooseBinding.incA.apply {
tvLable.text = "A"
})
vm.optionList.add(wordChooseBinding.incB.apply {
tvLable.text = "B"
})
vm.optionList.add(wordChooseBinding.incC.apply {
tvLable.text = "C"
})
vm.optionList.add(wordChooseBinding.incD.apply {
tvLable.text = "D"
})
//初始化事件
initChooseListener()
//正常、正确、错误 文本和背景颜色
vm.normalTextColor = ContextCompat.getColor(this, R.color.main_text_color)
vm.correctTextColor = ContextCompat.getColor(this, R.color.green_1)
vm.errorTextColor = ContextCompat.getColor(this, R.color.red_1)
vm.normalBg = R.drawable.shape_rounder_8_stroke_gray1
vm.correctBg = R.drawable.shape_rounder_8_stroke_green1
vm.errorBg = R.drawable.shape_rounder_8_stroke_red1
vm.correctIv = DrawableUti.changeSvgColor(resources, R.drawable.ic_right, R.color.green_1)
// DrawableUti.tintDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_right, null)!!, R.color.green_1)
vm.errorIv = DrawableUti.changeSvgColor(resources, R.drawable.ic_wrong, R.color.red_1)
// DrawableUti.tintDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_wrong, null)!!, R.color.red_1)
}
}
}
/** 初始四选一的点击事件 只处理问题和单词的事件,选项事件,每个新的单词开始时设置,答题后再取消设置
* 拼写,释义、单词都不发音,只有拼写和拼写完成发音
* 作文知识点测试,没有发音
* 辨音、口语单词与音频均可发音,
* 其他单词可以发音
* */
private fun initChooseListener() {
when (vm.intentData.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_SPELL, AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> {
}
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_ENGLISH_VOICE -> {
wordChooseBinding.tvWord.setOnClickListener(ivVoiceClick)
wordChooseBinding.ivVoice.setOnClickListener(ivVoiceClick)
}
else -> { //四选一题目可以发音
wordChooseBinding.tvWord.setOnClickListener(ivVoiceClick)
}
}
}
@SuppressLint("SetTextI18n")
private fun setNumberProgress() {
(vm.currentIndex + 1).let {
binding.incLearnTitle.tvNumProgress.text = "${vm.currentIndex + 1}/${vm.testData.size}"
binding.incStatistics.progressTestNumber.progress = it
}
} }
override fun loadData() { override fun loadData() {
//初始获取第一条数据
vm.loadNext()
//监听倒计时
vm.currentSuplusTime.observe(this) {
when (vm.intentData.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> spellBinding.progressWordCountdownTime.progress = it
else -> wordChooseBinding.progressWordCountdownTime.progress = it
}
}
//监听到数据的到来
vm.currentExamBean.observe(this) {
binding.tvErrorState.visibility = View.GONE
it?.let {
//更新进度
setNumberProgress()
//更新内容ui
initContentUI(it)
//设置初始事件
initChooseContentLister(it)
vm.startCurrentCountingTime()
} ?: testOver()
}
//监听到四选一答案到来
vm.currentChooseResultLiveData.observe(this) {
//取消事件
initChooseContentLister(null)
//未答和正确都更新显示正确的项 错误同时显示正确和错误的项
wordChooseBinding.run {
ivVoice.visibility = View.GONE
tvWord.visibility = View.VISIBLE
//显示正确项
vm.optionList[vm.currentCorrectPosition].run {
root.setBackgroundResource(vm.correctBg)
tvLable.setTextColor(vm.correctTextColor)
tvOption.setTextColor(vm.correctTextColor)
ivStatu.visibility = View.VISIBLE
ivStatu.setImageDrawable(vm.correctIv)
}
//显示错误项
if (it == AppConstants.TEST_ERROR) {
vm.optionList[vm.currentClickPosition].run {
root.setBackgroundResource(vm.errorBg)
tvLable.setTextColor(vm.errorTextColor)
tvOption.setTextColor(vm.errorTextColor)
ivStatu.visibility = View.VISIBLE
ivStatu.setImageDrawable(vm.errorIv)
}
}
if (it != AppConstants.TEST_CORRECT) {
binding.tvErrorState.visibility = View.VISIBLE
}
//获取下一题
vm.loadNext()
}
}
//拼写结果监听
vm.currentSpellResultLiveData.observe(this) {
if (it != AppConstants.TEST_CORRECT) {
binding.tvErrorState.visibility = View.VISIBLE
}
//获取下一题
vm.loadNext()
}
}
/** 四选一对option设置事件 新数据来的时候 设置option的事件
* @param examBean 非空,则需要设置事件 为空则取消点击事件
* */
private fun initChooseContentLister(examBean : ExamBean?) {
when (vm.intentData.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> {
examBean?.let {
when (it.answersIsAudio) { //选项是否音频
true -> {
// TODO: 2022/4/13 设置音频事件的路劲tag
vm.optionList.forEachIndexed { index, optionItemBinding ->
optionItemBinding.layoutChooseArea.click { vm.chooseResult(index) }
optionItemBinding.layoutLottieVoiceArea.click(ivVoiceClick)
optionItemBinding.tvOption.click(null)
optionItemBinding.tvOption.tag = ""
}
}
false -> {
vm.optionList.forEachIndexed { index, optionItemBinding ->
optionItemBinding.root.click { vm.chooseResult(index) }
optionItemBinding.tvOption.click(null)
optionItemBinding.tvOption.tag = ""
}
}
}
} ?: let { //取消事件
vm.optionList.forEachIndexed { index, optionItemBinding ->
optionItemBinding.root.click(null)
optionItemBinding.tvOption.click(ivVoiceClick)
}
}
}
else -> {
examBean?.let {
vm.optionList.forEachIndexed { index, optionItemBinding ->
optionItemBinding.root.click { vm.chooseResult(index) }
}
} ?: let {
vm.optionList.forEachIndexed { index, optionItemBinding ->
optionItemBinding.root.click(null)
}
}
}
}
}
private fun initContentUI(examBean : ExamBean) {
when (vm.intentData.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> initSpellUIData(examBean)
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> initSpokenUIData(examBean)
AppConstants.COURSE_TYPE_ENGLISH_VOICE -> initVoiceUIData(examBean)
else -> initChooseUIData(examBean)
}
}
private fun initChooseUIData(examBean : ExamBean) {
wordChooseBinding.tvWord.text = examBean.word
wordChooseBinding.progressWordCountdownTime.run {
max = vm.currentMaxTime
progress = vm.currentMaxTime
}
vm.optionList.forEachIndexed { index, optionBinding ->
optionBinding.run {
root.setBackgroundResource(vm.normalBg)
tvLable.setTextColor(vm.normalTextColor)
tvOption.setTextColor(vm.normalTextColor)
tvOption.text = vm.currentOption[index]
ivStatu.visibility = View.GONE
}
}
}
private fun initVoiceUIData(examBean : ExamBean) {
wordChooseBinding.tvWord.run {
text = examBean.word
visibility = View.GONE
}
wordChooseBinding.progressWordCountdownTime.visibility = View.GONE
wordChooseBinding.ivVoice.visibility = View.VISIBLE
vm.optionList.forEachIndexed { index, optionBinding ->
optionBinding.run {
root.setBackgroundResource(vm.normalBg)
tvLable.setTextColor(vm.normalTextColor)
tvOption.setTextColor(vm.normalTextColor)
tvOption.text = vm.currentOption[index]
ivStatu.visibility = View.GONE
}
}
}
private fun initSpokenUIData(examBean : ExamBean) {
wordChooseBinding.tvWord.text = examBean.word
if (examBean.questionIsAudio) {
wordChooseBinding.tvWord.visibility = View.GONE
wordChooseBinding.ivVoice.visibility = View.VISIBLE
} else {
wordChooseBinding.tvWord.visibility = View.VISIBLE
wordChooseBinding.ivVoice.visibility = View.GONE
}
vm.optionList.forEachIndexed { index, optionBinding ->
optionBinding.run {
root.setBackgroundResource(vm.normalBg)
tvLable.setTextColor(vm.normalTextColor)
tvOption.setTextColor(vm.normalTextColor)
tvOption.text = vm.currentOption[index]
ivStatu.visibility = View.GONE
if (examBean.answersIsAudio) { //答案为音频
groupVoiceChoose.visibility = View.VISIBLE
tvOption.visibility = View.GONE
ivOption.visibility = View.GONE
} else {
groupVoiceChoose.visibility = View.GONE
tvOption.visibility = View.VISIBLE
ivOption.visibility = View.VISIBLE
}
}
}
}
private fun initSpellUIData(examBean : ExamBean) {
spellBinding.tvExplain.text = examBean.correct //释义
//初始显示word的时候,文字置空,颜色修改为黑色
spellBinding.tvWord.run {
text = ""
setTextColor(ContextCompat.getColor(this@LearnExamActivity, R.color.main_text_color))
}
//进度
wordChooseBinding.progressWordCountdownTime.run {
max = vm.currentMaxTime
progress = vm.currentMaxTime
}
//设置数据
spellAdapter.setData(vm.currentSpellOption)
} }
/**
* 拼写时的点击事件
* @param selectedValue 选择的值
* @param nextPosition 下一个item的位置,拼写未完成时为下一列的位置,拼写完成为错误的第一个位置
* @param isOver 拼写是否完成 为true时则判断为拼写完成
* @param correctValue 正确的拼写值,但未选中的赋值了颜色
* @param errorSize 错误的个数,拼写完成后才会有这个值
*/
private val itemSpellingClick =
{ selectedValue : SpannableStringBuilder, nextPosition : Int, isOver : Boolean, correctValue : SpannableStringBuilder, errorSize : Int ->
when {
isOver -> { //拼写结束
//设置正确的值
spellBinding.tvWord.setTextColor(ContextCompat.getColor(this, R.color.red_1))
spellBinding.tvWord.text = correctValue
//拼写结束
vm.spellOver(selectedValue.toString(), errorSize)
}
else -> {
//设置为选中的值
spellBinding.tvWord.text = selectedValue
}
}
spellBinding.spellRecyclerView.scrollToPosition(nextPosition)
}
/** 点击发音事件 */
private val ivVoiceClick : (v : View) -> Unit = {
}
override fun onBackPressed() {
onBack()
}
/** 返回: 未结束提示弹窗 */ /** 返回: 未结束提示弹窗 */
private fun onBack(){
private fun onBack() {
vm.stopTotalCountTing()
when {
vm.isAllOver -> finish()
else -> CommonDialog.newInstance(CommonDialogBean(titleText = R.string.dialog_test_not_over,
contentText = R.string.dialog_test_not_over_tip,
leftText = R.string.quit,
rightText = R.string.quit)).apply {
onCommonDialogButtonClickListener = { dialog, isRightClick ->
if (!isRightClick){
vm.
}
dialog.dismissAllowingStateLoss()
}
}
}
finish() finish()
} }
/** 测试完成 : 弹窗显示 */
private fun testOver() {
//对话框信息实体
val learnDialogBean = LearnDialogBean(AppConstants.DIALOG_TYPE_EXAM).apply {
examType = vm.intentData.examType
score = vm.scoreValue
correctNumber = vm.correctLiveData.value!!
errorNumber = vm.errorLiveData.value!!
}
LearnDialog.newInstance(learnDialogBean).apply {
onDialogListener = { action, dialog ->
when (action) {
AppConstants.DIALOG_START_LEARN -> { //开始学习
dialog.dismissAllowingStateLoss()
//发动动作 : 继续学习
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).value =
LearnEventData(vm.intentData.subjectId,
vm.intentData.courseId,
AppConstants.ACTION_COURSE_TEST_START_LEARN)
finish()
}
}
}
}.show(supportFragmentManager, javaClass.name)
}
} }

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

package com.xkl.cdl.module.learn package com.xkl.cdl.module.learn


import com.suliang.common.base.viewmodel.BaseViewModel
import android.graphics.drawable.Drawable
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.suliang.common.eventbus.LiveDataBus
import com.suliang.common.extension.createRandomNewChar
import com.suliang.common.extension.diskIo2Main
import com.suliang.common.util.DateUtil
import com.suliang.common.util.LogUtil
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.DataTransferHolder import com.xkl.cdl.data.DataTransferHolder
import com.xkl.cdl.data.bean.course.DialogueSpokenItem
import com.xkl.cdl.data.bean.SpellItemBean
import com.xkl.cdl.data.bean.course.ExamBean import com.xkl.cdl.data.bean.course.ExamBean
import com.xkl.cdl.data.bean.intentdata.ExamData import com.xkl.cdl.data.bean.intentdata.ExamData
import com.xkl.cdl.data.event.LearnEventData
import com.xkl.cdl.databinding.IncludTestOptionItemBinding
import io.reactivex.rxjava3.core.Observable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import mqComsumerV1.Struct.*
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject


class LearnExamViewModel : BaseViewModel() {
class LearnExamViewModel : LearnBaseViewModel() {
//默认发音方式
var defaultSoundWay : Int = 0
//传递过来的测试数据 //传递过来的测试数据
val intentData : ExamData = DataTransferHolder.instance.getData() val intentData : ExamData = DataTransferHolder.instance.getData()
//测试题数据 //测试题数据
val testData : List<ExamBean> by lazy { val testData : List<ExamBean> by lazy {
intentData.testData!! intentData.testData!!
} }
//口语测试题
val allSentencesList : List<List<DialogueSpokenItem>> by lazy {
intentData.allSentencesList!!
}
//当前测试题下标, 默认为-1,进度设置需要 + 1 ,以数字显示为当前正在做的题数,而非已完成数 //当前测试题下标, 默认为-1,进度设置需要 + 1 ,以数字显示为当前正在做的题数,而非已完成数
var currentIndex = -1 var currentIndex = -1
//错误数
val errorLiveData = MutableLiveData<Int>(0)
//正确数
val correctLiveData = MutableLiveData<Int>(0)
//未答数
var unAnswerNumber = 0
/** 当前需要显示的题 */
val currentExamBean = MutableLiveData<ExamBean?>()
//获取下一题时的间隔时间,初始为0秒
var toNextTime = 0L
//当前最大进度值
var currentMaxTime = 0
//当前剩余进度时间
var currentSuplusTime = MutableLiveData<Int>(0)
//单个计时的间隔时间
val currentCountingIntervalTime = 20L
//当前四选一选项
var currentOption = mutableListOf<String>()
//当前四选一正确选项
var currentCorrectPosition = 0
//当前四选一的点击项
var currentClickPosition = -1
//当前四选一结果完成
val currentChooseResultLiveData = MutableLiveData<Long>()
//当前拼写选项
var currentSpellOption = mutableListOf<SpellItemBean>()
//四个选项布局view
val optionList = mutableListOf<IncludTestOptionItemBinding>()
//当前拼写结果
val currentSpellResultLiveData = MutableLiveData<Long>()
//正常、正确、错误 文本和背景颜色
var normalTextColor : Int = 0
var correctTextColor : Int = 0
var errorTextColor : Int = 0
var normalBg : Int = 0
var correctBg : Int = 0
var errorBg : Int = 0
lateinit var correctIv : Drawable
lateinit var errorIv : Drawable
var scoreValue = 0 //分数/词汇数
var vocabularyCoverange = 0 //词汇量测试的覆盖率
//测试结果试卷
private var learnExam : LearnExam.Builder? = null
//当前测试的单词
private var currentExamRecord : ExamRecord.Builder? = null
//最终测试题集合,用于上传
private val mExamRecordList : MutableList<ExamRecord> = mutableListOf()
//错误集合
private var mLearnEntities : MutableList<LearnEntity> = mutableListOf()
//学前总测试与章节前测试时新的学习错误集合
var newErrorMap : HashMap<String, Boolean> = hashMapOf()
//标记是否获取下一题
var isGetNextIng = false
//标记是否为当前错误,暂停获取下一题
var isErrorPauseToNext = false
//标记当前是否倒计时中
var isCurrentCounting = false
//标记当前是否是否返回后台
private var isInBackground = false
//标记是否显示返回弹窗 : 停止计时
private var isShowBackDialog = false
/** 获取下一题数据 */
fun loadNext() {
isGetNextIng = true
mHandler.postDelayed(toNextRunable, toNextTime)
}
/** 停止获取下一题 */
fun pauseToNext() {
isErrorPauseToNext = true
mHandler.removeCallbacks(toNextRunable)
}
/** 继续获取下一题 */
fun continueToNext() {
isErrorPauseToNext = false
isGetNextIng = true
mHandler.post(toNextRunable)
}
/**
* 显示或关闭的返回弹窗: 影响总计时和当前的一些计时
* @param isShow Boolean
*/
fun showOrDissmissBackDialogForTime(isShow: Boolean){
isShowBackDialog = isShow
when{
isShow -> { //显示返回弹窗
//关闭总计时
stopTotalCountTing()
//停止单题的倒计时
mHandler.removeCallbacks(currentCountingTimeRunable)
}
else -> { //关闭返回弹窗
//开启总计时
startTotalCounting()
when{
!isErrorPauseToNext ->{ // 暂停标志,不处理单题,非暂停
when {
isGetNextIng -> { //是否获取下一题中
//本来该获取下一题,现在获取下一题
continueToNext()
}
else -> { //不是,进行当前题的计时
mHandler.post(currentCountingTimeRunable) //现在恢复单题的计时
}
}
}
}
}
}
}
override fun onResume(owner : LifecycleOwner) {
super.onResume(owner)
when{
!isShowBackDialog -> { //没有显示返回弹窗,判断是否完成测试,没有完成,则进行计时,需要注意是否第一次进入,避免对第一题进行重复计时
}
}
}
override fun onPause(owner : LifecycleOwner) {
super.onPause(owner)
//关闭所有计时,如果弹窗显示,就只需要关闭总计时就可以了,否则关闭所有计时
}
// /**
// * 暂停或恢复计时
// * @param isPause Boolean
// */
// fun runAllTime(isPause:Boolean){
// if (isAllOver) return
// this.isPause = isPause
// when{
// isPause || isShowBackDialog -> { //暂停计时
// //停止总计时
// stopTotalCountTing()
// //移除下一条、单条计时
// mHandler.removeCallbacksAndMessages(null)
// }
// else -> { //运行计时
// //恢复总计时
// startTotalCounting()
// //错误暂停不处理,只进行总计时,否则进行是否在获取下一题的判断
// when {
// isPause -> when { //生命周期暂停后,需要恢复
// !isErrorPauseToNext -> when {
// isGetNextIng -> continueToNext() //本来该获取下一题,现在获取下一题
// else -> mHandler.post(currentCountingTimeRunable) //现在恢复单题的计时
// }
// }
// }
// }
// }
// }
/** 获取下一题的线程,同时判断是否完成 */
private val toNextRunable = object : Runnable {
override fun run() {
isGetNextIng = false
currentIndex++
if (currentIndex >= testData.size) {
// TODO: 2022/4/11 标记学习已完成 停止计时 计算分数 设置总消费时间
if (isAllOver) return
isAllOver = true
calculateScore()
//封装数据
packUpRecord()
//保存数据 后再使用 currentExamBean.value = null 都在saveData中
saveData()
} else {
val nextExam = testData[currentIndex]
when (intentData.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> {
initSpellOption(nextExam)
currentMaxTime =
Math.max(nextExam.word.length * 2 * AppConstants.SPELL_TEST_MIN_TIME, AppConstants.TEST_MIN_TIME)
currentSuplusTime.value = currentMaxTime
}
else -> {
initChooseOption(nextExam)
currentMaxTime = if (intentData.courseType == AppConstants.COURSE_TYPE_ENGLISH_VOICE) 0 else Math.max(
AppConstants.TEST_MIN_TIME,
(nextExam.word.length + nextExam.correct.length) * 200)
LogUtil.e("------currentMaxTime = $currentMaxTime")
currentSuplusTime.value = currentMaxTime
}
}
currentExamBean.value = nextExam
}
}
}
/** 初始化四选一选选项 */
private fun initChooseOption(examBean : ExamBean) {
currentOption.clear()
currentOption.add(examBean.correct)
currentOption.add(examBean.error1)
currentOption.add(examBean.error2)
currentOption.add(examBean.error3)
currentCorrectPosition = 0
for (i in 4 downTo 1) {
val currentPosition = i - 1
val swapPosition = (0 until i).random()
if (currentPosition != swapPosition) {
currentOption[currentPosition] = currentOption.set(swapPosition, currentOption[currentPosition])
if (currentCorrectPosition == swapPosition) {
currentCorrectPosition = currentPosition
}
}
}
}
/** 初始化拼写选项集合 */
private fun initSpellOption(examBean : ExamBean) {
currentSpellOption.clear()
examBean.word.forEachIndexed { index, c ->
val newChar = c.createRandomNewChar()
if ((0 .. 1).random() == 0) {
currentSpellOption.add(SpellItemBean(c, true))
currentSpellOption.add(SpellItemBean(newChar, false))
} else {
currentSpellOption.add(SpellItemBean(newChar, false))
currentSpellOption.add(SpellItemBean(c, true))
}
}
}
/** 当前单词的计时 */
private val currentCountingTimeRunable = object : Runnable {
override fun run() {
when (intentData.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_VOICE, AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { // 累加时间
currentMaxTime += currentCountingIntervalTime.toInt()
mHandler.postDelayed(this, currentCountingIntervalTime)
}
else -> { //倒计时
val surplusTime = currentSuplusTime.value!! - currentCountingIntervalTime
currentSuplusTime.value = surplusTime.toInt()
if (surplusTime > 0) {
mHandler.postDelayed(this, currentCountingIntervalTime)
} else {
chooseResult(-1) //-1为未答
}
}
}
}
}
/** 当前单词开始计时 */
fun startCurrentCountingTime() {
isCurrentCounting = true
mHandler.postDelayed(currentCountingTimeRunable, currentCountingIntervalTime)
}
/** 移除单词计时 */
fun stopCurrentCountingTime() {
isCurrentCounting = false
mHandler.removeCallbacks(currentCountingTimeRunable)
}
/**
* 四选一结果
* @param position Int 位置 -1..3 ,-1 为未答
*/
fun chooseResult(position : Int) {
//移除计时
mHandler.removeCallbacks(currentCountingTimeRunable)
//创建单题的测试记录
currentExamRecord = ExamRecord.newBuilder().apply {
questionId = currentExamBean.value!!.word_id
questionType = currentExamBean.value!!.type
userAnswer = when (position) { //答案选项
0 -> "A"
1 -> "B"
2 -> "C"
3 -> "D"
else -> ""
}
duration = when (intentData.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_ENGLISH_VOICE -> currentMaxTime.toLong()
else -> currentMaxTime.toLong() - currentSuplusTime.value!!
}
}
//问题和选项
createQuestion()
currentClickPosition = position
var chooseResult = -1L
//添加到记录集合
mExamRecordList.add(currentExamRecord!!.build())
//进行结果判断,并为记录赋值
if (position == currentCorrectPosition) { //正确
correctLiveData.value = correctLiveData.value!!.plus(1)
currentExamRecord!!.answerStatus = AppConstants.TEST_CORRECT
chooseResult = AppConstants.TEST_CORRECT
toNextTime = AppConstants.TEST_TO_NEXT_CORRECT_TIME
} else {
errorLiveData.value = errorLiveData.value!!.plus(1) //错误
currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR
chooseResult = AppConstants.TEST_ERROR
toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME
//未答
if (position == -1) {
unAnswerNumber += 1
currentExamRecord!!.answerStatus = AppConstants.TEST_UN_ANSWER
chooseResult = AppConstants.TEST_UN_ANSWER
}
//创建错误记录
createErrorRecord()
}
currentChooseResultLiveData.value = chooseResult
}
/** 拼写结束
* @param selectedValue 用户拼写的值,如果为空,则为未答
* @param errorSize 错误数
* */
fun spellOver(selectedValue : String, errorSize : Int) {
//移除计时
mHandler.removeCallbacks(currentCountingTimeRunable)
//创建测试记录
currentExamRecord = ExamRecord.newBuilder().apply {
questionId = currentExamBean.value!!.word_id
questionType = currentExamBean.value!!.type
userAnswer = selectedValue
duration = currentMaxTime.toLong() - currentSuplusTime.value!!
}
createQuestionSpell()
val result : Long
when {
selectedValue.isEmpty() -> { //未答
errorLiveData.value = errorLiveData.value!!.plus(1) //错误
currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR
toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME
unAnswerNumber += 1
currentExamRecord!!.answerStatus = AppConstants.TEST_UN_ANSWER
result = AppConstants.TEST_UN_ANSWER
}
errorSize > 0 -> { //错误
errorLiveData.value = errorLiveData.value!!.plus(1) //错误
currentExamRecord!!.answerStatus = AppConstants.TEST_ERROR
toNextTime = AppConstants.TEST_TO_NEXT_ERROR_TIME
result = AppConstants.TEST_TO_NEXT_ERROR_TIME
}
else -> { //正确
correctLiveData.value = correctLiveData.value!!.plus(1)
currentExamRecord!!.answerStatus = AppConstants.TEST_CORRECT
toNextTime = AppConstants.TEST_CORRECT
result = AppConstants.TEST_CORRECT
}
}
//通知结果
currentSpellResultLiveData.value = result
}
/** 创建错误记录 并将错误记录加入错误集合
* 学前、学前总的数据记录进入学习的课时中,口语学前总不需要记录错误
* */
fun createErrorRecord() {
if (intentData.examType == AppConstants.TEST_TYPE_BEFORE || (intentData.examType == AppConstants.TEST_TYPE_BEFORE_TOTAL && intentData.courseType != AppConstants.COURSE_TYPE_ENGLISH_SPOKEN)) {
val builder = LearnEntity.newBuilder().apply {
projectId = intentData.subjectId.toLong()
packId = intentData.coursePackId
courseId = intentData.courseId
currentExamBean.value!!.let {
chapterId = it.chapterId
lessonId = it.lessonId
entityId = it.word_id
tag = "android"
isFromExam = true
isError = true
created = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1)
lastReviewDate = created
}
}
//这个错误单词是否为新的错误或者是就的错,根据key判断错误集合中是否有此单词
val key = "${builder.chapterId}_${builder.lessonId}_${builder.entityId}"
if (intentData.mExamWRMap!!.contains(key)) {
//错误再错误,则恢复复习此数为1
builder.reviewNum = 1
} else { //相当于新错
if (!newErrorMap.containsKey(key)) {
newErrorMap[key] = true
//课时前错误 ,则更新课时错误数量
if (intentData.examType == AppConstants.TEST_TYPE_BEFORE) {
intentData.lesson!!.errorNumber = intentData.lesson!!.errorNumber + 1
}
}
}
mLearnEntities.add(builder.build())
}
}
/**
* 计算测试分数
*/
private fun calculateScore() {
when (intentData.examType) {
//词汇量测试
AppConstants.TEST_TYPE_NORMAL -> {
val calculateCorrectNumber : Double = correctLiveData.value!! + unAnswerNumber * 0.25
//测试是否成功,大于35题才算成功
val isVocablularySuccess = calculateCorrectNumber > 35
scoreValue = 0 //词汇数
vocabularyCoverange = 0 //覆盖率
//分数 与 覆盖率 只有测试成功才进行计算
if (isVocablularySuccess) {
val converange = (calculateCorrectNumber - 25) / 75 //被测试题的占比重
// TODO: 2022/4/14 16000应该为该词汇量测试对应的词汇数
scoreValue = (16000 * converange).toInt()
vocabularyCoverange = (converange * 100).toInt() //测试类型的覆盖率
}
}
//其他测试
else -> {
val calculateCorrectNumber : Double = correctLiveData.value!! + unAnswerNumber * 0.25
val scoreValue = ((calculateCorrectNumber - testData.size * 0.25) / (testData.size * 0.25) * 100).toInt()
this.scoreValue = when {
scoreValue < 0 -> 0
scoreValue > 100 -> 100
else -> scoreValue
}
}
}
}
//创建四选一的问题和选项
fun createQuestion() {
try {
val jsonObject = JSONObject()
jsonObject.put("question_describe", currentExamBean.value!!.word)
val array = JSONArray()
val a = JSONObject()
a.put("text", currentOption[0])
a.put("isCorrect", currentCorrectPosition == 0)
val b = JSONObject()
b.put("text", currentOption[1])
b.put("isCorrect", currentCorrectPosition == 1)
val c = JSONObject()
c.put("text", currentOption[2])
c.put("isCorrect", currentCorrectPosition == 2)
val d = JSONObject()
d.put("text", currentOption[3])
d.put("isCorrect", currentCorrectPosition == 3)
array.put(a)
array.put(b)
array.put(c)
array.put(d)
jsonObject.put("sequence", array)
currentExamRecord!!.question = jsonObject.toString()
} catch (e : JSONException) {
e.printStackTrace()
}
}
//创建当前问题的Question
fun createQuestionSpell() {
try {
val jsonObject = JSONObject()
jsonObject.put("question_describe", currentExamBean.value!!.correct)
val array = JSONArray()
val a = JSONObject()
a.put("text", currentExamBean.value!!.word)
a.put("isCorrect", false)
array.put(a)
jsonObject.put("sequence", array)
currentExamRecord!!.question = jsonObject.toString()
} catch (e : JSONException) {
e.printStackTrace()
}
}
/** 封装试卷 */
private fun packUpRecord() : Record.Builder {
val examType = intentData.examType
//生成试卷
learnExam = LearnExam.newBuilder().apply {
projectId = intentData.subjectId.toLong()
packId = intentData.coursePackId
courseId = intentData.courseId
chapterId = intentData.lesson?.chapterId ?: 0 //章节id,章节测试必传
lessonId = intentData.lesson?.lessonId ?: 0 //课时id
if (examType == AppConstants.TEST_TYPE_COMPOSITION) { //作文类型,作文必传
typeId = intentData.courseType.toLong()
}
title = intentData.saveTitle //测试试卷名称
type = examType.toLong() //测试类型
score = scoreValue.toFloat() //成绩(如果为词汇量测试则为词汇数)
totalNum = testData.size.toLong() //题目总数
correctNum = correctLiveData.value!!.toLong() //正确题目数
errorNum = errorLiveData.value!!.toLong() //错误题目数
unanswerNum = unAnswerNumber.toLong() //未答题目数
duration = totalUseTime.value!! //测试所用时间(单位为毫秒)
tag = "android"
created = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1) //数据创建时间
//courseType
val ext = "{\"courseType\": ${intentData.courseType} }"
//词汇量测试: 覆盖率、阶段
if (examType == AppConstants.TEST_TYPE_NORMAL) {
// TODO: 2022/4/14 设置词汇量测试的范围题目和覆盖率
stage = "小学"
coverRate = vocabularyCoverange.toFloat()
}
//添加测试题
addAllRecord(mExamRecordList)
}
// //是否完成
// if (examType == SyncStateContract.Constants.TEST_TYPE_AFTER_TOTAL && mData.getScore() >= SyncStateContract.Constants.SCORE_1 && !mData.isCourseFinished()) {
// learnExam.setIsFinished(true)
// }
//最终上传数据
val record = Record.newBuilder() //上传对象
record.addExam(learnExam) //测试试卷
//时间
if (examType == AppConstants.TEST_TYPE_BEFORE_TOTAL || examType == AppConstants.TEST_TYPE_BEFORE || examType == AppConstants.TEST_TYPE_AFTER || examType == AppConstants.TEST_TYPE_AFTER_TOTAL) {
record.addDuration(saveCurrentLearnDuration())
}
if (mLearnEntities.size != 0) {
record.addAllEntity(mLearnEntities)
}
return record
}
/**
* 保存当前的学习时长
*/
private fun saveCurrentLearnDuration() : LearnDuration.Builder {
return LearnDuration.newBuilder().apply {
projectId = intentData.subjectId.toLong()
packId = intentData.coursePackId
courseId = intentData.courseId
created = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1)
tag = "android"
timeFrame = DateUtil.getTimeFrame(System.currentTimeMillis()) //设置时段
isReview = false //是否复习时长
duration = totalUseTime.value!! //时长,毫秒
totalDuration = totalUseTime.value!! //总时间,包含空闲部分
categoryId = intentData.courseType.toLong()
}
}
/** 保存处理数据 */
private fun saveData() {
showHideLoading(true)
Observable.create<Boolean> {
viewModelScope.launch {
delay(2000)
it.onNext(true)
}
// TODO: 2022/4/14 传递保存record信息
}.compose(diskIo2Main()).subscribe({
showHideLoading(false)
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).value = LearnEventData(
intentData.subjectId,
intentData.courseId,
AppConstants.DATA_COURSE_TEST_BEFORE).apply {
this.scoreValue = this@LearnExamViewModel.scoreValue
this.newErrorMap = this@LearnExamViewModel.newErrorMap
}
currentExamBean.value = null
}, {
showHideLoading(false)
it.printStackTrace()
})
}
} }

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

import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.suliang.common.AppConfig import com.suliang.common.AppConfig
import com.suliang.common.base.fragment.BaseFragmentVM import com.suliang.common.base.fragment.BaseFragmentVM
import com.suliang.common.eventbus.LiveDataBus
import com.suliang.common.extension.replaceFragment import com.suliang.common.extension.replaceFragment
import com.suliang.common.util.LogUtil import com.suliang.common.util.LogUtil
import com.xkl.cdl.R import com.xkl.cdl.R
import com.xkl.cdl.data.AppConstants import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.DataTransferHolder import com.xkl.cdl.data.DataTransferHolder
import com.xkl.cdl.data.bean.intentdata.ExamData import com.xkl.cdl.data.bean.intentdata.ExamData
import com.xkl.cdl.data.event.LearnEventData
import com.xkl.cdl.data.manager.db.DbControlBase import com.xkl.cdl.data.manager.db.DbControlBase
import com.xkl.cdl.databinding.FragmentCourseMainBinding import com.xkl.cdl.databinding.FragmentCourseMainBinding
import com.xkl.cdl.module.learn.LearnExamActivity import com.xkl.cdl.module.learn.LearnExamActivity
* 用于加载该课程的多种状态: 复习、章节目录、总测试 * 用于加载该课程的多种状态: 复习、章节目录、总测试
*/ */
class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainFragmentViewModel>() { class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainFragmentViewModel>() {
companion object { companion object {
@JvmStatic @JvmStatic
fun newInstance(courseIndex:Int) = CourseMainFragment().apply {
fun newInstance(courseIndex : Int) = CourseMainFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putInt(AppConfig.INTENT_1,courseIndex)
putInt(AppConfig.INTENT_1, courseIndex)
} }
} }
} }

override fun initViewModel(): CourseMainFragmentViewModel {
return ViewModelProvider(this,ViewModelFactory(requireArguments().getInt(AppConfig.INTENT_1)))[CourseMainFragmentViewModel::class.java].apply {
override fun initViewModel() : CourseMainFragmentViewModel {
return ViewModelProvider(this,
ViewModelFactory(requireArguments().getInt(AppConfig.INTENT_1)))[CourseMainFragmentViewModel::class.java].apply {
coursePackMainActivityVM = ViewModelProvider(requireActivity())[CoursePackMainActivityViewModel::class.java] coursePackMainActivityVM = ViewModelProvider(requireActivity())[CoursePackMainActivityViewModel::class.java]
LogUtil.e("CourseMainFragment coursePackMainActivityVM hashCode -> ${coursePackMainActivityVM.hashCode()}") LogUtil.e("CourseMainFragment coursePackMainActivityVM hashCode -> ${coursePackMainActivityVM.hashCode()}")
} }
} }
override fun initFragment() { override fun initFragment() {
//设置课程 和 需要操作的类型 //设置课程 和 需要操作的类型
vm.course = vm.coursePackMainActivityVM.coursePack.childrenCourses[vm.courseIndex].apply { vm.course = vm.coursePackMainActivityVM.coursePack.childrenCourses[vm.courseIndex].apply {
vm.dbControlBase = DbControlBase(subjectId,coursePackId,courseId,courseType)
vm.dbControlBase = DbControlBase(subjectId, coursePackId, courseId, courseType)
} }

//监听动作 总测完成,切换到目录页
LiveDataBus.with<LearnEventData>(AppConstants.EVENT_COURSE).observe(this) {
if (it.subjectId != vm.course.subjectId || it.courseId != vm.course.courseId) return@observe
when (it.actionFlag) {
// 学前总测、学后总测 之继续学习 -> 切换到目录页
AppConstants.ACTION_COURSE_TEST_START_LEARN -> changeFragment(1)
//学前总测结束,传递数据回来更新数据
AppConstants.DATA_COURSE_TEST_BEFORE -> {
vm.courseDetail.st_before = it.scoreValue.toDouble() //学前总分
it.newErrorMap?.run {
when {
this.size > 0 -> {
//新错误加入错误列表集合
vm.courseDetail.exam_w_r_list.putAll(this)
// 如果有新错误则需更新列表数据
vm.modifyLessonErrorNumber()
}
}
}
}
}
}
} }

override fun loadData() { override fun loadData() {
LogUtil.e("CourseMainFragment 加载数据 --》 ${vm.courseIndex}") LogUtil.e("CourseMainFragment 加载数据 --》 ${vm.courseIndex}")

vm.loadMain().observe(this){
vm.loadMain().observe(this) {
changeChildrenFragment() changeChildrenFragment()
} }
} }


/** /**
* 改变加载的子Fragment * 改变加载的子Fragment
*/ */
private fun changeChildrenFragment(){
private fun changeChildrenFragment() {


// //加载复习 // //加载复习
// replaceFragment(R.id.layout_root, CourseReviewFragment.newInstance()) // replaceFragment(R.id.layout_root, CourseReviewFragment.newInstance())
//学后总测 //学后总测
// replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) // replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL))
//学习目录 //学习目录
vm.courseDetail.run { vm.courseDetail.run {
if (st_before == AppConstants.NOT_DOING) { //学前总测未做 if (st_before == AppConstants.NOT_DOING) { //学前总测未做
replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_BEFORE_TOTAL)) replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_BEFORE_TOTAL))
* 供其他地方手动调用切换界面 * 供其他地方手动调用切换界面
* @param position Int 1目录页 2学后总测 * @param position Int 1目录页 2学后总测
*/ */
fun changeFragment(position: Int){
when(position){
fun changeFragment(position : Int) {
when (position) {
1 -> replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance()) 1 -> replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance())
2 -> replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL))
2 -> replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL))
} }
} }
/** 跳转测试 */ /** 跳转测试 */
fun startExam(examData : ExamData){
fun startExam(examData : ExamData) {
DataTransferHolder.instance.putData(value = examData) DataTransferHolder.instance.putData(value = examData)
LearnExamActivity.newInstance(requireContext()) LearnExamActivity.newInstance(requireContext())
} }



inner class ViewModelFactory(private val courseIndex: Int) : ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
inner class ViewModelFactory(private val courseIndex : Int) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass : Class<T>) : T {
return CourseMainFragmentViewModel(courseIndex) as T return CourseMainFragmentViewModel(courseIndex) as T
} }
} }
} }

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

import io.reactivex.rxjava3.core.Observer import io.reactivex.rxjava3.core.Observer
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import java.util.* import java.util.*
import kotlin.collections.HashMap


/** /**
* author suliang * author suliang
return result return result
} }
/**
* 更新列表数据错误数,学前总测结束才进行修改
*/
fun modifyLessonErrorNumber() {
//重设detail中的错误数, exam_w_r_list为所有的测试前错误,因为时学前总测,所以,可以根据这个字段来计算每个课时的错误数
//记录错误数: key :chapterId_lessonId value :错误数
val errorNumber = hashMapOf<String,Int>()
courseDetail.exam_w_r_list.keys.forEach {
val split = it.split("_")
if (split.size == 3){
val errorKey = "${split[0]}_${split[1]}"
errorNumber[errorKey] = errorNumber.getOrElse(errorKey,{0}) + 1
}
}
courseDetail.wrong.putAll(errorNumber)
//更新lesson中的错误数
allLesson.forEach {
val key = "${it.chapterId}_${it.lessonId}"
it.errorNumber = errorNumber.getOrElse(key,{0})
}
}
} }

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

package com.xkl.cdl.module.m_center_learn.coursechildren package com.xkl.cdl.module.m_center_learn.coursechildren


import android.os.Bundle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.suliang.common.AppConfig
import com.suliang.common.base.fragment.BaseFragmentVM import com.suliang.common.base.fragment.BaseFragmentVM
import com.suliang.common.extension.click import com.suliang.common.extension.click
import com.xkl.cdl.data.AppConstants import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.databinding.FragmentCourseLessonBinding
import com.xkl.cdl.databinding.FragmentCourseReviewBinding import com.xkl.cdl.databinding.FragmentCourseReviewBinding


/** /**

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

import com.suliang.common.extension.setHtml import com.suliang.common.extension.setHtml
import com.xkl.cdl.R import com.xkl.cdl.R
import com.xkl.cdl.data.AppConstants import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.DataTransferHolder
import com.xkl.cdl.data.bean.course.ExamBean import com.xkl.cdl.data.bean.course.ExamBean
import com.xkl.cdl.data.bean.intentdata.ExamData import com.xkl.cdl.data.bean.intentdata.ExamData
import com.xkl.cdl.data.manager.CourseManager import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.databinding.FragmentCourseTotalTestBinding import com.xkl.cdl.databinding.FragmentCourseTotalTestBinding
import com.xkl.cdl.module.learn.LearnExamActivity


/** /**
* 课程总测试: 学前总,学后总 * 课程总测试: 学前总,学后总
private fun startTest(view: View) { private fun startTest(view: View) {
//生成数据 //生成数据
val examData = ExamData(vm.course.subjectId, totalTestType, vm.course.courseTitle, vm.course.courseTitle).apply { val examData = ExamData(vm.course.subjectId, totalTestType, vm.course.courseTitle, vm.course.courseTitle).apply {
coursePackId = vm.course.coursePackId
coursePackType = vm.course.coursePackType coursePackType = vm.course.coursePackType
courseId = vm.course.courseId
courseType = vm.course.courseType courseType = vm.course.courseType
this.testData = this@CourseTotalTestFragment.testData this.testData = this@CourseTotalTestFragment.testData
mExamWRMap = if (examType == AppConstants.TEST_TYPE_AFTER_TOTAL) vm.courseDetail.exam_w_r_list else null
mExamWRMap = if (examType == AppConstants.TEST_TYPE_BEFORE_TOTAL) vm.courseDetail.exam_w_r_list else null
} }
(parentFragment as CourseMainFragment).startExam(examData) (parentFragment as CourseMainFragment).startExam(examData)
} }

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

// show(supportFragmentManager,javaClass.name) // show(supportFragmentManager,javaClass.name)
// } // }
// LogUtil.e("Dialog -- > ${learnDialog.hashCode()}") // LogUtil.e("Dialog -- > ${learnDialog.hashCode()}")
SpUtils.instance.encode("my","abcdefgxxxxx")
SpUtils.instance.remove("my")
println(SpUtils.instance.decode("my",String::class.java))
if (true) return
// SpUtils.instance.encode("my","abcdefgxxxxx")
// SpUtils.instance.remove("my")
// println("---------------" + SpUtils.instance.decode("my",String::class.java))
//
// if (true) return




showHideLoading(true) showHideLoading(true)

+ 11989
- 0
app/src/main/java/mqComsumerV1/Struct.java
File diff suppressed because it is too large
View File


+ 5
- 0
app/src/main/res/drawable/shape_rounder_8_stroke_green1.xml View File

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

+ 5
- 0
app/src/main/res/drawable/shape_rounder_8_stroke_red1.xml View File

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

+ 2
- 1
app/src/main/res/layout/activity_learn_exam.xml View File

app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginBottom="40dp" android:layout_marginBottom="40dp"
app:cornerRadius="8dp" app:cornerRadius="8dp"
android:backgroundTint="@color/gray_4" />
android:backgroundTint="@color/gray_4"
android:visibility="gone"/>


</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

+ 106
- 0
app/src/main/res/layout/dialog_common.xml View File

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

+ 25
- 5
app/src/main/res/layout/dialog_lesson_learn.xml View File

app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_tip" app:layout_constraintTop_toBottomOf="@+id/tv_tip"
tools:text="课时学前测试" />
tools:text="课时学前测试"
android:visibility="gone"
tools:visibility="visible"/>


<TextView <TextView
android:id="@+id/tv_lesson_name" android:id="@+id/tv_lesson_name"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title" app:layout_constraintTop_toBottomOf="@+id/tv_title"
tools:text="[%s%s]"/>
tools:text="[%s%s]"
android:visibility="gone"
tools:visibility="visible"/>


<TextView <TextView
android:id="@+id/tv_count_time" android:id="@+id/tv_count_time"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_lesson_name" app:layout_constraintTop_toBottomOf="@+id/tv_lesson_name"
tools:text="@string/test_count_time_format"/>
tools:text="@string/test_count_time_format"
android:visibility="gone"
tools:visibility="visible"/>


<TextView <TextView
android:id="@+id/tv_tip_1" android:id="@+id/tv_tip_1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_tip_1" app:layout_constraintTop_toBottomOf="@+id/tv_tip_1"
android:visibility="gone"/>
android:visibility="gone"
tools:visibility="visible"/>






app:layout_constraintStart_toEndOf="@+id/tv_left" app:layout_constraintStart_toEndOf="@+id/tv_left"
android:background="@color/gray_3" android:background="@color/gray_3"
app:layout_constraintTop_toTopOf="@+id/tv_right" app:layout_constraintTop_toTopOf="@+id/tv_right"
app:layout_constraintBottom_toBottomOf="@+id/tv_right" />
app:layout_constraintBottom_toBottomOf="@+id/tv_right"
android:visibility="gone"
tools:visibility="visible"/>


<!--左边按钮--> <!--左边按钮-->
<TextView <TextView
app:layout_constraintTop_toTopOf="@+id/vSplit" app:layout_constraintTop_toTopOf="@+id/vSplit"
app:layout_constraintBottom_toBottomOf="@+id/vSplit" app:layout_constraintBottom_toBottomOf="@+id/vSplit"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"
/>

<!--学前总测试控制显示的布局id-->
<androidx.constraintlayout.widget.Group
android:id="@+id/group_total_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="tv_score,tv_tip_1,tv_title,inc_statistics_number"
/> />


</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
/> />






</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

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

style="@android:style/Widget.ProgressBar.Horizontal" style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="4dp" android:layout_height="4dp"
android:layout_marginLeft="88dp"
android:layout_marginStart="88dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginRight="87dp"
android:layout_marginEnd="88dp"
android:progressDrawable="@drawable/progressbar_countdown_time" android:progressDrawable="@drawable/progressbar_countdown_time"
app:layout_constraintTop_toBottomOf="@+id/tv_explain" app:layout_constraintTop_toBottomOf="@+id/tv_explain"
tools:progress="20" /> tools:progress="20" />

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

app:lottie_repeatMode="restart" app:lottie_repeatMode="restart"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>


<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_word" android:id="@+id/tv_word"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:gravity="center" android:gravity="center"
tools:text="gradual" tools:text="gradual"
tools:textColor="@color/num0"
android:textColor="@color/num0"
android:textSize="@dimen/size_max_word" android:textSize="@dimen/size_max_word"
app:autoSizeMaxTextSize="@dimen/size_max_word" app:autoSizeMaxTextSize="@dimen/size_max_word"
app:autoSizeMinTextSize="@dimen/miniSize" app:autoSizeMinTextSize="@dimen/miniSize"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />


<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="tv_word,iv_voice"/>

<!--单词测试时间-->
<ProgressBar
android:id="@+id/progress_word_countdown_time"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_marginStart="88dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="88dp"
android:progressDrawable="@drawable/progressbar_countdown_time"
app:layout_constraintTop_toBottomOf="@+id/barrier"
tools:progress="20"/>

<TextView <TextView
android:id="@+id/tv_rule_tip" android:id="@+id/tv_rule_tip"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:textSize="@dimen/smallerSize" android:textSize="@dimen/smallerSize"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_word" />
app:layout_constraintTop_toBottomOf="@+id/progress_word_countdown_time" />


<include <include
android:id="@+id/inc_a" android:id="@+id/inc_a"

+ 1
- 2
app/src/main/res/layout/inc_learn_test_statistic.xml View File

android:layout_marginRight="@dimen/global_spacing" android:layout_marginRight="@dimen/global_spacing"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:singleLine="true" android:singleLine="true"
android:maxEms="6"
android:ellipsize="end" android:ellipsize="end"
/> />


app:layout_constraintLeft_toRightOf="@+id/tv_test_type_name" app:layout_constraintLeft_toRightOf="@+id/tv_test_type_name"
app:layout_constraintTop_toBottomOf="@+id/progress_test_number" app:layout_constraintTop_toBottomOf="@+id/progress_test_number"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="@dimen/global_spacing"/>
android:layout_marginStart="@dimen/global_spacing" />




</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

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

android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:constraint_referenced_ids="layout_choose_area,layout_lottie_voice_area,guide_line" app:constraint_referenced_ids="layout_choose_area,layout_lottie_voice_area,guide_line"
android:visibility="gone"/>
android:visibility="gone"
tools:visibility="visible"/>




<TextView <TextView
app:layout_constraintRight_toLeftOf="@id/iv_statu" app:layout_constraintRight_toLeftOf="@id/iv_statu"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginRight="36dp" app:layout_goneMarginRight="36dp"
tools:text="喜好;爱好;类似的人(或物);(尤指被视为和某人或某事物一样好的)种类,类型 adj.类似的;相似的 adv.大概,可能;" />
tools:text="喜好" />


<ImageView <ImageView
android:id="@+id/iv_statu" android:id="@+id/iv_statu"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_right" tools:src="@drawable/ic_right"
app:tint="@color/green_1"/>
/>




</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

+ 16
- 0
app/src/main/res/layout/item_spell_single_word.xml View File

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

+ 6
- 0
app/src/main/res/values/strings.xml View File

<string name="start_learn">开始学习</string> <string name="start_learn">开始学习</string>
<string name="test_spell_tip">请从左至右依次点击选择正确的选项</string> <string name="test_spell_tip">请从左至右依次点击选择正确的选项</string>
<string name="pause">暂停</string> <string name="pause">暂停</string>
<string name="continue_">继续</string>
<string name="test_tip">请选择本单词正确的意义栏</string> <string name="test_tip">请选择本单词正确的意义栏</string>


<string name="dialog_test_not_over">测试还未结束,你确定要退出吗?</string>
<string name="dialog_test_not_over_tip">退出后系统将不会保存你的本次测试数据</string>
<string name="quit">退出</string>
<string name="cancel">取消</string>

</resources> </resources>

+ 10
- 1
build.gradle View File

//gson https://github.com/google/gson //gson https://github.com/google/gson
Gson : "com.google.code.gson:gson:2.9.0", Gson : "com.google.code.gson:gson:2.9.0",
//MMKV https://github.com/Tencent/MMKV/wiki/android_tutorial_cn //MMKV https://github.com/Tencent/MMKV/wiki/android_tutorial_cn
MMKV : "com.tencent:mmkv:1.2.13"
MMKV : "com.tencent:mmkv:1.2.13",
//protobuf
protobuf_java: "com.google.protobuf:protobuf-java:3.11.0",
protobuf_java_format: "com.googlecode.protobuf-java-format:protobuf-java-format:1.2",
//grpc
annotation_api: "javax.annotation:javax.annotation-api:1.3.2",
grpc_okhttp: "io.grpc:grpc-okhttp:1.27.0",
grpc_android: "io.grpc:grpc-android:1.27.0",
grpc_protobuf: "io.grpc:grpc-protobuf:1.27.0",
grpc_stub: "io.grpc:grpc-stub:1.27.0",
] ]





+ 0
- 7
lib/common/src/main/java/com/suliang/common/base/adapter/BaseAdapterVM.kt View File

package com.suliang.common.base.adapter

import androidx.lifecycle.ViewModel

abstract class BaseAdapterVM<T,VM:ViewModel>(val vm:VM) : BaseAdapter<T>() {

}

lib/common/src/main/java/com/suliang/common/base/adapter/BaseAdapter.kt → lib/common/src/main/java/com/suliang/common/base/adapter/BaseRVAdapter.kt View File

import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.suliang.common.R import com.suliang.common.R


abstract class BaseAdapter<T> :
abstract class BaseRVAdapter<T> :
RecyclerView.Adapter<BaseAdapterViewHolder>() { RecyclerView.Adapter<BaseAdapterViewHolder>() {


companion object{ companion object{
fun getItem(position: Int) : T { fun getItem(position: Int) : T {
return mData.get(position) return mData.get(position)
} }
fun getData(): MutableList<T>{
return mData
}




/** /**
* @param data List<T>? * @param data List<T>?
*/ */
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
fun setData(data: MutableList<T>?) {
open fun setData(data: MutableList<T>?) {
mData.clear() mData.clear()
data?.let { it -> //不为空 data?.let { it -> //不为空
mData.addAll(data) mData.addAll(data)

+ 0
- 0
lib/common/src/main/java/com/suliang/common/base/adapter/BaseRVAdapterVM.kt View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save