Browse Source

test_data test_ui

master
suliang 2 years ago
parent
commit
3836f58409
41 changed files with 1183 additions and 416 deletions
  1. 14
    12
      .idea/codeStyles/Project.xml
  2. 5
    0
      .idea/misc.xml
  3. 11
    0
      README.md
  4. 11
    8
      app/build.gradle
  5. 1
    1
      app/src/main/AndroidManifest.xml
  6. 29
    1
      app/src/main/java/com/xkl/cdl/data/AppConstants.kt
  7. 17
    4
      app/src/main/java/com/xkl/cdl/data/DataTransferHolder.kt
  8. 26
    0
      app/src/main/java/com/xkl/cdl/data/bean/course/ExamBean.kt
  9. 57
    16
      app/src/main/java/com/xkl/cdl/data/manager/CourseManager.kt
  10. 170
    58
      app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt
  11. 17
    0
      app/src/main/java/com/xkl/cdl/dialog/DialogEventAction.kt
  12. 52
    16
      app/src/main/java/com/xkl/cdl/dialog/LearnDialog.kt
  13. 61
    0
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamActivity.kt
  14. 0
    12
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamWordActivity.kt
  15. 22
    1
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragment.kt
  16. 60
    38
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragmentViewModel.kt
  17. 34
    12
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseTotalTestFragment.kt
  18. 19
    19
      app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt
  19. 100
    0
      app/src/main/java/com/xkl/cdl/widget/VoiceSwitch.kt
  20. 10
    0
      app/src/main/res/drawable/shape_switch_thumb.xml
  21. 8
    0
      app/src/main/res/drawable/shape_switch_track.xml
  22. 7
    1
      app/src/main/res/layout/activity_learn_exam_word.xml
  23. 1
    2
      app/src/main/res/layout/fragment_course_total_test.xml
  24. 112
    0
      app/src/main/res/layout/inc_learn_test_statistic.xml
  25. 70
    0
      app/src/main/res/layout/inc_learn_title.xml
  26. BIN
      app/src/main/res/mipmap-xxhdpi/test_score_level_1.png
  27. BIN
      app/src/main/res/mipmap-xxhdpi/test_score_level_2.png
  28. BIN
      app/src/main/res/mipmap-xxhdpi/test_score_level_3.png
  29. 0
    1
      app/src/main/res/values/attrs.xml
  30. 1
    0
      app/src/main/res/values/colors.xml
  31. 4
    3
      lib/common/build.gradle
  32. 26
    4
      lib/common/src/main/java/com/suliang/common/base/BaseDialogFragment.kt
  33. 4
    2
      lib/common/src/main/java/com/suliang/common/base/LoadingDialog.kt
  34. 1
    1
      lib/common/src/main/java/com/suliang/common/base/ViewBehavior.kt
  35. 8
    181
      lib/common/src/main/java/com/suliang/common/base/activity/BaseActivity.kt
  36. 4
    1
      lib/common/src/main/java/com/suliang/common/base/activity/BaseActivityVM.kt
  37. 196
    0
      lib/common/src/main/java/com/suliang/common/base/activity/UIBaseActivity.kt
  38. 7
    10
      lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragment.kt
  39. 6
    1
      lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragmentVM.kt
  40. 5
    11
      lib/common/src/main/java/com/suliang/common/base/viewmodel/BaseViewModel.kt
  41. 7
    0
      lib/common/src/main/java/com/suliang/common/extension/RxScheduler.kt.kt

+ 14
- 12
.idea/codeStyles/Project.xml View File

<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<ComposeCustomCodeStyleSettings>
<option name="USE_CUSTOM_FORMATTING_FOR_MODIFIERS" value="false" />
</ComposeCustomCodeStyleSettings>
<DBN-PSQL> <DBN-PSQL>
<case-options enabled="true"> <case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" /> <option name="KEYWORD_CASE" value="lower" />
</formatting-settings> </formatting-settings>
</DBN-SQL> </DBN-SQL>
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="CONTINUATION_INDENT_IN_PARAMETER_LISTS" value="true" />
<option name="CONTINUATION_INDENT_FOR_EXPRESSION_BODIES" value="true" />
<option name="CONTINUATION_INDENT_IN_SUPERTYPE_LISTS" value="true" />
<option name="WRAP_EXPRESSION_BODY_FUNCTIONS" value="0" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
<option name="IF_RPAREN_ON_NEW_LINE" value="false" />
<option name="SPACE_AROUND_RANGE" value="true" />
<option name="SPACE_BEFORE_TYPE_COLON" value="true" />
<option name="WRAP_ELVIS_EXPRESSIONS" value="2" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<DBN-PSQL> <DBN-PSQL>
</codeStyleSettings> </codeStyleSettings>
<codeStyleSettings language="kotlin"> <codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="RIGHT_MARGIN" value="120" />
<option name="RIGHT_MARGIN" value="130" />
<option name="KEEP_LINE_BREAKS" value="false" /> <option name="KEEP_LINE_BREAKS" value="false" />
<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_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="EXTENDS_LIST_WRAP" value="0" />
<option name="VARIABLE_ANNOTATION_WRAP" value="2" />
<option name="ENUM_CONSTANTS_WRAP" value="2" />
<option name="WRAP_ON_TYPING" value="0" />
<indentOptions>
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
</indentOptions>
</codeStyleSettings> </codeStyleSettings>
</code_scheme> </code_scheme>
</component> </component>

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

<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/progress_center.xml" value="0.287962962962963" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/progress_center.xml" value="0.287962962962963" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_12_white.xml" value="0.5140625" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_12_white.xml" value="0.5140625" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_toplr_24_white.xml" value="0.5061538461538462" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_toplr_24_white.xml" value="0.5061538461538462" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_switch_thumb.xml" value="0.14074074074074075" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/spoken_autoplay_btn_text_color_.xml" value="0.5140625" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/spoken_autoplay_btn_text_color_.xml" value="0.5140625" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/switch_thumb_selector.xml" value="0.3768518518518518" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/switch_track_selector.xml" value="0.3768518518518518" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_base.xml" value="0.4979166666666667" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_base.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_exam_word.xml" value="0.33" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_word2.xml" value="0.4979166666666667" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_word2.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.5" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.5" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_first.xml" value="0.4979166666666667" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_first.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_learn_center.xml" value="0.25" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_learn_center.xml" value="0.25" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_my.xml" value="0.28229166666666666" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_my.xml" value="0.28229166666666666" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/inc_learn_test_statistic.xml" value="0.35260416666666666" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/inc_over_number.xml" value="0.4979166666666667" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/inc_over_number.xml" value="0.4979166666666667" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_main_learn_center_course_progress.xml" value="0.503125" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_main_learn_center_course_progress.xml" value="0.503125" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_main_learn_center_course_type_title.xml" value="0.67" /> <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/include_main_learn_center_course_type_title.xml" value="0.67" />

+ 11
- 0
README.md View File



注意include为merge的坑,viewbinding中需动态绑定 https://juejin.cn/post/6844904065655111693 注意include为merge的坑,viewbinding中需动态绑定 https://juejin.cn/post/6844904065655111693


讨论点:
状态加载的判断: 加载复习状态页没问题,学前总测试状态页也没问题,
但如何判断课程学习完成,目前是课程学习完成后才判断进入课程学后总测试
我的考虑是在进入课程详情界面时,对所有课时的学习进度进行判断,不包含课时的学后测试,当所有课时都学习完了,则进入学后总测试的状态页,
而在学习中,同样做引导课时前测试,学习,课时后测试,
课时后测试将包含 小游戏练习、再测一次、重新学习、下一步(进入学后总测试)或者下一课时(为当前课时的下一课时),
需要考虑的是,下一步或者下一课时的判断为循环判断,如,共10课时,现在点击学习的第5个,如果后面几个课时都学习完后,而1课时没有学习完成,下一课时将直接进入1课时的学习,
而下一步是在所有课时都学习完成的情况下,直接进入学后总测试的界面。






Kotlin的单例模式: https://developer.aliyun.com/article/642618 Kotlin的单例模式: https://developer.aliyun.com/article/642618

+ 11
- 8
app/build.gradle View File

// } // }


compileOptions { compileOptions {
coreLibraryDesugaringEnabled true //为了使用jdk8的脱糖属性
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }


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-viewmodel-ktx:2.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
// implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
// implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
// implementation 'androidx.appcompat:appcompat:1.2.0'
// implementation 'com.google.android.material:material:1.3.0'
// implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
// implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
// implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'


rootProject.ext.dependencies_required.each{ k, v -> implementation v} rootProject.ext.dependencies_required.each{ k, v -> implementation v}
testImplementation rootProject.ext.dependencies_testImplementation.junit testImplementation rootProject.ext.dependencies_testImplementation.junit
//Lottie //Lottie
implementation customDependencies.Lottie implementation customDependencies.Lottie


coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'

} }

+ 1
- 1
app/src/main/AndroidManifest.xml View File

android:name=".module.learn.LearnExamSpellActivity" android:name=".module.learn.LearnExamSpellActivity"
android:exported="true" /> android:exported="true" />
<activity <activity
android:name=".module.learn.LearnExamWordActivity"
android:name=".module.learn.LearnExamActivity"
android:exported="true" /> android:exported="true" />
<activity <activity
android:name=".module.learn.LearnSpellActivity" android:name=".module.learn.LearnSpellActivity"

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



