<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> |
<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" /> |
注意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 |
// } | // } | ||||
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' | |||||
} | } |
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" |
/** 测试通过:>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 //中文 | |||||
} | } |
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() | ||||
} | } |
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 //答案是否是音频 | |||||
} |
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()) | |||||
} | |||||
} | |||||
} | |||||
} | } |
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>, | |||||
) { | |||||
} | } | ||||
} | } |
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,来处理对应的动作 | |||||
} |
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) | |||||
} | } | ||||
} | } |
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) | |||||
} | |||||
} | |||||
} |
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) | |||||
} | |||||
} |
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)) | |||||
} | |||||
} | } | ||||
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 | |||||
} | |||||
} | } |
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()) | |||||
} | } | ||||
} | } |
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() | |||||
// } | |||||
} | } |
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() | |||||
} | |||||
} |
<?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> |
<?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> |
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> |
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" |
<?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> |
<?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> |
<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> |
<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> |
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' | |||||
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() | |||||
} | |||||
} | |||||
} | } |
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) | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
interface ViewBehavior { | interface ViewBehavior { | ||||
/** 是否显示视图 */ | /** 是否显示视图 */ | ||||
fun showHideLoading(isShow: Boolean) | fun showHideLoading(isShow: Boolean) | ||||
/** 显示toast */ | /** 显示toast */ | ||||
fun showToast(msg: ToastEvent) | fun showToast(msg: ToastEvent) | ||||
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) | |||||
// } | |||||
// } | |||||
} | } |
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)} | |||||
} | } | ||||
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) | |||||
// } | |||||
// } | |||||
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) | |||||
} | } | ||||
} | } | ||||
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 |
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) | ||||
} | } |
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)) | |||||
} | |||||
} | } |