@@ -1,5 +1,8 @@ | |||
<component name="ProjectCodeStyleConfiguration"> | |||
<code_scheme name="Project" version="173"> | |||
<ComposeCustomCodeStyleSettings> | |||
<option name="USE_CUSTOM_FORMATTING_FOR_MODIFIERS" value="false" /> | |||
</ComposeCustomCodeStyleSettings> | |||
<DBN-PSQL> | |||
<case-options enabled="true"> | |||
<option name="KEYWORD_CASE" value="lower" /> | |||
@@ -25,12 +28,9 @@ | |||
</formatting-settings> | |||
</DBN-SQL> | |||
<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" /> | |||
</JetCodeStyleSettings> | |||
<DBN-PSQL> | |||
@@ -172,15 +172,17 @@ | |||
</codeStyleSettings> | |||
<codeStyleSettings language="kotlin"> | |||
<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="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" /> | |||
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" /> | |||
<option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" /> | |||
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" /> | |||
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" /> | |||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" /> | |||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" /> | |||
<option name="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> | |||
</code_scheme> | |||
</component> |
@@ -25,11 +25,15 @@ | |||
<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_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/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/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_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_main.xml" value="0.5" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> | |||
@@ -46,6 +50,7 @@ | |||
<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_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/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" /> |
@@ -37,6 +37,17 @@ image包: 实现了图片加载的封装 | |||
注意include为merge的坑,viewbinding中需动态绑定 https://juejin.cn/post/6844904065655111693 | |||
讨论点: | |||
状态加载的判断: 加载复习状态页没问题,学前总测试状态页也没问题, | |||
但如何判断课程学习完成,目前是课程学习完成后才判断进入课程学后总测试 | |||
我的考虑是在进入课程详情界面时,对所有课时的学习进度进行判断,不包含课时的学后测试,当所有课时都学习完了,则进入学后总测试的状态页, | |||
而在学习中,同样做引导课时前测试,学习,课时后测试, | |||
课时后测试将包含 小游戏练习、再测一次、重新学习、下一步(进入学后总测试)或者下一课时(为当前课时的下一课时), | |||
需要考虑的是,下一步或者下一课时的判断为循环判断,如,共10课时,现在点击学习的第5个,如果后面几个课时都学习完后,而1课时没有学习完成,下一课时将直接进入1课时的学习, | |||
而下一步是在所有课时都学习完成的情况下,直接进入学后总测试的界面。 | |||
Kotlin的单例模式: https://developer.aliyun.com/article/642618 |
@@ -50,6 +50,7 @@ android { | |||
// } | |||
compileOptions { | |||
coreLibraryDesugaringEnabled true //为了使用jdk8的脱糖属性 | |||
sourceCompatibility JavaVersion.VERSION_1_8 | |||
targetCompatibility JavaVersion.VERSION_1_8 | |||
} | |||
@@ -69,15 +70,15 @@ android { | |||
dependencies { | |||
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 '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} | |||
testImplementation rootProject.ext.dependencies_testImplementation.junit | |||
@@ -94,4 +95,6 @@ dependencies { | |||
//Lottie | |||
implementation customDependencies.Lottie | |||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | |||
} |
@@ -25,7 +25,7 @@ | |||
android:name=".module.learn.LearnExamSpellActivity" | |||
android:exported="true" /> | |||
<activity | |||
android:name=".module.learn.LearnExamWordActivity" | |||
android:name=".module.learn.LearnExamActivity" | |||
android:exported="true" /> | |||
<activity | |||
android:name=".module.learn.LearnSpellActivity" |
@@ -113,6 +113,34 @@ object AppConstants { | |||
/** 测试通过:>80 < 90 继续加油 <80 悲伤 */ | |||
const val TEST_SCORE_LEVEL_1 = 80 | |||
/** 测试通过 >= 太棒了 */ | |||
/** 测试通过 >= 太棒了 90 */ | |||
const val TEST_SCORE_LEVEL_2 = 90 | |||
const val ACTION_START_BEFORE_TEST = 1 | |||
const val ACTION_START_AFTER_TEST = 2 | |||
const val ACTION_START_TOTAL_TEST = 3 | |||
const val ACTION_START_TOTAL_AFTER_TEST = 4 | |||
//测试的题目类型 | |||
/** 选择题类型 */ | |||
const val TEST_QUEST_TYPE_CHOICE = 1 | |||
/** 口语对话测试 */ | |||
const val TEST_QUEST_TYPE_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,15 +17,28 @@ class DataTransferHolder private constructor(){ | |||
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") | |||
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(){ | |||
dataMap.clear() | |||
} |
@@ -0,0 +1,26 @@ | |||
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 //答案是否是音频 | |||
} |
@@ -1,7 +1,10 @@ | |||
package com.xkl.cdl.data.manager | |||
import android.util.Size | |||
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.ExamBean | |||
import java.io.File | |||
import java.math.BigDecimal | |||
import java.math.RoundingMode | |||
@@ -13,25 +16,27 @@ import java.text.DecimalFormat | |||
* Describe: 课程管理类 | |||
*/ | |||
object CourseManager { | |||
/** 项目对应的课程包 */ | |||
val subjectWithCoursePackMap = hashMapOf<Int, List<CoursePack>>() | |||
/** | |||
* 获取对应项目的课程数量 | |||
* @param subjectId Int 项目 | |||
* @return Int 课程数量 | |||
*/ | |||
fun getSubjectForCourseSize(subjectId: Int): Int { | |||
fun getSubjectForCourseSize(subjectId : Int) : Int { | |||
return subjectWithCoursePackMap[subjectId]?.let { | |||
var count = 0 | |||
it.forEach { coursePack -> | |||
count += coursePack.childrenCourses?.size ?: 0 | |||
count += coursePack.childrenCourses?.size | |||
?: 0 | |||
} | |||
count | |||
} ?: 0 | |||
} | |||
?: 0 | |||
} | |||
/** | |||
* 搜索项目下的课程包 | |||
* @param subjectId Int 项目id | |||
@@ -39,7 +44,7 @@ object CourseManager { | |||
* @return MutableList<CoursePack>? 结果值 | |||
*/ | |||
@Synchronized | |||
fun filterSearchCoursePack(subjectId: Int, searchValue: String?): MutableList<CoursePack>? { | |||
fun filterSearchCoursePack(subjectId : Int, searchValue : String?) : MutableList<CoursePack>? { | |||
return if (searchValue.isNullOrEmpty() || searchValue.trim().isEmpty()) { | |||
subjectWithCoursePackMap[subjectId]?.toMutableList() | |||
} else { | |||
@@ -54,7 +59,7 @@ object CourseManager { | |||
} | |||
} | |||
} | |||
/** | |||
* 根据subjectId,courseId 获取CoursePack | |||
* @param subjectId Int 项目Id | |||
@@ -62,7 +67,7 @@ object CourseManager { | |||
* @return CoursePack? 课程包 | |||
*/ | |||
@Synchronized | |||
fun getCoursePackWithCoursePackId(subjectId: Int, coursePackId: Long): CoursePack? { | |||
fun getCoursePackWithCoursePackId(subjectId : Int, coursePackId : Long) : CoursePack? { | |||
return subjectWithCoursePackMap[subjectId]?.let { | |||
//返回很符合条件的第一个元素 | |||
it.firstOrNull { | |||
@@ -70,7 +75,7 @@ object CourseManager { | |||
} | |||
} | |||
} | |||
/*** | |||
* 检查课程包下课程的db数据文件是否存在,不存在,则复制过去 | |||
*/ | |||
@@ -79,9 +84,8 @@ object CourseManager { | |||
entry.value.forEach { coursePack -> | |||
coursePack.childrenCourses.forEach { | |||
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()) { | |||
FileUtil.copyAsset(it.dbPathName, file) | |||
@@ -90,14 +94,14 @@ object CourseManager { | |||
} | |||
} | |||
} | |||
/** | |||
* 用于显示的进度 | |||
* @param progress Double 课程的进度 | |||
* @return String | |||
*/ | |||
@JvmStatic | |||
fun useToShowProgress(progress: Double): String { | |||
fun useToShowProgress(progress : Double) : String { | |||
return when { | |||
progress == 0.0 -> "0" | |||
progress < 0.1 -> "0.1" | |||
@@ -108,5 +112,42 @@ object CourseManager { | |||
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()) | |||
} | |||
} | |||
} | |||
} |
@@ -2,10 +2,12 @@ package com.xkl.cdl.data.manager.db | |||
import android.annotation.SuppressLint | |||
import android.widget.Toast | |||
import androidx.core.database.getLongOrNull | |||
import com.suliang.common.util.AppGlobals | |||
import com.suliang.common.util.thread.AppExecutors | |||
import com.xkl.cdl.data.AppConstants | |||
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.manager.FilePathManager | |||
import net.sqlcipher.database.SQLiteDatabase | |||
@@ -21,9 +23,9 @@ object DBCourseManager { | |||
private const val SPOKEN = "XKL_SPOKEN_COURSE_DATA_KEY" | |||
private const val COMPOSITION = "XKL_LOCAL_COMPOSITION_DATA_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) { | |||
if (mDataBase != null && currentBase != base) { | |||
mDataBase?.close() | |||
@@ -37,27 +39,27 @@ object DBCourseManager { | |||
AppConstants.COURSE_TYPE_CHINESE_LITERACY -> LITERACY | |||
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) { | |||
AppExecutors.mainThread.run { | |||
Toast.makeText(AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG) | |||
Toast.makeText( | |||
AppGlobals.application, "课程数据获取失败,请重启应用或联系业务员", Toast.LENGTH_LONG | |||
) | |||
} | |||
} | |||
} | |||
} | |||
/**获取该课程的所有课时 | |||
* @param base DbControlBase | |||
* @param detail CourseDetail 课程信息 | |||
* @return List<Lesson> | |||
*/ | |||
@SuppressLint("Range") | |||
fun queryAllLesson(base: DbControlBase, detail: CourseDetail): List<Lesson> { | |||
fun queryAllLesson(base : DbControlBase, detail : CourseDetail) : List<Lesson> { | |||
val mutableList = mutableListOf<Lesson>() | |||
open(base) | |||
//聚合所有课时,先所有chapter_sort和word_sort排序,然后根据leesonId分组并聚合leeson_id到 | |||
@@ -75,38 +77,40 @@ object DBCourseManager { | |||
var positionIndex = 0 | |||
mDataBase?.rawQuery(rawQurySql, null)?.let { | |||
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 lessonName = when (base.courseType) { | |||
AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> it.getString(it.getColumnIndex("lesson_title")) | |||
else -> it.getString(it.getColumnIndex("lesson")) | |||
} | |||
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(",") | |||
}.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_ENGLISH_SPOKEN -> it.getInt(it.getColumnIndex("lesson_type")) | |||
else -> AppConstants.LESSON_TYPE_WORD | |||
} | |||
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 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 | |||
this.wordIds = wordIds //内容 | |||
totalNumber = this.wordIds.size //总数 | |||
@@ -126,41 +130,149 @@ object DBCourseManager { | |||
mutableList.last().lastLesson = true | |||
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)的随机个数测试数据 */ | |||
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>, | |||
) { | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
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,来处理对应的动作 | |||
} |
@@ -1,10 +1,13 @@ | |||
package com.xkl.cdl.dialog | |||
import android.view.View | |||
import androidx.core.content.ContextCompat | |||
import androidx.fragment.app.FragmentManager | |||
import com.suliang.common.base.BaseDialogFragment | |||
import com.suliang.common.extension.click | |||
import com.suliang.common.util.LogUtil | |||
import com.xkl.cdl.R | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.databinding.DialogLessonLearnBinding | |||
/** | |||
@@ -14,7 +17,7 @@ import com.xkl.cdl.databinding.DialogLessonLearnBinding | |||
*/ | |||
class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
private lateinit var dialogListener : DialogFragmentListener | |||
private lateinit var dialogListener: DialogFragmentListener | |||
override fun initFragment() { | |||
initClick() | |||
@@ -22,15 +25,15 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
initLessonBeforeTestOver() | |||
} | |||
fun initClick(){ | |||
dialogListener = if (parentFragment != null){ | |||
fun initClick() { | |||
dialogListener = if (parentFragment != null) { | |||
parentFragment as DialogFragmentListener | |||
}else{ | |||
} else { | |||
activity as DialogFragmentListener | |||
} | |||
} | |||
fun initLessonBeforeTest(){ | |||
fun initLessonBeforeTest() { | |||
binding.tvTitle.text = "课时学前测试" | |||
binding.tvLessonName.text = "章节课时名称" | |||
binding.tvCountTime.text = "测试题目时间" | |||
@@ -40,16 +43,16 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
binding.tvRight.click { | |||
LogUtil.e("Dialog -- > show()") | |||
dialogListener.rightClick() | |||
} | |||
binding.tvLeft.click { | |||
LogUtil.e("Dialog -- > hide()") | |||
dialogListener.leftClick() | |||
} | |||
} | |||
fun initLessonBeforeTestOver(){ | |||
fun initLessonBeforeTestOver() { | |||
binding.tvScore.run { | |||
visibility = View.VISIBLE | |||
text = "100分" | |||
@@ -68,20 +71,53 @@ class LearnDialog : BaseDialogFragment<DialogLessonLearnBinding>() { | |||
} | |||
} | |||
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) | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
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) | |||
} | |||
} | |||
} |
@@ -1,12 +0,0 @@ | |||
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) | |||
} | |||
} |
@@ -8,6 +8,7 @@ import com.suliang.common.base.fragment.BaseFragmentVM | |||
import com.suliang.common.extension.replaceFragment | |||
import com.suliang.common.util.LogUtil | |||
import com.xkl.cdl.R | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.data.manager.db.DbControlBase | |||
import com.xkl.cdl.databinding.FragmentCourseMainBinding | |||
import com.xkl.cdl.module.m_center_learn.CoursePackMainActivityViewModel | |||
@@ -64,7 +65,27 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
//学后总测 | |||
// 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)) | |||
} | |||
} | |||
@@ -1,51 +1,52 @@ | |||
package com.xkl.cdl.module.m_center_learn.coursechildren | |||
import android.provider.ContactsContract | |||
import androidx.lifecycle.MutableLiveData | |||
import com.suliang.common.base.activity.ToastEvent | |||
import com.suliang.common.base.viewmodel.BaseViewModel | |||
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.CourseDetail | |||
import com.xkl.cdl.data.bean.course.ExamBean | |||
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.repository.DataRepository | |||
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.Observer | |||
import io.reactivex.rxjava3.disposables.Disposable | |||
import java.util.* | |||
import kotlin.collections.ArrayList | |||
import kotlin.collections.HashMap | |||
/** | |||
* author suliang | |||
* create 2022/3/28 15:26 | |||
* Describe: | |||
* @property courseIndex 该课程在课程包的课程集合中的位置 | |||
* | |||
*/ | |||
class CourseMainFragmentViewModel(val courseIndex: Int) : BaseViewModel() { | |||
class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
//课程包主页的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 | |||
//获取课程的统计信息 | |||
//课程收藏本: 主要用于口语对话学习的数据 | |||
//获取课程的复习数据 | |||
@@ -53,38 +54,59 @@ class CourseMainFragmentViewModel(val courseIndex: Int) : BaseViewModel() { | |||
fun loadMain() : MutableLiveData<Boolean> { | |||
showHideLoading(true) | |||
val mutableLiveData = MutableLiveData<Boolean>() | |||
Observable.concat( | |||
DataRepository.getCourseStatistics().flatMap { | |||
courseDetail = it | |||
Observable.concat(DataRepository.getCourseStatistics().flatMap { | |||
courseDetail = 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 -> | |||
if (it is List<*>){ | |||
if (it is List<*>) { | |||
allLesson = it as List<Lesson> | |||
} | |||
} | |||
} | |||
override fun onError(e: Throwable?) { | |||
override fun onError(e : Throwable?) { | |||
e?.printStackTrace() | |||
showHideLoading(false) | |||
showToast(ToastEvent(content = "数据加载异常")) | |||
} | |||
override fun onComplete() { | |||
showHideLoading(false) | |||
mutableLiveData.postValue(true) | |||
} | |||
}) | |||
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 | |||
} | |||
} |
@@ -9,12 +9,14 @@ import com.suliang.common.extension.click | |||
import com.suliang.common.extension.setHtml | |||
import com.xkl.cdl.R | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.data.bean.course.ExamBean | |||
import com.xkl.cdl.data.manager.CourseManager | |||
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>() { | |||
@@ -30,18 +32,42 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
override fun initViewModel(): CourseMainFragmentViewModel { | |||
return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java] | |||
} | |||
override fun initFirst() { | |||
super.initFirst() | |||
//由于使用的是与父类相同的loadingEvent,所以将此evnet移除 | |||
vm.loadingEvent.removeObservers(this) | |||
} | |||
//传递过来的总测类型: 学前,学后 | |||
private var totalTestType = 0 | |||
//测试的数据 | |||
private var testData : List<ExamBean> = emptyList() | |||
override fun initFragment() { | |||
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) { | |||
AppConstants.TEST_TYPE_BEFORE_TOTAL -> { | |||
//学前总测 | |||
binding.run { | |||
tvTitle.setText(R.string.test_total_before_title) | |||
tvCountTip.text = "共25题,预计3分钟" | |||
button1.run { | |||
setText(R.string.start_learn) | |||
click { | |||
continueLearn(it) | |||
} | |||
} | |||
//按钮初始 | |||
button2.run { | |||
setText(R.string.start_test) | |||
@@ -51,7 +77,6 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
} | |||
} | |||
} | |||
AppConstants.TEST_TYPE_AFTER_TOTAL -> { | |||
//学后总测 | |||
binding.run { | |||
@@ -64,7 +89,6 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
when { | |||
it == -1.0 -> { //未测 | |||
tvMainTip.setText(R.string.test_total_after_tip_1) | |||
tvCountTip.text = "共25题,预计3分钟" | |||
button2.run { | |||
setText(R.string.test_type_after_total) | |||
click { view -> | |||
@@ -115,19 +139,17 @@ class CourseTotalTestFragment : BaseFragmentVM<FragmentCourseTotalTestBinding, C | |||
} | |||
} | |||
} | |||
override fun loadData() { | |||
} | |||
/**继续学习*/ | |||
private fun continueLearn(view: View) { | |||
showToast("继续学习") | |||
(requireParentFragment() as CourseMainFragment).changeFragment(1) | |||
} | |||
/** 开始测试 */ | |||
private fun startTest(view: View) { | |||
showToast("开始测试") | |||
LearnExamActivity.newInstance(requireContext()) | |||
} | |||
} |
@@ -19,7 +19,7 @@ import java.io.File | |||
import java.util.concurrent.TimeUnit | |||
@SuppressLint("CustomSplashScreen") | |||
class SplashActivity : BaseActivity<ActivitySplashBinding>(), LearnDialog.DialogFragmentListener { | |||
class SplashActivity : BaseActivity<ActivitySplashBinding>(){ | |||
override fun onCreate(savedInstanceState: Bundle?) { | |||
if (!isTaskRoot) { | |||
@@ -45,11 +45,11 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(), LearnDialog.Dialog | |||
// 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) | |||
@@ -72,20 +72,20 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(), LearnDialog.Dialog | |||
} | |||
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() | |||
// } | |||
} |
@@ -0,0 +1,100 @@ | |||
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() | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
<?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> |
@@ -0,0 +1,8 @@ | |||
<?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> |
@@ -4,6 +4,12 @@ | |||
xmlns:tools="http://schemas.android.com/tools" | |||
android:layout_width="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> |
@@ -100,8 +100,7 @@ | |||
app:layout_constraintTop_toTopOf="@+id/button_2" | |||
app:strokeColor="@color/theme_color" | |||
app:strokeWidth="@dimen/line_height" | |||
android:visibility="gone" | |||
tools:visibility="visible"/> | |||
/> | |||
<Button | |||
android:id="@+id/button_2" |
@@ -0,0 +1,112 @@ | |||
<?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> |
@@ -0,0 +1,70 @@ | |||
<?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> |
@@ -8,5 +8,4 @@ | |||
<attr name="progressNumber" format="integer"/> <!--弧形进度--> | |||
<attr name="backgroundColor" format="color"/> <!--背景颜色--> | |||
</declare-styleable> | |||
</resources> |
@@ -26,6 +26,7 @@ | |||
<color name="gray_3">#FAFAFA</color> | |||
<color name="green_1">#40A540</color> | |||
<color name="red_1">#FFF26255</color> | |||
<color name="red_2">#F7874F</color> | |||
<color name="num0">#E50213</color> |
@@ -12,7 +12,7 @@ android { | |||
targetSdk rootProject.ext.android.target_sdk_version | |||
versionCode 1 | |||
versionName "1.0" | |||
// multiDexEnabled true | |||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
consumerProguardFiles "consumer-rules.pro" | |||
} | |||
@@ -30,6 +30,7 @@ android { | |||
} | |||
} | |||
compileOptions { | |||
coreLibraryDesugaringEnabled true | |||
sourceCompatibility JavaVersion.VERSION_1_8 | |||
targetCompatibility JavaVersion.VERSION_1_8 | |||
} | |||
@@ -44,7 +45,7 @@ android { | |||
dependencies { | |||
// 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} | |||
testImplementation rootProject.ext.dependencies_testImplementation.junit | |||
rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v} | |||
@@ -63,7 +64,7 @@ dependencies { | |||
//RxJava RxAndroid | |||
api customDependencies.RxJava | |||
api customDependencies.RxAndroid | |||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | |||
@@ -1,5 +1,6 @@ | |||
package com.suliang.common.base | |||
import android.R.attr | |||
import android.graphics.Color | |||
import android.os.Bundle | |||
import android.view.LayoutInflater | |||
@@ -11,8 +12,11 @@ import com.suliang.common.extension.initBinding | |||
import android.graphics.drawable.ColorDrawable | |||
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 | |||
/** | |||
@@ -49,12 +53,30 @@ abstract class BaseDialogFragment<VB : ViewBinding> : DialogFragment() { | |||
dialog!!.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) | |||
} | |||
} | |||
abstract fun initFragment() | |||
/** 是否布局背景透明 */ | |||
open fun isTransparent():Boolean { | |||
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() | |||
} | |||
} | |||
} |
@@ -13,9 +13,11 @@ class LoadingDialog : BaseDialogFragment<LoadingFragmentBinding>() { | |||
companion object{ | |||
@JvmStatic | |||
fun newInstance(msg : String): LoadingDialog { | |||
fun newInstance(msg : String?): LoadingDialog { | |||
return LoadingDialog().apply { | |||
arguments?.putString(AppConfig.INTENT_1,msg) | |||
if (!msg.isNullOrEmpty()) { | |||
arguments?.putString(AppConfig.INTENT_1, msg) | |||
} | |||
} | |||
} | |||
} |
@@ -10,7 +10,7 @@ import com.suliang.common.base.activity.ToastEvent | |||
interface ViewBehavior { | |||
/** 是否显示视图 */ | |||
fun showHideLoading(isShow: Boolean) | |||
/** 显示toast */ | |||
fun showToast(msg: ToastEvent) | |||
@@ -1,199 +1,26 @@ | |||
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 com.suliang.common.base.LoadingDialog | |||
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 | |||
*/ | |||
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!! | |||
// 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) | |||
setContentView(binding.root) | |||
initStatusBar() | |||
setLayoutAfter() | |||
} | |||
/** | |||
* 状态栏初始化方法 | |||
*/ | |||
open fun initStatusBar() { | |||
statusBarOnly { | |||
fitWindow = true | |||
color = Color.WHITE | |||
light = true | |||
} | |||
} | |||
override fun onDestroy() { | |||
super.onDestroy() | |||
ActivityStackManager.removeActivity(this) | |||
_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) | |||
// } | |||
// } | |||
} |
@@ -2,6 +2,7 @@ package com.suliang.common.base.activity | |||
import androidx.viewbinding.ViewBinding | |||
import com.suliang.common.base.viewmodel.BaseViewModel | |||
import com.suliang.common.util.LogUtil | |||
/** | |||
* Activity DataBinding 与 ViewModel基类,封装DataBinding和ViewModel | |||
@@ -26,7 +27,9 @@ abstract class BaseActivityVM<VB : ViewBinding, VM : BaseViewModel> : BaseActivi | |||
vm.pageEvent.observe(this) { startActivity(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)} | |||
} | |||
@@ -0,0 +1,196 @@ | |||
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) | |||
// } | |||
// } | |||
@@ -12,8 +12,10 @@ import androidx.fragment.app.Fragment | |||
import androidx.viewbinding.ViewBinding | |||
import com.suliang.common.base.LoadingDialog | |||
import com.suliang.common.base.ViewBehavior | |||
import com.suliang.common.base.activity.BaseActivity | |||
import com.suliang.common.base.activity.ToastEvent | |||
import com.suliang.common.extension.initBinding | |||
import com.suliang.common.util.LogUtil | |||
/** | |||
* Fragment ViewBinding基类,封装ViewDataBinding 自带懒加载 | |||
@@ -82,12 +84,7 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment(),ViewBehavior { | |||
* 界面显示出来后再加载数据 | |||
*/ | |||
abstract fun loadData() | |||
private val loadingDialog by lazy { | |||
LoadingDialog() | |||
} | |||
/** toast提示 */ | |||
override fun showToast(event: ToastEvent) { | |||
@@ -107,11 +104,11 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment(),ViewBehavior { | |||
showToast(ToastEvent(contentResId = resId, showLong = showLong)) | |||
} | |||
/** 调用父类的 加载框 */ | |||
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) | |||
} | |||
} | |||
@@ -4,9 +4,11 @@ import androidx.databinding.ViewDataBinding | |||
import androidx.lifecycle.ViewModel | |||
import androidx.viewbinding.ViewBinding | |||
import com.suliang.common.base.viewmodel.BaseViewModel | |||
import com.suliang.common.util.LogUtil | |||
/** | |||
* Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel 自带懒加载 | |||
* 如果vm 初始的为已存在的(即父fragmnet或父Activity)vm,则重写initFirst,避免出现loading显示异常的问题 | |||
* @property VB : ViewDataBinding | |||
* @property VM: ViewModel | |||
*/ | |||
@@ -17,7 +19,10 @@ abstract class BaseFragmentVM<VB : ViewBinding, VM : BaseViewModel> : BaseFragme | |||
vm = initViewModel() | |||
vm.pageEvent.observe(this) { startActivity(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 |
@@ -48,24 +48,18 @@ open class BaseViewModel : ViewModel(), ViewModelLifecycle, ViewBehavior { | |||
super.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) { | |||
loadingEvent.postValue(isShow) | |||
} | |||
override fun showToast(msg: ToastEvent) { | |||
toastEvent.postValue(msg) | |||
} | |||
override fun startActivity(clazz: Class<*>) { | |||
pageEvent.postValue(clazz) | |||
} |
@@ -15,4 +15,11 @@ fun <T> diskIo2DiskIo(): ObservableTransformer<T, T> { | |||
upstream!!.subscribeOn(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)) | |||
} | |||
} |