/** 测试通过:>80 < 90 继续加油 <80 悲伤 */ /** 测试通过:>80 < 90 继续加油 <80 悲伤 */
const val TEST_SCORE_LEVEL_1 = 80 const val TEST_SCORE_LEVEL_1 = 80
/** 测试通过 >= 太棒了 */
/** 测试通过 >= 太棒了 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_SPOKEN_DIALOGUE = 4
const val TEST_QUEST_TYPE_GAP_FILLING = 2 //填空题
const val TEST_QUEST_TYPE_JUDGE = 3 //判断题
/**总测试 */
const val TEST_COUNT_TOTAL = 25
/**章节测试*/
const val TEST_COUNT_LESSON = 20
/**词汇量测试 */
const val TEST_COUNT_VOCABULARY = 100
/** 美 */
const val SOUND_TYPE_US = 1 //美
/** 发音默认type 英 */
const val SOUND_TYPE_UK = 2 //英
/**中文*/
const val SOUND_TYPE_CN = 3 //中文
} }

+ 17
- 4
app/src/main/java/com/xkl/cdl/data/DataTransferHolder.kt View File



private val dataMap = hashMapOf<String,Any>() private val dataMap = hashMapOf<String,Any>()


fun <T : Any> putData(t : T){
dataMap.put(AppConfig.INTENT_1,t)
/**
* 存放数据
* @param key String
* @param value T
*/
fun <T : Any> putData(key: String, value : T){
dataMap[key] = value
} }


/**
* 获取数据
* @param key String
* @return T
*/
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T> getData() : T{
return dataMap.get(AppConfig.INTENT_1) as T
fun <T> getData(key : String) : T{
return dataMap[key] as T
} }


/**
* 清空集合
*/
fun clear(){ fun clear(){
dataMap.clear() dataMap.clear()
} }

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

package com.xkl.cdl.data.bean.course

/**
* author suliang
* create 2022/4/6 11:03
* Describe: 测试数据
*/
class ExamBean {
var id : Long = 0 //当前记录所在的id 英语: 测试题的id 作文:exam_id
var word_id : Long = 0 //英语:词汇id 作文: knowledge_id知识点id
var word : String = "" //测试题干
var correct : String = "" //正确选项 在拼写时记录为该单词的释义
var error1: String = "" //错误选项1
var error2: String = "" //错误选项2
var error3 : String = "" //错误选项3
var type = 0 //测试题类型

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

var questionIsAudio = false //问题是否是音频
var answersIsAudio = false //答案是否是音频



}

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

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


import android.util.Size
import com.suliang.common.util.file.FileUtil import com.suliang.common.util.file.FileUtil
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.bean.course.CoursePack import com.xkl.cdl.data.bean.course.CoursePack
import com.xkl.cdl.data.bean.course.ExamBean
import java.io.File import java.io.File
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
* Describe: 课程管理类 * Describe: 课程管理类
*/ */
object CourseManager { object CourseManager {
/** 项目对应的课程包 */ /** 项目对应的课程包 */
val subjectWithCoursePackMap = hashMapOf<Int, List<CoursePack>>() val subjectWithCoursePackMap = hashMapOf<Int, List<CoursePack>>()
/** /**
* 获取对应项目的课程数量 * 获取对应项目的课程数量
* @param subjectId Int 项目 * @param subjectId Int 项目
* @return Int 课程数量 * @return Int 课程数量
*/ */
fun getSubjectForCourseSize(subjectId: Int): Int {
fun getSubjectForCourseSize(subjectId : Int) : Int {
return subjectWithCoursePackMap[subjectId]?.let { return subjectWithCoursePackMap[subjectId]?.let {
var count = 0 var count = 0
it.forEach { coursePack -> it.forEach { coursePack ->
count += coursePack.childrenCourses?.size ?: 0
count += coursePack.childrenCourses?.size
?: 0
} }
count count
} ?: 0
}
?: 0
} }
/** /**
* 搜索项目下的课程包 * 搜索项目下的课程包
* @param subjectId Int 项目id * @param subjectId Int 项目id
* @return MutableList<CoursePack>? 结果值 * @return MutableList<CoursePack>? 结果值
*/ */
@Synchronized @Synchronized
fun filterSearchCoursePack(subjectId: Int, searchValue: String?): MutableList<CoursePack>? {
fun filterSearchCoursePack(subjectId : Int, searchValue : String?) : MutableList<CoursePack>? {
return if (searchValue.isNullOrEmpty() || searchValue.trim().isEmpty()) { return if (searchValue.isNullOrEmpty() || searchValue.trim().isEmpty()) {
subjectWithCoursePackMap[subjectId]?.toMutableList() subjectWithCoursePackMap[subjectId]?.toMutableList()
} else { } else {
} }
} }
} }
/** /**
* 根据subjectId,courseId 获取CoursePack * 根据subjectId,courseId 获取CoursePack
* @param subjectId Int 项目Id * @param subjectId Int 项目Id
* @return CoursePack? 课程包 * @return CoursePack? 课程包
*/ */
@Synchronized @Synchronized
fun getCoursePackWithCoursePackId(subjectId: Int, coursePackId: Long): CoursePack? {
fun getCoursePackWithCoursePackId(subjectId : Int, coursePackId : Long) : CoursePack? {
return subjectWithCoursePackMap[subjectId]?.let { return subjectWithCoursePackMap[subjectId]?.let {
//返回很符合条件的第一个元素 //返回很符合条件的第一个元素
it.firstOrNull { it.firstOrNull {
} }
} }
} }
/*** /***
* 检查课程包下课程的db数据文件是否存在,不存在,则复制过去 * 检查课程包下课程的db数据文件是否存在,不存在,则复制过去
*/ */
entry.value.forEach { coursePack -> entry.value.forEach { coursePack ->
coursePack.childrenCourses.forEach { coursePack.childrenCourses.forEach {
val file = File( val file = File(
FileUtil.getSaveDirPath("db"),
"${entry.key}/${coursePack.coursePackId}/${it.courseId}/course.db"
)
FileUtil.getSaveDirPath("db"), "${entry.key}/${coursePack.coursePackId}/${it.courseId}/course.db"
)
//不存在,复制,存在则不操作 //不存在,复制,存在则不操作
if (!file.exists()) { if (!file.exists()) {
FileUtil.copyAsset(it.dbPathName, file) FileUtil.copyAsset(it.dbPathName, file)
} }
} }
} }
/** /**
* 用于显示的进度 * 用于显示的进度
* @param progress Double 课程的进度 * @param progress Double 课程的进度
* @return String * @return String
*/ */
@JvmStatic @JvmStatic
fun useToShowProgress(progress: Double): String {
fun useToShowProgress(progress : Double) : String {
return when { return when {
progress == 0.0 -> "0" progress == 0.0 -> "0"
progress < 0.1 -> "0.1" progress < 0.1 -> "0.1"
else -> "100" else -> "100"
} }
} }

/**
* 计算预计测试时间
* 总测: 口语总测试与辨音课程测试不显示时长
* 课时测:辨音不显示时长, 作文测试没有显示时长
* @param courseType Int 课程类型
* @param testData List<ExamBean> 测试题
* @return String 格式化好的数据: 共%d题,预计%d分钟
*/
@JvmStatic
fun expectedTestTime(courseType : Int, testType : Int, testData : List<ExamBean>) : String {
return when (courseType) {
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> {
""
}
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> {
String.format("共${testData.size}题,预计%d分钟", (Math.ceil(testData.stream().mapToInt {
it.word.length
}.sum() * 1.6 / 60)).toInt())
}
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> {
when(testType) {
AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> {
"共${testData.size}题"
}
else -> {
String.format("共${testData.size}题,预计%d分钟", Math.ceil(testData.size * 0.1).toInt())
}
}
}
AppConstants.COURSE_TYPE_ENGLISH_VOICE -> {
"共${testData.size}题"
}
else -> {
String.format("共${testData.size}题,预计%d分钟", Math.ceil(testData.size * 0.1).toInt())
}
}
}
} }

+ 170
- 58
app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt View File



import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.widget.Toast import android.widget.Toast
import androidx.core.database.getLongOrNull
import com.suliang.common.util.AppGlobals import com.suliang.common.util.AppGlobals
import com.suliang.common.util.thread.AppExecutors import com.suliang.common.util.thread.AppExecutors
import com.xkl.cdl.data.AppConstants import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.bean.course.CourseDetail import com.xkl.cdl.data.bean.course.CourseDetail
import com.xkl.cdl.data.bean.course.ExamBean
import com.xkl.cdl.data.bean.course.Lesson import com.xkl.cdl.data.bean.course.Lesson
import com.xkl.cdl.data.manager.FilePathManager import com.xkl.cdl.data.manager.FilePathManager
import net.sqlcipher.database.SQLiteDatabase import net.sqlcipher.database.SQLiteDatabase
private const val SPOKEN = "XKL_SPOKEN_COURSE_DATA_KEY" private const val SPOKEN = "XKL_SPOKEN_COURSE_DATA_KEY"
private const val COMPOSITION = "XKL_LOCAL_COMPOSITION_DATA_KEY" private const val COMPOSITION = "XKL_LOCAL_COMPOSITION_DATA_KEY"
private const val LITERACY = "XKL_LOCAL_CHINESE_COURSE_KEY" private const val LITERACY = "XKL_LOCAL_CHINESE_COURSE_KEY"
private var mDataBase: SQLiteDatabase? = null
private fun open(base: DbControlBase) {
private var mDataBase : SQLiteDatabase? = null
private fun open(base : DbControlBase) {
synchronized(DBCourseManager) { synchronized(DBCourseManager) {
if (mDataBase != null && currentBase != base) { if (mDataBase != null && currentBase != base) {
mDataBase?.close() mDataBase?.close()
AppConstants.COURSE_TYPE_CHINESE_LITERACY -> LITERACY AppConstants.COURSE_TYPE_CHINESE_LITERACY -> LITERACY
else -> NORMAL else -> NORMAL
} }
mDataBase = SQLiteDatabase.openDatabase(FilePathManager.getCourseDbPath(currentBase).path,
p,
null,
SQLiteDatabase.OPEN_READONLY)
mDataBase = SQLiteDatabase.openDatabase(
FilePathManager.getCourseDbPath(currentBase).path, p, null, SQLiteDatabase.OPEN_READONLY
)
} }
if (mDataBase == null) { if (mDataBase == null) {
AppExecutors.mainThread.run { AppExecutors.mainThread.run {
Toast.makeText(AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG)
Toast.makeText(
AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG
)
} }
} }
} }
} }
/**获取该课程的所有课时 /**获取该课程的所有课时
* @param base DbControlBase * @param base DbControlBase
* @param detail CourseDetail 课程信息 * @param detail CourseDetail 课程信息
* @return List<Lesson> * @return List<Lesson>
*/ */

@SuppressLint("Range") @SuppressLint("Range")
fun queryAllLesson(base: DbControlBase, detail: CourseDetail): List<Lesson> {
fun queryAllLesson(base : DbControlBase, detail : CourseDetail) : List<Lesson> {
val mutableList = mutableListOf<Lesson>() val mutableList = mutableListOf<Lesson>()
open(base) open(base)
//聚合所有课时,先所有chapter_sort和word_sort排序,然后根据leesonId分组并聚合leeson_id到 //聚合所有课时,先所有chapter_sort和word_sort排序,然后根据leesonId分组并聚合leeson_id到
var positionIndex = 0 var positionIndex = 0
mDataBase?.rawQuery(rawQurySql, null)?.let { mDataBase?.rawQuery(rawQurySql, null)?.let {
while (it.moveToNext()) { while (it.moveToNext()) {
val chapterId: Long = it.getLong(it.getColumnIndex("chapter_id"))
val chapterName: String = it.getString(it.getColumnIndex("chapter_title"))
val chapterId : Long = it.getLong(it.getColumnIndex("chapter_id"))
val chapterName : String = it.getString(it.getColumnIndex("chapter_title"))
val lessonId = it.getLong(it.getColumnIndex("lesson_id")) val lessonId = it.getLong(it.getColumnIndex("lesson_id"))
val lessonName = when (base.courseType) { val lessonName = when (base.courseType) {
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("lesson_title")) AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("lesson_title"))
else -> it.getString(it.getColumnIndex("lesson")) else -> it.getString(it.getColumnIndex("lesson"))
} }
val wordIds = when (base.courseType) { val wordIds = when (base.courseType) {
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("relation_id"))
.split(",")
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("relation_id")).split(",")
else -> it.getString(it.getColumnIndex("wordIds")).split(",") else -> it.getString(it.getColumnIndex("wordIds")).split(",")
}.map { value -> value.toLong() }.toMutableList() }.map { value -> value.toLong() }.toMutableList()
val lessonType: Int = when (base.courseType) {
val lessonType : Int = when (base.courseType) {
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getInt(it.getColumnIndex("type")) AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getInt(it.getColumnIndex("type"))
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> it.getInt(it.getColumnIndex("lesson_type")) AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> it.getInt(it.getColumnIndex("lesson_type"))
else -> AppConstants.LESSON_TYPE_WORD else -> AppConstants.LESSON_TYPE_WORD
} }
val key = "${chapterId}_${lessonId}" val key = "${chapterId}_${lessonId}"

val learnIndex = wordIds.indexOf(detail.lesson_learn_point.getOrElse(key, { -1 })) //学习位置,当前位置为已学

val learnIndex = wordIds.indexOf(
detail.lesson_learn_point.getOrElse(key, { -1 })
) //学习位置,当前位置为已学
// 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 // 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。
val learnIsOver = wordIds.size - 1 == learnIndex val learnIsOver = wordIds.size - 1 == learnIndex

val lesson = Lesson(base.subjectId,
base.coursePackId,
base.courseId,
base.courseType,
chapterId,
chapterName,
lessonId,
lessonName).apply {
val lesson = Lesson(
base.subjectId,
base.coursePackId,
base.courseId,
base.courseType,
chapterId,
chapterName,
lessonId,
lessonName).apply {
lessonPositionInList = positionIndex lessonPositionInList = positionIndex
this.wordIds = wordIds //内容 this.wordIds = wordIds //内容
totalNumber = this.wordIds.size //总数 totalNumber = this.wordIds.size //总数
mutableList.last().lastLesson = true mutableList.last().lastLesson = true
return mutableList.toList() return mutableList.toList()
} }

/** 获取总测试的数据 */
fun queryTotalTest(coursePackId: Long, courseId: Long) {

}

/** 获取课时测试数据 */
fun queryLessonTest(coursePackId: Long, courseId: Long, chapterId: Long, lessonId: Long, courseType: Long) {

/** 获取学习时的测试的数据 (学前、学后总测、课时学前、学后总测)
* @param base DbControlBase
* @param testType Int 测试类型
* @param lesson Lesson? 测试章节 仅当课时学前学后测试时才传入课时
* @return List<ExamBean> 返回数据
*/
fun queryLearnTest(base : DbControlBase, testType : Int, lesson : Lesson? = null) : List<ExamBean> {
open(base)
val result = mutableListOf<ExamBean>()
//查询数量
val count = when (testType) {
AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> AppConstants.TEST_COUNT_TOTAL //学前、学后总测
AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> AppConstants.TEST_COUNT_LESSON //课时学前、学后
else -> 0
}
val sql = when (base.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE,
AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_CHINESE_LITERACY,
AppConstants.COURSE_TYPE_CHINESE_PINYIN,
-> { //没有作文,作文没有学前总测
"SELECT exam.*, chapter_id,lesson_id,basic_explaination FROM chapter JOIN exam ON chapter.word_id = exam.word_id AND exam.type = ${AppConstants.TEST_QUEST_TYPE_CHOICE} " + when (testType) {
AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> { //学前、学后测
" AND chapter_id = ${lesson!!.chapterId} AND lesson_id = ${lesson.lessonId} "
}
else -> ""
} + " ORDER by random() LIMIT $count"
}
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> { //拼写 不需要查询测试表,不需要选项
"SELECT id, word_id,word,chapter_id,lesson_id,basic_explaination FROM chapter " + when (testType) {
AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> { //学前、学后测
" AND chapter_id = ${lesson!!.chapterId} AND lesson_id = ${lesson.lessonId} "
}
else -> " ORDER by random() LIMIT $count"
}
}
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { //口语
when (testType) {
AppConstants.TEST_TYPE_BEFORE_TOTAL, AppConstants.TEST_TYPE_AFTER_TOTAL -> { //总测
"SELECT * FROM exam WHERE type = 5 ORDER by random() LIMIT $count"
}
AppConstants.TEST_TYPE_BEFORE, AppConstants.TEST_TYPE_AFTER -> {
"SELECT exam.* FROM chapter JOIN exam on chapter.word_id = exam.word_id AND exam.type = 1 AND chapter_id = ${lesson!!.chapterId} AND lesson_id = ${lesson.lessonId} ORDER by random() LIMIT $count"
}
else -> ""
}
}
else -> "" //没有任何课程,课程都特别写入类型,用于匹配
}
mDataBase?.rawQuery(
sql, null
)?.run {
when (base.courseType) {
AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE,
AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_CHINESE_LITERACY,
AppConstants.COURSE_TYPE_CHINESE_PINYIN,
-> {
while (moveToNext()) {
result.add(ExamBean().apply {
id = getLong(0)
word_id = getLong(1)
word = getString(2)
correct = getString(3)
error1 = getString(4)
error2 = getString(5)
error3 = getString(6)
type = AppConstants.TEST_QUEST_TYPE_CHOICE // 为1
chapterId = getLong(8)
lessonId = getLong(9)
})
}
}
AppConstants.COURSE_TYPE_ENGLISH_SPELL -> {
while (moveToNext()) {
result.add(ExamBean().apply {
id = getLong(0)
word_id = getLong(1)
word = getString(2)
chapterId = getLong(3)
lessonId = getLong(4)
correct = getString(5)
type = AppConstants.TEST_QUEST_TYPE_CHOICE // 为1
})
}
}
AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> {
while (moveToNext()) {
result.add(ExamBean().apply {
id = getLong(0)
word_id = getLong(1)
word = getString(2)
correct = getString(3)
error1 = getString(4)
error2 = getString(5)
error3 = getString(6)
type = AppConstants.TEST_QUEST_TYPE_CHOICE // 为1
lesson?.let { //课时测试时才会有课时存在
chapterId = it.chapterId
lessonId = it.lessonId
}
})
}
}
}
close()
}
return result
} }

/** 获取指定课时(指定数据源wordIds)的随机个数测试数据 */ /** 获取指定课时(指定数据源wordIds)的随机个数测试数据 */
fun queryLessonTestRandomSize(coursePackId: Long,
courseId: Long,
chapterId: Long,
lessonId: Long,
courseType: Long,
limitSize: Int,
wordIds: ArrayList<Long>? = null) {

fun queryLessonTestRandomSize(
coursePackId : Long,
courseId : Long,
chapterId : Long,
lessonId : Long,
courseType : Long,
limitSize : Int,
wordIds : ArrayList<Long>? = null,
) {
} }

/** 获取课时学习内容 */ /** 获取课时学习内容 */
fun queryLessonLearnData(coursePackId: Long,
courseId: Long,
chapterId: Long,
lessonId: Long,
wordIds: ArrayList<Long>) {

fun queryLessonLearnData(
coursePackId : Long,
courseId : Long,
chapterId : Long,
lessonId : Long,
wordIds : ArrayList<Long>,
) {
} }

/** 获取课程今日需要复习的数据 */ /** 获取课程今日需要复习的数据 */
fun queryCourseReviewDatas(coursePackId: Long, courseId: Long, wordIds: ArrayList<Long>) {

fun queryCourseReviewDatas(
coursePackId : Long,
courseId : Long,
wordIds : ArrayList<Long>,
) {
} }


} }

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

package com.xkl.cdl.dialog

/**
* author suliang
* create 2022/4/6 9:54
* Describe: LearnDialog 按钮点击动作
*/
enum class DialogEventAction {
/**下一步*/
NEXT,
/** 开始学习 */
START_LEARN,
/** 课时学前测试 */
START_LESSON_BEFORE_TEST

// TODO: 2022/4/6 可以添加相应的动作action,来处理对应的动作
}

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

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


import android.view.View import android.view.View
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentManager
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.xkl.cdl.R import com.xkl.cdl.R
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.databinding.DialogLessonLearnBinding import com.xkl.cdl.databinding.DialogLessonLearnBinding


/** /**
*/ */
class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() {


private lateinit var dialogListener : DialogFragmentListener
private lateinit var dialogListener: DialogFragmentListener


override fun initFragment() { override fun initFragment() {
initClick() initClick()
initLessonBeforeTestOver() initLessonBeforeTestOver()
} }


fun initClick(){
dialogListener = if (parentFragment != null){
fun initClick() {
dialogListener = if (parentFragment != null) {
parentFragment as DialogFragmentListener parentFragment as DialogFragmentListener
}else{
} else {
activity as DialogFragmentListener activity as DialogFragmentListener
} }
} }


fun initLessonBeforeTest(){
fun initLessonBeforeTest() {
binding.tvTitle.text = "课时学前测试" binding.tvTitle.text = "课时学前测试"
binding.tvLessonName.text = "章节课时名称" binding.tvLessonName.text = "章节课时名称"
binding.tvCountTime.text = "测试题目时间" binding.tvCountTime.text = "测试题目时间"


binding.tvRight.click { binding.tvRight.click {
LogUtil.e("Dialog -- > show()") LogUtil.e("Dialog -- > show()")
dialogListener.rightClick()
} }


binding.tvLeft.click { binding.tvLeft.click {
LogUtil.e("Dialog -- > hide()") LogUtil.e("Dialog -- > hide()")
dialogListener.leftClick()
} }
} }


fun initLessonBeforeTestOver(){
fun initLessonBeforeTestOver() {
binding.tvScore.run { binding.tvScore.run {
visibility = View.VISIBLE visibility = View.VISIBLE
text = "100分" text = "100分"
} }


} }
fun initLessonAfterTestOver(){
//未通过


//已通过
fun initLessonAfterTestOver() {
//未通过、通过和通过已通过,均为一致,只是显示的状态不同
val score = if (1 + 1 == 2) 100 else 88

when {
score < AppConstants.TEST_SCORE_LEVEL_1 -> { //小于80
binding.imgIv.setImageResource(R.mipmap.test_score_level_1)
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray_2))
}
score >= AppConstants.TEST_SCORE_LEVEL_2 -> { //大于90
binding.imgIv.setImageResource(R.mipmap.test_score_level_2)
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_1))
}
else -> { // 大于等于80 且小于90
binding.imgIv.setImageResource(R.mipmap.test_score_level_3)
binding.tvScore.setTextColor(ContextCompat.getColor(requireContext(), R.color.red_2))
}
}


//已通过的再通过
binding.tvScore.run {
visibility = View.VISIBLE
text = "${score}分"
}


}
binding.tvTip.visibility = View.VISIBLE
binding.tvTitle.text = "恭喜你,完成了课时学前测试"
binding.tvLessonName.visibility = View.GONE
binding.tvCountTime.visibility = View.GONE
binding.incStatisticsNumber.root.visibility = View.VISIBLE


// binding.tvTip1.visibility = View.VISIBLE
// binding.tvTip1.setText(resources.getString(R.string.test_before_test_over_tip))


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


}




public interface DialogFragmentListener{
fun rightClick()
fun leftClick()
interface DialogFragmentListener {
fun dialogButtonClick(action: DialogEventAction)
} }


} }

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

package com.xkl.cdl.module.learn

import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.viewbinding.ViewBinding
import com.suliang.common.AppConfig
import com.suliang.common.base.activity.UIBaseActivity
import com.xkl.cdl.R
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.databinding.ActivityLearnExamWordBinding
import com.xkl.cdl.databinding.ActivityLearnSpellBinding
import com.xkl.cdl.module.m_center_learn.CoursePackMainActivity

/**
* author suliang
* create 2022/4/6 19:18
* Describe: 测试界面 总测 课时测
*/
class LearnExamActivity : UIBaseActivity() {
companion object {
@JvmStatic
fun newInstance(context: Context) {
context.startActivity(Intent(context, LearnExamActivity::class.java))
}
}
/** 音频测试、单词测试布局 */
private var _normalBinding : ActivityLearnExamWordBinding? = null
/**布局 */
private var _spellBinding : ActivityLearnSpellBinding? = null
override fun setContentView() {
_normalBinding = ActivityLearnExamWordBinding.inflate(layoutInflater)
setContentView(_normalBinding!!.root)
}
override fun initActivity(savedInstanceState : Bundle?) {
}
override fun loadData() {
_normalBinding?.let {
it.incLearnTitle.voiceSwitch.soundWayChange.observe(this){
println("发音 --》 $it ")
}
it.incLearnTitle.voiceSwitch.setSoundWay(AppConstants.SOUND_TYPE_US)
}
}
}

+ 0
- 12
app/src/main/java/com/xkl/cdl/module/learn/LearnExamWordActivity.kt View File

package com.xkl.cdl.module.learn

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.xkl.cdl.R

class LearnExamWordActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_learn_exam_word)
}
}

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

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.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.m_center_learn.CoursePackMainActivityViewModel import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel
//学后总测 //学后总测
// replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL)) // replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL))
//学习目录 //学习目录
replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance())

vm.courseDetail.run {
if (st_before == AppConstants.NOT_DOING) { //学前总测未做
replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_BEFORE_TOTAL))
} else if (courseLearnProgress != 100.0) { //学习未完成
replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance())
} else { //学习完成,学后总测试界面
replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL))
}
}
}
/**
* 供其他地方手动调用切换界面
* @param position Int 1目录页 2学后总测
*/
fun changeFragment(position: Int){
when(position){
1 -> replaceFragment(R.id.layout_root, CourseLessonFragment.newInstance())
2 -> replaceFragment(R.id.layout_root, CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL))
}
} }





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

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


import android.provider.ContactsContract
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.suliang.common.base.activity.ToastEvent
import com.suliang.common.base.viewmodel.BaseViewModel import com.suliang.common.base.viewmodel.BaseViewModel
import com.suliang.common.extension.diskIo2DiskIo import com.suliang.common.extension.diskIo2DiskIo
import com.suliang.common.extension.diskIo2Main
import com.xkl.cdl.data.bean.course.Course import com.xkl.cdl.data.bean.course.Course
import com.xkl.cdl.data.bean.course.CourseDetail import com.xkl.cdl.data.bean.course.CourseDetail
import com.xkl.cdl.data.bean.course.ExamBean
import com.xkl.cdl.data.bean.course.Lesson import com.xkl.cdl.data.bean.course.Lesson
import com.xkl.cdl.data.manager.db.DBCourseManager
import com.xkl.cdl.data.manager.db.DbControlBase import com.xkl.cdl.data.manager.db.DbControlBase
import com.xkl.cdl.data.repository.DataRepository import com.xkl.cdl.data.repository.DataRepository
import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Observable
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.ArrayList
import kotlin.collections.HashMap


/** /**
* author suliang * author suliang
* create 2022/3/28 15:26 * create 2022/3/28 15:26
* Describe: * Describe:
* @property courseIndex 该课程在课程包的课程集合中的位置 * @property courseIndex 该课程在课程包的课程集合中的位置
*
*/ */
class CourseMainFragmentViewModel(val courseIndex: Int) : BaseViewModel() {
class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() {
//课程包主页的ViewModel //课程包主页的ViewModel
lateinit var coursePackMainActivityVM: CoursePackMainActivityViewModel
lateinit var dbControlBase: DbControlBase
lateinit var coursePackMainActivityVM : CoursePackMainActivityViewModel
lateinit var dbControlBase : DbControlBase
//当前操作课程 //当前操作课程
lateinit var course: Course
lateinit var course : Course
/** 课程的收藏本: 目前仅作用在口语课程会使用到该数据 */ /** 课程的收藏本: 目前仅作用在口语课程会使用到该数据 */
lateinit var collectList: HashMap<String, Long>
lateinit var collectList : HashMap<String, Long>
/** 复习的数据 */ /** 复习的数据 */
lateinit var reviewDataList: MutableList<String>
lateinit var reviewDataList : MutableList<String>
/** 课程所有课时 */ /** 课程所有课时 */
lateinit var allLesson: List<Lesson>
lateinit var allLesson : List<Lesson>
//课程的详细信息 //课程的详细信息
lateinit var courseDetail: CourseDetail
lateinit var courseDetail : CourseDetail
//获取课程的统计信息 //获取课程的统计信息
//课程收藏本: 主要用于口语对话学习的数据 //课程收藏本: 主要用于口语对话学习的数据
//获取课程的复习数据 //获取课程的复习数据
fun loadMain() : MutableLiveData<Boolean> { fun loadMain() : MutableLiveData<Boolean> {
showHideLoading(true) showHideLoading(true)
val mutableLiveData = MutableLiveData<Boolean>() val mutableLiveData = MutableLiveData<Boolean>()
Observable.concat(
DataRepository.getCourseStatistics().flatMap {
courseDetail = it
Observable.concat(DataRepository.getCourseStatistics().flatMap {
courseDetail = it
return@flatMap DataRepository.getCourseAllLesson(dbControlBase, it) return@flatMap DataRepository.getCourseAllLesson(dbControlBase, it)
}, DataRepository.getReviewData()
, DataRepository.getCourseCollect())
.compose(diskIo2DiskIo())
.subscribe(object :Observer<Any>{
override fun onSubscribe(d: Disposable?) {

}, DataRepository.getReviewData(), DataRepository.getCourseCollect()).compose(diskIo2DiskIo())
.subscribe(object : Observer<Any> {
override fun onSubscribe(d : Disposable?) {
} }
override fun onNext(t: Any?) {
override fun onNext(t : Any?) {
t?.let { it -> t?.let { it ->
if (it is List<*>){
if (it is List<*>) {
allLesson = it as List<Lesson> allLesson = it as List<Lesson>
} }
} }
} }
override fun onError(e: Throwable?) {
override fun onError(e : Throwable?) {
e?.printStackTrace() e?.printStackTrace()
showHideLoading(false)
showToast(ToastEvent(content = "数据加载异常"))
} }

override fun onComplete() { override fun onComplete() {
showHideLoading(false) showHideLoading(false)
mutableLiveData.postValue(true) mutableLiveData.postValue(true)
} }
}) })
return mutableLiveData return mutableLiveData
} }


/**
* 获取课程的测试数据
* @param testType Int 测试类型
* @param lesson Lesson? 课时章节
*/
fun loadTest(testType : Int, lesson : Lesson? = null) : MutableLiveData<List<ExamBean>> {
val result = MutableLiveData<List<ExamBean>>()
// showHideLoading(true)
Observable.create<List<ExamBean>> {
it.onNext(DBCourseManager.queryLearnTest(dbControlBase, testType, lesson))
it.onComplete()
}.compose(diskIo2Main()).subscribe({
// showHideLoading(false)
result.value = it
}, {
it.printStackTrace()
// showHideLoading(false)
})
return result
}
} }

+ 34
- 12
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.bean.course.ExamBean
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


/** /**
* 课程总测试: 学前总,学后总 * 课程总测试: 学前总,学后总
* @property param1 String?
* @property param2 String?
* @property totalTestType 测试类型
*/ */
class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, CourseMainFragmentViewModel>() { class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, CourseMainFragmentViewModel>() {


override fun initViewModel(): CourseMainFragmentViewModel { override fun initViewModel(): CourseMainFragmentViewModel {
return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java] return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java]
} }
override fun initFirst() {
super.initFirst()
//由于使用的是与父类相同的loadingEvent,所以将此evnet移除
vm.loadingEvent.removeObservers(this)
}


//传递过来的总测类型: 学前,学后 //传递过来的总测类型: 学前,学后
private var totalTestType = 0 private var totalTestType = 0

//测试的数据
private var testData : List<ExamBean> = emptyList()
override fun initFragment() { override fun initFragment() {
totalTestType = requireArguments().getInt(AppConfig.INTENT_1) totalTestType = requireArguments().getInt(AppConfig.INTENT_1)
}

override fun loadData() {
vm.loadTest(totalTestType).observe(this){
testData = it
initView()
}
}
private fun initView(){
binding.tvCountTip.text = CourseManager.expectedTestTime(vm.course.courseType,totalTestType,testData!!)
when (totalTestType) { when (totalTestType) {
AppConstants.TEST_TYPE_BEFORE_TOTAL -> { AppConstants.TEST_TYPE_BEFORE_TOTAL -> {
//学前总测 //学前总测
binding.run { binding.run {
tvTitle.setText(R.string.test_total_before_title) tvTitle.setText(R.string.test_total_before_title)
tvCountTip.text = "共25题,预计3分钟"
button1.run {
setText(R.string.start_learn)
click {
continueLearn(it)
}
}
//按钮初始 //按钮初始
button2.run { button2.run {
setText(R.string.start_test) setText(R.string.start_test)
} }
} }
} }

AppConstants.TEST_TYPE_AFTER_TOTAL -> { AppConstants.TEST_TYPE_AFTER_TOTAL -> {
//学后总测 //学后总测
binding.run { binding.run {
when { when {
it == -1.0 -> { //未测 it == -1.0 -> { //未测
tvMainTip.setText(R.string.test_total_after_tip_1) tvMainTip.setText(R.string.test_total_after_tip_1)
tvCountTip.text = "共25题,预计3分钟"
button2.run { button2.run {
setText(R.string.test_type_after_total) setText(R.string.test_type_after_total)
click { view -> click { view ->
} }
} }
} }

override fun loadData() {

}


/**继续学习*/ /**继续学习*/
private fun continueLearn(view: View) { private fun continueLearn(view: View) {
showToast("继续学习")
(requireParentFragment() as CourseMainFragment).changeFragment(1)
} }


/** 开始测试 */ /** 开始测试 */
private fun startTest(view: View) { private fun startTest(view: View) {
showToast("开始测试")
LearnExamActivity.newInstance(requireContext())
} }


} }

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

import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit


@SuppressLint("CustomSplashScreen") @SuppressLint("CustomSplashScreen")
class SplashActivity : BaseActivity<ActivitySplashBinding>(), LearnDialog.DialogFragmentListener {
class SplashActivity : BaseActivity<ActivitySplashBinding>(){


override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
if (!isTaskRoot) { if (!isTaskRoot) {
// e.printStackTrace() // e.printStackTrace()
// } // }


learnDialog = LearnDialog().apply {
show(supportFragmentManager,javaClass.name)
}
LogUtil.e("Dialog -- > ${learnDialog.hashCode()}")
if (true) return
// learnDialog = LearnDialog().apply {
// show(supportFragmentManager,javaClass.name)
// }
// LogUtil.e("Dialog -- > ${learnDialog.hashCode()}")
// if (true) return




showHideLoading(true) showHideLoading(true)
} }




lateinit var learnDialog :LearnDialog
lateinit var learnDialog1: LearnDialog

override fun rightClick() {
if (!this::learnDialog1.isInitialized) {
learnDialog1 = LearnDialog()
}
LogUtil.e("Dialog 1 -- > ${learnDialog1.hashCode()}")
learnDialog1.show(supportFragmentManager,javaClass.name)
}
// lateinit var learnDialog :LearnDialog
// lateinit var learnDialog1: LearnDialog


override fun leftClick() {
learnDialog1.dismiss()
}
// override fun rightClick() {
// if (!this::learnDialog1.isInitialized) {
// learnDialog1 = LearnDialog()
// }
// LogUtil.e("Dialog 1 -- > ${learnDialog1.hashCode()}")
// learnDialog1.show(supportFragmentManager,javaClass.name)
// }
//
// override fun leftClick() {
// learnDialog1.dismiss()
// }




} }

+ 100
- 0
app/src/main/java/com/xkl/cdl/widget/VoiceSwitch.kt View File

package com.xkl.cdl.widget

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.MutableLiveData
import com.suliang.common.util.os.ScreenUtil
import com.xkl.cdl.R
import com.xkl.cdl.data.AppConstants

/**
* author suliang
* create 2022/4/7 16:12
* Describe: 自定义英美发音switch
*/
class VoiceSwitch @JvmOverloads constructor(context : Context, attr : AttributeSet? = null, defStyle : Int = 0) :
View(context, attr, defStyle) {
//背景图片和滑块
private val strack : Bitmap by lazy {
val drawable = ResourcesCompat.getDrawable(resources, R.drawable.shape_switch_track, null)!!
val createBitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(createBitmap)
drawable.setBounds(0,0,drawable.intrinsicWidth,drawable.intrinsicHeight)
drawable.draw(canvas)
createBitmap
// BitmapFactory.decodeResource(resources, R.drawable.shape_switch_track)
}
private val thumb : Bitmap by lazy {
val drawable = ResourcesCompat.getDrawable(resources, R.drawable.shape_switch_thumb, null)!!
val createBitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(createBitmap)
drawable.setBounds(0,0,drawable.intrinsicWidth,drawable.intrinsicHeight)
drawable.draw(canvas)
createBitmap
}
//默认英音
private var soundWay = AppConstants.SOUND_TYPE_UK
private val uk = "英音"
private val us = "美音"
val soundWayChange = MutableLiveData<Int>()
private var w : Int = 0
private var h : Int = 0
private val paint : Paint = Paint().apply {
isAntiAlias = true
textSize = ScreenUtil.dp2px(12f).toFloat()
color = ResourcesCompat.getColor(resources,R.color.main_text_color,null)
textAlign = Paint.Align.CENTER
style = Paint.Style.FILL
}
override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int) {
w = strack.width + paddingStart + paddingRight
h = strack.height
setMeasuredDimension(w,h)
}
override fun onDraw(canvas : Canvas) {
// super.onDraw(canvas)
//背景滑块
// val drawable = ResourcesCompat.getDrawable(resources, R.drawable.shape_switch_track, null)!!
// drawable.setBounds(0,0,drawable.intrinsicWidth,drawable.intrinsicHeight)
// drawable.draw(canvas)
canvas.drawBitmap(strack,paddingStart.toFloat(),0f,paint)
when(soundWay){
AppConstants.SOUND_TYPE_UK -> {
canvas.drawBitmap(thumb,0f,0f,paint) //左边
canvas.drawText(uk, thumb.width/2f + paddingStart , h/2 + ((paint.fontMetrics.bottom - paint.fontMetrics.top)/2) - paint.fontMetrics.bottom,paint)
}
AppConstants.SOUND_TYPE_US -> {
canvas.drawBitmap(thumb,strack.width - thumb.width.toFloat(), 0f,paint) //右边
canvas.drawText(us,(w - thumb.width - paddingRight) + thumb.width/2f,h/2 + ((paint.fontMetrics.bottom - paint.fontMetrics.top)/2) - paint.fontMetrics.bottom,paint)
}
}
}
override fun onTouchEvent(event : MotionEvent) : Boolean {
when(event.action){
MotionEvent.ACTION_UP -> {
soundWay = if (soundWay == AppConstants.SOUND_TYPE_UK) AppConstants.SOUND_TYPE_US else AppConstants.SOUND_TYPE_UK
soundWayChange.value = soundWay
invalidate()
}
}
return true
}
/** 设置默认发音 */
fun setSoundWay(way : Int){
soundWay = way
invalidate()
}
}

+ 10
- 0
app/src/main/res/drawable/shape_switch_thumb.xml View File

<?xml version="1.0" encoding="utf-8"?><!--滑块图片-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="24dp" android:width="40dp" />
<corners android:radius="12dp" />
<solid android:color="@color/white" />
<stroke
android:width="1dp"
android:color="@color/gray_1" />
</shape>

+ 8
- 0
app/src/main/res/drawable/shape_switch_track.xml View File

<?xml version="1.0" encoding="utf-8"?>
<!--背景图片:滑动条-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="24dp" android:width="50dp" />
<corners android:radius="12dp" />
<solid android:color="@color/gray_1" />
</shape>

+ 7
- 1
app/src/main/res/layout/activity_learn_exam_word.xml View File

xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".module.learn.LearnExamWordActivity">
android:background="@color/white"
tools:context=".module.learn.LearnExamActivity">

<include
android:id="@+id/inc_learn_title"
layout="@layout/inc_learn_title" />



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

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

app:layout_constraintTop_toTopOf="@+id/button_2" app:layout_constraintTop_toTopOf="@+id/button_2"
app:strokeColor="@color/theme_color" app:strokeColor="@color/theme_color"
app:strokeWidth="@dimen/line_height" app:strokeWidth="@dimen/line_height"
android:visibility="gone"
tools:visibility="visible"/>
/>


<Button <Button
android:id="@+id/button_2" android:id="@+id/button_2"

+ 112
- 0
app/src/main/res/layout/inc_learn_test_statistic.xml View File

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>
<variable
name="examType"
type="Long" />
<variable
name="totalNum"
type="Long" />
<variable
name="progressNum"
type="Integer" />
<!--时间 : 毫秒-->
<variable
name="time"
type="Long" />
<variable
name="correctNum"
type="Long" />
<variable
name="errorNum"
type="Long" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".ui.mainlearncenter.com_fragment.TestingStatisticFragment">


<!--测试数量进度-->
<ProgressBar
android:id="@+id/progress_test_number"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_2"
android:indeterminateOnly="false"
app:layout_constraintTop_toTopOf="parent"
android:progressDrawable="@drawable/progress_statistics"
android:max="@{(int)totalNum}"
android:progress="@{progressNum}"/>

<!--错误数量-->
<TextView
android:id="@+id/tv_error_number"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_34"
android:textSize="@dimen/dp_12"
android:textColor="@color/color_6"
android:drawablePadding="@dimen/dp_2"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progress_test_number"
android:drawableLeft="@mipmap/test_error_icon"
android:text="@{@string/error_number_format(errorNum)}"
android:layout_marginRight="@dimen/dp_16"
android:gravity="center"/>

<!--正确数量-->
<TextView
android:id="@+id/tv_correct_number"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_34"
android:textSize="@dimen/dp_12"
android:textColor="@color/color_6"
android:drawableLeft="@mipmap/test_right_icon"
android:drawablePadding="@dimen/dp_2"
app:layout_constraintRight_toLeftOf="@+id/tv_error_number"
app:layout_constraintTop_toBottomOf="@+id/progress_test_number"
android:text="@{@string/correct_number_foramt(correctNum)}"
android:layout_marginRight="@dimen/dp_16"
android:gravity="center" />

<!--测试类型-->
<TextView
android:id="@+id/tv_test_type_name"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_34"
android:textSize="@dimen/dp_12"
android:textColor="@color/color_6"
android:drawablePadding="@dimen/dp_2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progress_test_number"
tools:text="学后总测试"
android:gravity="center"
android:layout_marginLeft="@dimen/dp_16"
android:layout_marginRight="@dimen/dp_16"
android:singleLine="true"
android:maxEms="6"
android:ellipsize="end"
app:learnTestExamType="@{examType}"
/>

<!--测试时间-->
<TextView
android:id="@+id/tv_test_time"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_34"
android:textSize="@dimen/dp_12"
android:textColor="@color/color_6"
tools:text="00:00:00"
app:statisticsTotalTime="@{time}"
android:gravity="center"
app:layout_constraintLeft_toRightOf="@+id/tv_test_type_name"
app:layout_constraintTop_toBottomOf="@+id/progress_test_number"
android:layout_marginLeft="@dimen/dp_16"/>


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

+ 70
- 0
app/src/main/res/layout/inc_learn_title.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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="0dp"
android:layout_height="@dimen/title_bar_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:showIn="@layout/activity_learn_exam_word">

<ImageView
android:id="@+id/img_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:paddingStart="@dimen/global_spacing"
android:scaleType="centerInside"
android:src="@drawable/ic_back"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="66dp"
android:ellipsize="end"
android:maxLines="1"
android:text="小学英语单词课程小学英语单词课程小学英语单词课程"
android:textColor="@color/main_text_color"
android:textSize="@dimen/bigSize"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tv_num_progress"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="66dp" />

<TextView
android:id="@+id/tv_num_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="66dp"
android:text="90/100"
android:textColor="@color/main_text_color"
android:textSize="@dimen/bigSize"
android:textStyle="bold"
android:visibility="visible"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_title"
app:layout_constraintTop_toTopOf="parent" />


<com.xkl.cdl.widget.VoiceSwitch
android:id="@+id/voice_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="@dimen/global_spacing"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

BIN
app/src/main/res/mipmap-xxhdpi/test_score_level_1.png View File


BIN
app/src/main/res/mipmap-xxhdpi/test_score_level_2.png View File


BIN
app/src/main/res/mipmap-xxhdpi/test_score_level_3.png View File


+ 0
- 1
app/src/main/res/values/attrs.xml View File

<attr name="progressNumber" format="integer"/> <!--弧形进度--> <attr name="progressNumber" format="integer"/> <!--弧形进度-->
<attr name="backgroundColor" format="color"/> <!--背景颜色--> <attr name="backgroundColor" format="color"/> <!--背景颜色-->
</declare-styleable> </declare-styleable>

</resources> </resources>

+ 1
- 0
app/src/main/res/values/colors.xml View File

<color name="gray_3">#FAFAFA</color> <color name="gray_3">#FAFAFA</color>
<color name="green_1">#40A540</color> <color name="green_1">#40A540</color>
<color name="red_1">#FFF26255</color> <color name="red_1">#FFF26255</color>
<color name="red_2">#F7874F</color>




<color name="num0">#E50213</color> <color name="num0">#E50213</color>

+ 4
- 3
lib/common/build.gradle View File

targetSdk rootProject.ext.android.target_sdk_version targetSdk rootProject.ext.android.target_sdk_version
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
// multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro" consumerProguardFiles "consumer-rules.pro"
} }
} }
} }
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }


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'
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}
//RxJava RxAndroid //RxJava RxAndroid
api customDependencies.RxJava api customDependencies.RxJava
api customDependencies.RxAndroid api customDependencies.RxAndroid
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'







+ 26
- 4
lib/common/src/main/java/com/suliang/common/base/BaseDialogFragment.kt View File

package com.suliang.common.base package com.suliang.common.base


import android.R.attr
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable


import android.util.DisplayMetrics import android.util.DisplayMetrics


import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import android.R.attr.tag
import androidx.fragment.app.Fragment
import java.lang.IllegalStateException




/** /**
dialog!!.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) dialog!!.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
} }
} }


abstract fun initFragment() abstract fun initFragment()


/** 是否布局背景透明 */ /** 是否布局背景透明 */
open fun isTransparent():Boolean { open fun isTransparent():Boolean {
return true return true
} }
override fun show(manager : FragmentManager, tag : String?) {
//避免重复添加的异常 java.lang.IllegalStateException: Fragment already added
val fragment = manager.findFragmentByTag(tag)
if (fragment != null) {
val fragmentTransaction = manager.beginTransaction()
fragmentTransaction.remove(fragment)
fragmentTransaction.commitAllowingStateLoss()
}
//避免状态丢失的异常 java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
try {
super.show(manager, tag)
} catch (e : IllegalStateException) {
e.printStackTrace()
}
}

} }

+ 4
- 2
lib/common/src/main/java/com/suliang/common/base/LoadingDialog.kt View File



companion object{ companion object{
@JvmStatic @JvmStatic
fun newInstance(msg : String): LoadingDialog {
fun newInstance(msg : String?): LoadingDialog {
return LoadingDialog().apply { return LoadingDialog().apply {
arguments?.putString(AppConfig.INTENT_1,msg)
if (!msg.isNullOrEmpty()) {
arguments?.putString(AppConfig.INTENT_1, msg)
}
} }
} }
} }

+ 1
- 1
lib/common/src/main/java/com/suliang/common/base/ViewBehavior.kt View File

interface ViewBehavior { interface ViewBehavior {
/** 是否显示视图 */ /** 是否显示视图 */
fun showHideLoading(isShow: Boolean) fun showHideLoading(isShow: Boolean)
/** 显示toast */ /** 显示toast */
fun showToast(msg: ToastEvent) fun showToast(msg: ToastEvent)



+ 8
- 181
lib/common/src/main/java/com/suliang/common/base/activity/BaseActivity.kt View File

package com.suliang.common.base.activity package com.suliang.common.base.activity


import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.suliang.common.base.LoadingDialog
import com.suliang.common.extension.initBinding import com.suliang.common.extension.initBinding
import com.suliang.common.util.ActivityStackManager
import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly
import com.suliang.common.base.ViewBehavior
import com.suliang.common.util.LogUtil




/** /**
* 基类Activity * 基类Activity
*/ */
abstract class BaseActivity<VB : ViewBinding> : LifecycleLogActivity(), ViewBehavior {
private var _binding: VB? = null
val binding: VB
abstract class BaseActivity<VB : ViewBinding> : UIBaseActivity() {
private var _binding : VB? = null
val binding : VB
get() = _binding!! get() = _binding!!

// val binding : VB by lazy {
// initViewBinding()
// }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//入栈
ActivityStackManager.addActivity(this)
initFirst()
initActivity(savedInstanceState)
loadData()
}

/***
* 实例化binding,
*/
protected open fun initFirst() {
setLayoutBefore()
override fun setContentView() {
_binding = initBinding(layoutInflater) _binding = initBinding(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
initStatusBar()
setLayoutAfter()
} }

/**
* 状态栏初始化方法
*/
open fun initStatusBar() {
statusBarOnly {
fitWindow = true
color = Color.WHITE
light = true
}
}

override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
ActivityStackManager.removeActivity(this)
_binding = null _binding = null
} }

/** 布局前操作 : 空实现 */
open fun setLayoutBefore() {

}

/**
* 布局后操作:空实现
*/
open fun setLayoutAfter() {

}

/**
* onCreate中给与app中用户的具体实现
* binding 与 ViewModel都已实现好
* @param savedInstanceState Bundle?
*/
abstract fun initActivity(savedInstanceState: Bundle?)

/** onCreate()最后实现的数据加载调用 */
abstract fun loadData()

private val loadingDialog by lazy {
LoadingDialog()
}

/** toast提示 */
override fun showToast(event: ToastEvent) {
val showTime = if (event.showLong) Toast.LENGTH_LONG else Toast.LENGTH_LONG
event.content?.let {
Toast.makeText(this, it, showTime).show()
} ?: let {
Toast.makeText(this, event.contentResId!!, showTime).show()
}
}

protected fun showToast(text: String, showLong: Boolean = false) {
showToast(ToastEvent(content = text, showLong = showLong))
}

protected fun showToast(@StringRes resId: Int, showLong: Boolean = false) {
showToast(ToastEvent(contentResId = resId, showLong = showLong))
}

override fun showHideLoading(isShow: Boolean) {
if (isShow) {
loadingDialog.show(supportFragmentManager, javaClass.name)
} else if (loadingDialog.activity != null) {
loadingDialog.dismiss()
}
}

/** 单独封装一个方法,用于进行无参数的activity的跳转 */
override fun startActivity(clazz: Class<*>) {
startActivity(Intent(this, clazz))
}

//覆写该方法,避免多次跳转失败的问题
override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {
if (checkDoubleClick(intent)) {
super.startActivityForResult(intent, requestCode, options)
} else {
LogUtil.e("多次跳转无效操作--> $intent")
}
}

// private var mActivityJumpTag : String = "" // activity跳转tag
private var clickTime = 0L //点击时间

//避免多次点击跳转到同一个activity 和 避免短时间内多次点击跳转到不同activity
private fun checkDoubleClick(intent: Intent): Boolean {
val currentTime = System.currentTimeMillis()
var result = true
var tag: String = ""
if (intent.component != null) { //显示跳转
tag = intent.component!!.className
} else if (intent.action != null) { //隐式跳转
tag = intent.action!!
} else {
return true
}

// result = tag == mActivityJumpTag && currentTime - clickTime >= 500
result = currentTime - clickTime >= 500

// mActivityJumpTag = tag
clickTime = currentTime

return result
}

// 反射实现 ViewBinding
// @SuppressWarnings("unchecked")
// fun initBinding(inflater: LayoutInflater): VB {
// val vbClass =
// (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
// val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java)
// return inflate.invoke(null, inflater) as VB
// }

// https://juejin.cn/post/6926007534188773384#heading-1
// private fun initViewBinding(): VB{
// return reflexViewBinding(this::class.java,layoutInflater)
// }
// private fun <V : ViewBinding?> reflexViewBinding(aClass: Class<*>, from: LayoutInflater?): V {
// return try {
// val genericSuperclass: Type = aClass.genericSuperclass
// if (genericSuperclass is ParameterizedType) {
// val actualTypeArguments: Array<Type> =
// (Objects.requireNonNull(genericSuperclass) as ParameterizedType).getActualTypeArguments()
// for (actualTypeArgument in actualTypeArguments) {
// var tClass: Class<*>
// tClass = try {
// actualTypeArgument as Class<*>
// } catch (e: Exception) {
// continue
// }
// if (ViewBinding::class.java.isAssignableFrom(tClass)) {
// val inflate: Method = tClass.getMethod("inflate", LayoutInflater::class.java)
// return inflate.invoke(null, from) as V
// }
// }
// }
// reflexViewBinding(aClass.superclass, from)
// } catch (e: Exception) {
// e.printStackTrace()
// throw RuntimeException(e.message, e)
// }
// }


} }

+ 4
- 1
lib/common/src/main/java/com/suliang/common/base/activity/BaseActivityVM.kt View File



import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.suliang.common.base.viewmodel.BaseViewModel import com.suliang.common.base.viewmodel.BaseViewModel
import com.suliang.common.util.LogUtil


/** /**
* Activity DataBinding 与 ViewModel基类,封装DataBinding和ViewModel * Activity DataBinding 与 ViewModel基类,封装DataBinding和ViewModel


vm.pageEvent.observe(this) { startActivity(it) } vm.pageEvent.observe(this) { startActivity(it) }
vm.toastEvent.observe(this){ showToast(it) } vm.toastEvent.observe(this){ showToast(it) }
vm.loadingEvent.observe(this) { showHideLoading(it)}
vm.loadingEvent.observe(this) {
LogUtil.e("监听到 activity vm 的 loadingEvent isShow $it")
showHideLoading(it)}


} }



+ 196
- 0
lib/common/src/main/java/com/suliang/common/base/activity/UIBaseActivity.kt View File

package com.suliang.common.base.activity

import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.widget.Toast
import androidx.annotation.StringRes
import com.suliang.common.base.LoadingDialog
import com.suliang.common.base.ViewBehavior
import com.suliang.common.util.ActivityStackManager
import com.suliang.common.util.LogUtil
import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly

/**
* author suliang
* create 2022/4/7 11:45
* Describe:
*/
abstract class UIBaseActivity : LifecycleLogActivity(), ViewBehavior {
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
//入栈
ActivityStackManager.addActivity(this)
initFirst()
initActivity(savedInstanceState)
loadData()
}
/***
* 实例化binding,
*/
protected open fun initFirst() {
setLayoutBefore()
initContentView()
initStatusBar()
setLayoutAfter()
}
private fun initContentView() {
setContentView()
}
abstract fun setContentView()
/**
* 状态栏初始化方法
*/
open fun initStatusBar() {
statusBarOnly {
fitWindow = true
color = Color.WHITE
light = true
}
}
override fun onDestroy() {
super.onDestroy()
ActivityStackManager.removeActivity(this)
}
/** 布局前操作 : 空实现 */
open fun setLayoutBefore() {}
/**
* 布局后操作:空实现
*/
open fun setLayoutAfter() {}
/**
* onCreate中给与app中用户的具体实现
* binding 与 ViewModel都已实现好
* @param savedInstanceState Bundle?
*/
abstract fun initActivity(savedInstanceState : Bundle?)
/** onCreate()最后实现的数据加载调用 */
abstract fun loadData()
private var loadingDialog : LoadingDialog? = null
private var dialogList = arrayListOf<String>()
/** toast提示 */
override fun showToast(event : ToastEvent) {
val showTime = if (event.showLong) Toast.LENGTH_LONG else Toast.LENGTH_LONG
event.content?.let {
Toast.makeText(this, it, showTime).show()
}
?: let {
Toast.makeText(this, event.contentResId!!, showTime).show()
}
}
protected fun showToast(text : String, showLong : Boolean = false) {
showToast(ToastEvent(content = text, showLong = showLong))
}
protected fun showToast(@StringRes resId : Int, showLong : Boolean = false) {
showToast(ToastEvent(contentResId = resId, showLong = showLong))
}
override fun showHideLoading(isShow : Boolean) {
val findFragment = supportFragmentManager.findFragmentByTag("loading")
if (isShow) { //显示
findFragment?.let {
if (findFragment is LoadingDialog) {
findFragment.dialog?.show()
}
}
?: let {
val loadingDialog = LoadingDialog.newInstance("")
loadingDialog.show(supportFragmentManager, "loading")
}
} else { //关闭
findFragment?.let {
if (it is LoadingDialog) {
it.dismissAllowingStateLoss()
}
}
}
}
/** 单独封装一个方法,用于进行无参数的activity的跳转 */
override fun startActivity(clazz : Class<*>) {
startActivity(Intent(this, clazz))
}
//覆写该方法,避免多次跳转失败的问题
override fun startActivityForResult(intent : Intent, requestCode : Int, options : Bundle?) {
if (checkDoubleClick(intent)) {
super.startActivityForResult(intent, requestCode, options)
} else {
LogUtil.e("多次跳转无效操作--> $intent")
}
}
private var clickTime = 0L //点击时间
//避免多次点击跳转到同一个activity 和 避免短时间内多次点击跳转到不同activity
private fun checkDoubleClick(intent : Intent) : Boolean {
val currentTime = System.currentTimeMillis()
var result = true
var tag : String = ""
if (intent.component != null) { //显示跳转
tag = intent.component!!.className
} else if (intent.action != null) { //隐式跳转
tag = intent.action!!
} else {
return true
}
result = currentTime - clickTime >= 500
clickTime = currentTime
return result
}
}
// 反射实现 ViewBinding
// @SuppressWarnings("unchecked")
// fun initBinding(inflater: LayoutInflater): VB {
// val vbClass =
// (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
// val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java)
// return inflate.invoke(null, inflater) as VB
// }

// https://juejin.cn/post/6926007534188773384#heading-1
// private fun initViewBinding(): VB{
// return reflexViewBinding(this::class.java,layoutInflater)
// }
// private fun <V : ViewBinding?> reflexViewBinding(aClass: Class<*>, from: LayoutInflater?): V {
// return try {
// val genericSuperclass: Type = aClass.genericSuperclass
// if (genericSuperclass is ParameterizedType) {
// val actualTypeArguments: Array<Type> =
// (Objects.requireNonNull(genericSuperclass) as ParameterizedType).getActualTypeArguments()
// for (actualTypeArgument in actualTypeArguments) {
// var tClass: Class<*>
// tClass = try {
// actualTypeArgument as Class<*>
// } catch (e: Exception) {
// continue
// }
// if (ViewBinding::class.java.isAssignableFrom(tClass)) {
// val inflate: Method = tClass.getMethod("inflate", LayoutInflater::class.java)
// return inflate.invoke(null, from) as V
// }
// }
// }
// reflexViewBinding(aClass.superclass, from)
// } catch (e: Exception) {
// e.printStackTrace()
// throw RuntimeException(e.message, e)
// }
// }


+ 7
- 10
lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragment.kt View File

import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.suliang.common.base.LoadingDialog import com.suliang.common.base.LoadingDialog
import com.suliang.common.base.ViewBehavior import com.suliang.common.base.ViewBehavior
import com.suliang.common.base.activity.BaseActivity
import com.suliang.common.base.activity.ToastEvent import com.suliang.common.base.activity.ToastEvent
import com.suliang.common.extension.initBinding import com.suliang.common.extension.initBinding
import com.suliang.common.util.LogUtil


/** /**
* Fragment ViewBinding基类,封装ViewDataBinding 自带懒加载 * Fragment ViewBinding基类,封装ViewDataBinding 自带懒加载
* 界面显示出来后再加载数据 * 界面显示出来后再加载数据
*/ */
abstract fun loadData() abstract fun loadData()



private val loadingDialog by lazy {
LoadingDialog()
}


/** toast提示 */ /** toast提示 */
override fun showToast(event: ToastEvent) { override fun showToast(event: ToastEvent) {
showToast(ToastEvent(contentResId = resId, showLong = showLong)) showToast(ToastEvent(contentResId = resId, showLong = showLong))
} }


/** 调用父类的 加载框 */
override fun showHideLoading(isShow: Boolean) { override fun showHideLoading(isShow: Boolean) {
if (isShow) {
loadingDialog.show(childFragmentManager, javaClass.name)
} else if (loadingDialog.activity != null){
loadingDialog.dismiss()
synchronized(this){
// LogUtil.e(" BaseFragment showHideLoding -> $isShow")
(activity as BaseActivity<*>).showHideLoading(isShow)
} }
} }



+ 6
- 1
lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragmentVM.kt View File

import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.suliang.common.base.viewmodel.BaseViewModel import com.suliang.common.base.viewmodel.BaseViewModel
import com.suliang.common.util.LogUtil


/** /**
* Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel 自带懒加载 * Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel 自带懒加载
* 如果vm 初始的为已存在的(即父fragmnet或父Activity)vm,则重写initFirst,避免出现loading显示异常的问题
* @property VB : ViewDataBinding * @property VB : ViewDataBinding
* @property VM: ViewModel * @property VM: ViewModel
*/ */
vm = initViewModel() vm = initViewModel()
vm.pageEvent.observe(this) { startActivity(it) } vm.pageEvent.observe(this) { startActivity(it) }
vm.toastEvent.observe(this){ showToast(it) } vm.toastEvent.observe(this){ showToast(it) }
vm.loadingEvent.observe(this) { showHideLoading(it)}
vm.loadingEvent.observe(this) {
LogUtil.e("监听到 fragment vm 的 loadingEvent isShow $it")
showHideLoading(it)
}
} }


abstract fun initViewModel(): VM abstract fun initViewModel(): VM

+ 5
- 11
lib/common/src/main/java/com/suliang/common/base/viewmodel/BaseViewModel.kt View File

super.onCleared() super.onCleared()
LogUtil.i("${javaClass.name} onCleared() ") LogUtil.i("${javaClass.name} onCleared() ")
} }

var loadingEvent = MutableLiveData<Boolean>()
private set

var toastEvent = MutableLiveData<ToastEvent>()
private set

var pageEvent = MutableLiveData<Class<*>>()
private set
val loadingEvent = MutableLiveData<Boolean>()
val toastEvent = MutableLiveData<ToastEvent>()
val pageEvent = MutableLiveData<Class<*>>()


override fun showHideLoading(isShow: Boolean) { override fun showHideLoading(isShow: Boolean) {
loadingEvent.postValue(isShow) loadingEvent.postValue(isShow)
} }

override fun showToast(msg: ToastEvent) { override fun showToast(msg: ToastEvent) {
toastEvent.postValue(msg) toastEvent.postValue(msg)
} }

override fun startActivity(clazz: Class<*>) { override fun startActivity(clazz: Class<*>) {
pageEvent.postValue(clazz) pageEvent.postValue(clazz)
} }

+ 7
- 0
lib/common/src/main/java/com/suliang/common/extension/RxScheduler.kt.kt View File

upstream!!.subscribeOn(Schedulers.from(AppExecutors.diskIO)) upstream!!.subscribeOn(Schedulers.from(AppExecutors.diskIO))
.observeOn(Schedulers.from(AppExecutors.diskIO)) .observeOn(Schedulers.from(AppExecutors.diskIO))
} }
}

fun <T> diskIo2Main(): ObservableTransformer<T, T> {
return ObservableTransformer { upstream ->
upstream!!.subscribeOn(Schedulers.from(AppExecutors.diskIO))
.observeOn(Schedulers.from(AppExecutors.mainThread))
}
} }

Loading…
Cancel
Save