| @@ -90,6 +90,7 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_empty.xml" value="0.4979166666666667" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_historical_route.xml" value="0.4859375" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_spell_single_word.xml" value="0.23632218844984804" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_task_image.xml" value="0.23353596757852077" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_course_progress.xml" value="0.25" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_coursepack.xml" value="0.43500866551126516" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/merge_recyclerview_smart_refresh_layout.xml" value="0.34427083333333336" /> | |||
| @@ -107,5 +107,7 @@ dependencies { | |||
| implementation customDependencies.grpc_android | |||
| implementation customDependencies.grpc_protobuf | |||
| implementation customDependencies.grpc_stub | |||
| //XPopup | |||
| implementation customDependencies.XPopup | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| package com.xkl.cdl.adapter | |||
| import android.view.ViewGroup | |||
| import android.widget.ImageView | |||
| import com.suliang.common.base.adapter.BaseAdapterViewHolder | |||
| import com.suliang.common.base.adapter.BaseRVAdapterVM | |||
| import com.suliang.common.extension.click | |||
| import com.suliang.common.util.image.ImageLoader | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.module.learn.LearnCTaskViewModel | |||
| import java.io.File | |||
| /** | |||
| * author suliang | |||
| * create 2022/5/23 14:44 | |||
| * Describe: | |||
| */ | |||
| class AdapterImageTask(viewModel : LearnCTaskViewModel) : BaseRVAdapterVM<File, LearnCTaskViewModel>(viewModel) { | |||
| override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder { | |||
| return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_task_image)) | |||
| } | |||
| override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) { | |||
| ImageLoader.loadImage(holder.binding.root as ImageView,getItem(position)) | |||
| holder.binding.root.click { | |||
| if (onItemClickIsInitialized()){ | |||
| onItemClick.invoke(it,position,getItem(position)) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,65 +0,0 @@ | |||
| package com.xkl.cdl.data.bean; | |||
| import java.io.Serializable; | |||
| /** | |||
| * author suliang | |||
| * create 2021/4/6 18:25 | |||
| * Describe: 作文课外练习实体 | |||
| */ | |||
| public class CompositionTaskBean { | |||
| private long taskId; //数据id | |||
| private int stage; //阶段 | |||
| private int type; //类型 | |||
| private String title; //标题 | |||
| private String introduce; //介绍 | |||
| private String photo; //图片 | |||
| public long getTaskId() { | |||
| return taskId; | |||
| } | |||
| public void setTaskId(long taskId) { | |||
| this.taskId = taskId; | |||
| } | |||
| public int getStage() { | |||
| return stage; | |||
| } | |||
| public void setStage(int stage) { | |||
| this.stage = stage; | |||
| } | |||
| public int getType() { | |||
| return type; | |||
| } | |||
| public void setType(int type) { | |||
| this.type = type; | |||
| } | |||
| public String getTitle() { | |||
| return title; | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title = title; | |||
| } | |||
| public String getIntroduce() { | |||
| return introduce; | |||
| } | |||
| public void setIntroduce(String introduce) { | |||
| this.introduce = introduce; | |||
| } | |||
| public String getPhoto() { | |||
| return photo; | |||
| } | |||
| public void setPhoto(String photo) { | |||
| this.photo = photo; | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| package com.xkl.cdl.data.bean | |||
| /** | |||
| * author suliang | |||
| * create 2021/4/6 18:25 | |||
| * Describe: 作文课外练习实体 | |||
| */ | |||
| data class CompositionTaskBean(val taskId : Long, //数据id | |||
| val stage : Long, //阶段 | |||
| val type : Long, //类型 | |||
| val title : String, //标题 | |||
| var introduce : String?, //介绍 | |||
| var photo : String?, | |||
| val position : Int) | |||
| @@ -1,15 +1,18 @@ | |||
| package com.xkl.cdl.data.manager | |||
| import appApi.AppApi | |||
| import com.suliang.common.util.AppGlobals | |||
| 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 com.xkl.cdl.data.bean.course.Lesson | |||
| import java.io.File | |||
| import java.io.* | |||
| import java.math.BigDecimal | |||
| import java.math.RoundingMode | |||
| import java.text.DecimalFormat | |||
| import java.util.zip.ZipFile | |||
| import java.util.zip.ZipInputStream | |||
| /** | |||
| * author suliang | |||
| @@ -66,16 +69,52 @@ object CourseManager { | |||
| /*** | |||
| * 检查课程包下课程的db数据文件是否存在,不存在,则复制过去 | |||
| * 作文是.zip结尾的文件,需要解压,将文件放入对应的目录 | |||
| */ | |||
| fun checkCourseDb() { | |||
| subjectWithCoursePackMap.forEach { entry -> | |||
| entry.value.forEach { coursePack -> | |||
| coursePack.childrenCourses.forEach { | |||
| val file = File(FileUtil.getSaveDirPath("db"), | |||
| "${entry.key}/${coursePack.coursePackId}/${it.courseId}/course.db") | |||
| //不存在,复制,存在则不操作 | |||
| if (!file.exists()) { | |||
| FileUtil.copyAsset(it.dbPathName, file) | |||
| //如果是作文,作文需要检查数据库,同时需要检查其下的图片、视频和关键帧 | |||
| if (it.subjectId == AppConstants.SUBJECT_CHINESE && it.coursePackType == AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION){ | |||
| val assetFileName = it.dbPathName.substringBeforeLast(".") + ".zip" | |||
| val inputStream = AppGlobals.application.resources.assets.open(assetFileName) | |||
| val zipInputStream = ZipInputStream(inputStream) | |||
| var count = 0 | |||
| val buffer = ByteArray(5120) | |||
| var nextEntry = zipInputStream.nextEntry | |||
| while (nextEntry != null){ | |||
| when{ | |||
| nextEntry.name.endsWith(".mp4.png") -> File(FilePathManager.getMp4PngRootPath(),"${entry.key}/${coursePack.coursePackId}/${it.courseId}/${nextEntry.name.substringAfterLast("/")}") | |||
| nextEntry.name.endsWith(".mp4") -> File(FilePathManager.getMp4RootPath(),"${entry.key}/${coursePack.coursePackId}/${it.courseId}/${nextEntry.name.substringAfterLast("/")}") | |||
| nextEntry.name.endsWith(".png") -> File(FilePathManager.getPngRootPath(),"${entry.key}/${coursePack.coursePackId}/${it.courseId}/${nextEntry.name.substringAfterLast("/")}") | |||
| nextEntry.name.endsWith(".db") -> File(FilePathManager.getDbRootPath(),"${entry.key}/${coursePack.coursePackId}/${it.courseId}/course.db") | |||
| else -> null | |||
| }?.let { file -> | |||
| //文件不存在 ,复制当前文件进入该文件 | |||
| if (!file.exists()){ | |||
| file.parentFile?.let { parentFile -> | |||
| if (!parentFile.exists()) parentFile.mkdirs() | |||
| } | |||
| val outputStream = BufferedOutputStream(FileOutputStream(file)) | |||
| while ((zipInputStream.read(buffer).also { count = it }) != -1 ){ | |||
| outputStream.write(buffer,0,count) | |||
| } | |||
| outputStream.close() | |||
| } | |||
| } | |||
| nextEntry = zipInputStream.nextEntry | |||
| } | |||
| }else{ //其他的项目只需要复制和检查数据库 | |||
| val file = File(FilePathManager.getDbRootPath(), "${entry.key}/${coursePack.coursePackId}/${it.courseId}/course.db") | |||
| //不存在,复制,存在则不操作 | |||
| if (!file.exists()) { | |||
| FileUtil.copyAsset(it.dbPathName, file) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -18,17 +18,39 @@ class FilePathManager { | |||
| */ | |||
| @JvmStatic | |||
| fun getCoursePackDbPath(): File{ | |||
| return File(FileUtil.getSaveDirPath("db"), "course-pack.db") | |||
| return File(getDbRootPath(), "course-pack.db") | |||
| } | |||
| /** 获取课程数据地址 */ | |||
| @JvmStatic | |||
| fun getCourseDbPath(base:DbControlBase): File{ | |||
| return File(FileUtil.getSaveDirPath("db"), "${base.subjectId}/${base.coursePackId}/${base.courseId}/course.db") | |||
| return File(getDbRootPath(), "${base.subjectId}/${base.coursePackId}/${base.courseId}/course.db") | |||
| } | |||
| /** 获取课程数据地址 */ | |||
| @JvmStatic | |||
| fun getCourseDbPath(subjectId:Int,coursePackId:Long,courseId:Long): File{ | |||
| return File(FileUtil.getSaveDirPath("db"), "subjectId/coursePackId/courseId/course.db") | |||
| return File(getDbRootPath(), "subjectId/coursePackId/courseId/course.db") | |||
| } | |||
| /** db文件保存的文件夹 */ | |||
| fun getDbRootPath(): String{ | |||
| return FileUtil.getSaveDirPath("db") | |||
| } | |||
| /**作文mp4.png保存的文件夹*/ | |||
| fun getMp4PngRootPath():String{ | |||
| return FileUtil.getSaveDirPath("mp4png") | |||
| } | |||
| /**作文课外练习png保存的文件夹*/ | |||
| fun getPngRootPath():String{ | |||
| return FileUtil.getSaveDirPath("png") | |||
| } | |||
| /**作文课外练习png保存的文件*/ | |||
| fun getPngFile(subjectId : Int,coursePackId : Long,courseId : Long, pngName:String):File{ | |||
| return File(getPngRootPath(),"$subjectId/$coursePackId/$courseId/$pngName") | |||
| } | |||
| /**作文视频MP4保存的文件夹*/ | |||
| fun getMp4RootPath():String{ | |||
| return FileUtil.getSaveDirPath("mp4") | |||
| } | |||
| } | |||
| @@ -44,12 +44,10 @@ object DBCourseManager { | |||
| val p = when (base.courseType) { | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> COMPOSITION | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> SPOKEN | |||
| AppConstants.COURSE_TYPE_CHINESE_LITERACY,AppConstants.COURSE_TYPE_CHINESE_PINYIN -> LITERACY_PINYING | |||
| AppConstants.COURSE_TYPE_CHINESE_LITERACY, AppConstants.COURSE_TYPE_CHINESE_PINYIN -> LITERACY_PINYING | |||
| else -> NORMAL | |||
| } | |||
| mDataBase = SQLiteDatabase.openDatabase(FilePathManager.getCourseDbPath(currentBase).path, | |||
| p, | |||
| null, | |||
| mDataBase = SQLiteDatabase.openDatabase(FilePathManager.getCourseDbPath(currentBase).path, p, null, | |||
| SQLiteDatabase.OPEN_READONLY) | |||
| } | |||
| if (mDataBase == null) { | |||
| @@ -89,7 +87,7 @@ object DBCourseManager { | |||
| val lessonId = it.getLong(it.getColumnIndex("lesson_id")) | |||
| val lessonName : String = when (base.courseType) { | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION, AppConstants.COURSE_TYPE_CHINESE_LITERACY, AppConstants.COURSE_TYPE_CHINESE_PINYIN -> it.getString( | |||
| it.getColumnIndex("lesson_title")) | |||
| it.getColumnIndex("lesson_title")) | |||
| else -> it.getString(it.getColumnIndex("lesson")) | |||
| } | |||
| val wordIds : MutableList<Long> = when (base.courseType) { | |||
| @@ -109,15 +107,8 @@ object DBCourseManager { | |||
| // 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 todo 分课程类型设置进度点 | |||
| val learnIsOver = wordIds.size - 1 == learnIndex | |||
| val lesson = Lesson(base.subjectId, | |||
| base.coursePackId, | |||
| base.coursePackType, | |||
| base.courseId, | |||
| base.courseType, | |||
| chapterId, | |||
| chapterName, | |||
| lessonId, | |||
| lessonName).apply { | |||
| val lesson = Lesson(base.subjectId, base.coursePackId, base.coursePackType, base.courseId, base.courseType, | |||
| chapterId, chapterName, lessonId, lessonName).apply { | |||
| lessonPositionInList = positionIndex | |||
| this.wordIds = wordIds //内容 | |||
| totalNumber = this.wordIds.size //总数 | |||
| @@ -132,12 +123,12 @@ object DBCourseManager { | |||
| mutableList.add(lesson) | |||
| positionIndex += 1 | |||
| // TODO: 2022/5/9 对课时数量进行限制,课时太多,开发人员不好进行测试 | |||
| val needBreak = when(base.coursePackType){ | |||
| val needBreak = when (base.coursePackType) { | |||
| AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION -> positionIndex == 11 //保留十个课时 | |||
| AppConstants.COURSEPACK_TYPE_ENGLISH_SPOKEN -> positionIndex == 7 //保留六个课时 | |||
| else -> positionIndex == 3 //保留三个课时 | |||
| } | |||
| if (needBreak){ | |||
| if (needBreak) { | |||
| break | |||
| } | |||
| } | |||
| @@ -164,7 +155,9 @@ object DBCourseManager { | |||
| } | |||
| val sql = when (base.courseType) { | |||
| //作文知识点测试 | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> "SELECT * FROM exam WHERE chapter_id = ${lesson!!.chapterId} AND exam_id in (${Joiner.on(",").join(lesson.wordIds)}) ORDER BY random()" | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> "SELECT * FROM exam WHERE chapter_id = ${lesson!!.chapterId} AND exam_id in (${ | |||
| Joiner.on(",").join(lesson.wordIds) | |||
| }) ORDER BY random()" | |||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE, | |||
| AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSE_TYPE_CHINESE_LITERACY, | |||
| @@ -203,7 +196,7 @@ object DBCourseManager { | |||
| mDataBase?.rawQuery(sql, null)?.run { | |||
| when (base.courseType) { | |||
| //作文知识点测试 | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> while (moveToNext()) { | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> while (moveToNext()) { | |||
| result.add(ExamBean().apply { | |||
| id = getLong(3) | |||
| word_id = getLong(2) | |||
| @@ -218,23 +211,21 @@ object DBCourseManager { | |||
| }) | |||
| } | |||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE, | |||
| 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_SOUNDMARK -> while (moveToNext()) { | |||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN, AppConstants.COURSE_TYPE_ENGLISH_VOICE, 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_SOUNDMARK -> while (moveToNext()) { | |||
| result.add(ExamBean().apply { | |||
| id = getLong(0) | |||
| word_id = getLong(1) | |||
| @@ -355,28 +346,24 @@ object DBCourseManager { | |||
| //不为空,写入本身,如果为空,用另外的发音方式写入 | |||
| val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||
| val audio_us_file_path = audio_us?.let { | |||
| val audioFileNameUS = | |||
| "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_US}" | |||
| val audioFileNameUS = "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_US}" | |||
| val file = File(parentPath, audioFileNameUS) | |||
| FileUtil.writeBytesToFile(file, it) | |||
| file.path | |||
| } ?: audio_uk?.let { | |||
| val audioFileNameUS = | |||
| "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_US}" | |||
| val audioFileNameUS = "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_US}" | |||
| val file = File(parentPath, audioFileNameUS) | |||
| FileUtil.writeBytesToFile(file, it) | |||
| file.path | |||
| } | |||
| //不为空,写入本身,如果为空,用另外的发音方式写入 | |||
| val audio_uk_file_path = audio_uk?.let { | |||
| val audioFileNameUk = | |||
| "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||
| val audioFileNameUk = "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||
| val file = File(parentPath, audioFileNameUk) | |||
| FileUtil.writeBytesToFile(file, it) | |||
| file.path | |||
| } ?: audio_us?.let { | |||
| val audioFileNameUk = | |||
| "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||
| val audioFileNameUk = "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||
| val file = File(parentPath, audioFileNameUk) | |||
| FileUtil.writeBytesToFile(file, it) | |||
| file.path | |||
| @@ -392,8 +379,7 @@ object DBCourseManager { | |||
| while (it.moveToNext()) { | |||
| //写入文件 | |||
| audio_cn_file_path = it.getBlobOrNull(0)?.let { | |||
| val audioFileNameCN = | |||
| "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_CN}" | |||
| val audioFileNameCN = "${base.subjectId}_${base.coursePackId}_${base.courseId}_${wordId}_${AppConstants.SOUND_TYPE_CN}" | |||
| val file = File(FileUtil.getSaveDirPath(AppConfig.VOICE), audioFileNameCN) | |||
| FileUtil.writeBytesToFile(file, it) | |||
| file.path | |||
| @@ -434,25 +420,16 @@ object DBCourseManager { | |||
| open(dbcb) | |||
| //从lesson已学位置开始获取数据 | |||
| val needLearnIds = lesson.wordIds.subList(lesson.learnedIndex + 1, lesson.wordIds.size) | |||
| val sql = | |||
| "SELECT * FROM chapter WHERE chapter_id = ${lesson.chapterId} and lesson_id = ${lesson.lessonId} AND word_id in (${ | |||
| Joiner.on(",").join(needLearnIds) | |||
| }) " + "ORDER by word_sort ASC" | |||
| val sql = "SELECT * FROM chapter WHERE chapter_id = ${lesson.chapterId} and lesson_id = ${lesson.lessonId} AND word_id in (${ | |||
| Joiner.on(",").join(needLearnIds) | |||
| }) " + "ORDER by word_sort ASC" | |||
| mDataBase?.rawQuery(sql, null)?.run { | |||
| while (moveToNext()) { | |||
| //单词id | |||
| val word_id = getLong(getColumnIndex("word_id")) | |||
| //学习单词实体 | |||
| result.add(LearnWord(dbcb.subjectId, | |||
| dbcb.coursePackId, | |||
| dbcb.courseId, | |||
| dbcb.coursePackType, | |||
| dbcb.courseType, | |||
| lesson.chapterId, | |||
| lesson.lessonId, | |||
| word_id, | |||
| true, | |||
| lesson.lessonType).apply { | |||
| result.add(LearnWord(dbcb.subjectId, dbcb.coursePackId, dbcb.courseId, dbcb.coursePackType, dbcb.courseType, | |||
| lesson.chapterId, lesson.lessonId, word_id, true, lesson.lessonType).apply { | |||
| word = getString(getColumnIndex("word")) | |||
| basic_explanation = getString(getColumnIndex("basic_explaination")) | |||
| extend_explanation = getString(getColumnIndex("all_explaination")) | |||
| @@ -506,21 +483,13 @@ object DBCourseManager { | |||
| else -> wordIds.append(value).append(",") | |||
| } | |||
| } | |||
| val sql = | |||
| "SELECT * FROM knowledge WHERE chapter_id = ${lesson.chapterId} AND knowledge_id in ($wordIds) ORDER by CASE $sortSqlBuilder END" | |||
| val sql = "SELECT * FROM knowledge WHERE chapter_id = ${lesson.chapterId} AND knowledge_id in ($wordIds) ORDER by CASE $sortSqlBuilder END" | |||
| mDataBase?.rawQuery(sql, null)?.run { | |||
| while (moveToNext()) { | |||
| //学习单词实体 | |||
| result.add(LearnWord(dbcb.subjectId, | |||
| dbcb.coursePackId, | |||
| dbcb.courseId, | |||
| dbcb.coursePackType, | |||
| dbcb.courseType, | |||
| lesson.chapterId, | |||
| lesson.lessonId, | |||
| getLong(getColumnIndex("knowledge_id")), | |||
| true, | |||
| result.add(LearnWord(dbcb.subjectId, dbcb.coursePackId, dbcb.courseId, dbcb.coursePackType, dbcb.courseType, | |||
| lesson.chapterId, lesson.lessonId, getLong(getColumnIndex("knowledge_id")), true, | |||
| lesson.lessonType).apply { | |||
| word = getString(getColumnIndex("title")) | |||
| basic_explanation = getString(getColumnIndex("explaination")) | |||
| @@ -558,10 +527,14 @@ object DBCourseManager { | |||
| * @return List<LearnWord> | |||
| */ | |||
| @SuppressLint("Range") | |||
| fun queryLearnWord(dbcb : DbControlBase, chapterId:Long, lessonId:Long, wordIds:String, isNeedOriginSort:Boolean = false):List<LearnWord>{ | |||
| fun queryLearnWord(dbcb : DbControlBase, | |||
| chapterId : Long, | |||
| lessonId : Long, | |||
| wordIds : String, | |||
| isNeedOriginSort : Boolean = false) : List<LearnWord> { | |||
| val result = mutableListOf<LearnWord>() | |||
| open(dbcb) | |||
| val sql = when{ | |||
| val sql = when { | |||
| !isNeedOriginSort -> "SELECT * FROM chapter WHERE chapter_id = $chapterId and lesson_id = $lessonId AND word_id in ($wordIds) ORDER by word_sort ASC" | |||
| else -> { | |||
| val sortSqlBuilder = StringBuilder() //排序value | |||
| @@ -574,15 +547,8 @@ object DBCourseManager { | |||
| mDataBase?.rawQuery(sql, null)?.run { | |||
| while (moveToNext()) { | |||
| //学习单词实体 | |||
| result.add(LearnWord(dbcb.subjectId, | |||
| dbcb.coursePackId, | |||
| dbcb.courseId, | |||
| dbcb.coursePackType, | |||
| dbcb.courseType, | |||
| chapterId, | |||
| lessonId, | |||
| getLong(getColumnIndex("word_id")), | |||
| true, | |||
| result.add(LearnWord(dbcb.subjectId, dbcb.coursePackId, dbcb.courseId, dbcb.coursePackType, dbcb.courseType, | |||
| chapterId, lessonId, getLong(getColumnIndex("word_id")), true, | |||
| AppConstants.LESSON_TYPE_WORD).apply { | |||
| word = getString(getColumnIndex("word")) | |||
| basic_explanation = getString(getColumnIndex("basic_explaination")) | |||
| @@ -590,7 +556,7 @@ object DBCourseManager { | |||
| phrase = getString(getColumnIndex("phrase")) | |||
| example = getString(getColumnIndex("example")) | |||
| reference = getString(getColumnIndex("reference")) | |||
| when (dbcb.courseType) { | |||
| AppConstants.COURSE_TYPE_CHINESE_LITERACY -> { | |||
| literacyIspolyphone = getInt(getColumnIndex("polyphone")) > 0 | |||
| @@ -619,23 +585,21 @@ object DBCourseManager { | |||
| fun queryCompositionReading(dbcb : DbControlBase, entity : Lesson) : List<CompositionReadingBean> { | |||
| val result = mutableListOf<CompositionReadingBean>() | |||
| val sql = "SELECT * FROM reading WHERE reading_id in (${Joiner.on(",").join(entity.wordIds)}) AND chapter_id = 5272 ORDER by CASE " + entity.wordIds.let { | |||
| val sql = "SELECT * FROM reading WHERE reading_id in (${ | |||
| Joiner.on(",") | |||
| .join(entity.wordIds) | |||
| }) AND chapter_id = ${entity.chapterId} ORDER by CASE " + entity.wordIds.let { | |||
| val sortedCase = StringBuilder() | |||
| it.forEachIndexed{ index,id -> | |||
| it.forEachIndexed { index, id -> | |||
| sortedCase.append(" WHEN reading_id = $id THEN $index") | |||
| } | |||
| sortedCase | |||
| } + " END" | |||
| open(dbcb) | |||
| mDataBase?.rawQuery(sql,null)?.run { | |||
| while (moveToNext()){ | |||
| result.add(CompositionReadingBean( | |||
| getLong(2), | |||
| getString(3), | |||
| getString(4), | |||
| getString(7), | |||
| getString(6)).apply { | |||
| collectId = 0 | |||
| mDataBase?.rawQuery(sql, null)?.run { | |||
| while (moveToNext()) { | |||
| result.add(CompositionReadingBean(getLong(2), getString(3), getString(4), getString(7), getString(6)).apply { | |||
| collectId = 0 | |||
| }) | |||
| } | |||
| close() | |||
| @@ -650,10 +614,41 @@ object DBCourseManager { | |||
| * @param entity Lesson | |||
| * @return List<CompositionTaskBean> | |||
| */ | |||
| fun queryCompositonTask(dbcb : DbControlBase,entity : Lesson) : List<CompositionTaskBean>{ | |||
| fun queryCompositionTask(dbcb : DbControlBase, entity : Lesson) : List<CompositionTaskBean> { | |||
| val result = mutableListOf<CompositionTaskBean>() | |||
| val sql = "SELECT * FROM task WHERE task_id in (${ | |||
| Joiner.on(",") | |||
| .join(entity.wordIds) | |||
| }) AND chapter_id = ${entity.chapterId} ORDER by CASE " + entity.wordIds.let { | |||
| val sortedCase = StringBuilder() | |||
| it.forEachIndexed { index, id -> | |||
| sortedCase.append(" WHEN task_id = $id THEN $index") | |||
| } | |||
| sortedCase | |||
| } + " END" | |||
| open(dbcb) | |||
| mDataBase?.rawQuery(sql, null)?.run { | |||
| var position = 0 | |||
| while (moveToNext()) { | |||
| result.add(CompositionTaskBean(getLong(2), | |||
| getLong(4), | |||
| getLong(3), | |||
| getString(6), | |||
| getString(7), | |||
| getString(8), | |||
| position)) | |||
| position++ | |||
| result.add(CompositionTaskBean(getLong(2), | |||
| getLong(4), | |||
| getLong(3), | |||
| getString(6), | |||
| getString(7), | |||
| getString(8), | |||
| position)) | |||
| position++ | |||
| } | |||
| close() | |||
| } | |||
| return result | |||
| } | |||
| @@ -1,6 +1,5 @@ | |||
| package com.xkl.cdl.data.repository | |||
| import androidx.lifecycle.MutableLiveData | |||
| import com.suliang.common.util.file.FileUtil | |||
| import com.xkl.cdl.data.bean.course.CourseDetail | |||
| import com.xkl.cdl.data.bean.course.Lesson | |||
| @@ -1,9 +1,28 @@ | |||
| package com.xkl.cdl.module.learn | |||
| import android.media.Image | |||
| import android.os.Bundle | |||
| import android.text.TextUtils | |||
| import android.widget.ImageView | |||
| import androidx.databinding.DataBindingUtil | |||
| import androidx.lifecycle.ViewModelProvider | |||
| import androidx.recyclerview.widget.LinearLayoutManager | |||
| import androidx.recyclerview.widget.StaggeredGridLayoutManager | |||
| import com.google.android.material.bottomsheet.BottomSheetDialog | |||
| import com.lxj.xpopup.XPopup | |||
| import com.lxj.xpopup.core.ImageViewerPopupView | |||
| import com.lxj.xpopup.interfaces.OnSrcViewUpdateListener | |||
| import com.lxj.xpopup.util.SmartGlideImageLoader | |||
| import com.suliang.common.base.activity.BaseActivityVM | |||
| import com.suliang.common.extension.click | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.adapter.AdapterBottomDialogSwitch | |||
| import com.xkl.cdl.adapter.AdapterImageTask | |||
| import com.xkl.cdl.data.bean.CompositionReadingBean | |||
| import com.xkl.cdl.data.bean.CompositionTaskBean | |||
| import com.xkl.cdl.data.manager.FilePathManager | |||
| import com.xkl.cdl.databinding.ActivityLearnCtaskBinding | |||
| import com.xkl.cdl.databinding.DialogBottomAutoPlaySelectBinding | |||
| /** | |||
| * author suliang | |||
| @@ -17,11 +36,85 @@ class LearnCTaskActivity : BaseActivityVM<ActivityLearnCtaskBinding,LearnCTaskVi | |||
| } | |||
| override fun initActivity(savedInstanceState : Bundle?) { | |||
| binding.titleBar.textViewTitle.run { | |||
| ellipsize = TextUtils.TruncateAt.MIDDLE | |||
| text = vm.lesson.lessonName | |||
| } | |||
| binding.titleBar.onBackClick = { | |||
| finish() | |||
| } | |||
| binding.titleBar.onRightClick = { | |||
| showChangeTaskDialog() | |||
| } | |||
| //初始化图片显示recyclerView | |||
| binding.recyclerView.apply { | |||
| layoutManager = StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL) | |||
| adapter = AdapterImageTask(vm).apply { | |||
| onItemClick = { v, position, item -> | |||
| XPopup.Builder(this@LearnCTaskActivity) | |||
| .asImageViewer(v as ImageView, position, | |||
| (binding.recyclerView.adapter as AdapterImageTask).getData() as List<*>, object : OnSrcViewUpdateListener{ | |||
| override fun onSrcViewUpdate(popupView : ImageViewerPopupView, position : Int) { | |||
| popupView.updateSrcView(binding.recyclerView.getChildAt(position) as ImageView) | |||
| } | |||
| }, SmartGlideImageLoader()) | |||
| .show() | |||
| } | |||
| } | |||
| } | |||
| } | |||
| override fun loadData() { | |||
| vm.queryData() | |||
| //当前练习 | |||
| vm.currentTask.observe(this){ | |||
| binding.run { | |||
| topicValue.text = it.title | |||
| requireValue.text = it.introduce | |||
| sourceValue.text = "学考乐" | |||
| } | |||
| it.photo?.run { | |||
| val fileList = split(",").map { | |||
| FilePathManager.getPngFile(vm.lesson.subjectId,vm.lesson.coursePackId,vm.lesson.courseId,it.substringAfterLast ("/")) | |||
| }.toMutableList() | |||
| (binding.recyclerView.adapter as AdapterImageTask).setData(fileList) | |||
| } | |||
| } | |||
| } | |||
| private var changeTaskDialog: BottomSheetDialog? = null | |||
| /** 切换item的dialog */ | |||
| private fun showChangeTaskDialog() { | |||
| if (!vm.isCanSwitch) return | |||
| if (changeTaskDialog == null) { | |||
| changeTaskDialog = BottomSheetDialog(this, R.style.dialog_style).apply { | |||
| val autoPlayBinding = DataBindingUtil.inflate<DialogBottomAutoPlaySelectBinding>(layoutInflater, | |||
| R.layout.dialog_bottom_auto_play_select, | |||
| null, false) | |||
| setContentView(autoPlayBinding.root) | |||
| autoPlayBinding.tvTitle.text = "请选择课外练习查看详情" | |||
| autoPlayBinding.ivCancel.click { v -> dismiss() } | |||
| autoPlayBinding.rvRepeat.run { | |||
| layoutManager = LinearLayoutManager(this@LearnCTaskActivity, LinearLayoutManager.VERTICAL, false) | |||
| adapter = AdapterBottomDialogSwitch<CompositionTaskBean>().apply { | |||
| onItemClick = { _, position, _ -> | |||
| dismiss() | |||
| vm.currentTask.value = vm.mTaskList[position] | |||
| } | |||
| setData(vm.mTaskList.toMutableList()) | |||
| } | |||
| } | |||
| //显示时,定位到当前item的位置 | |||
| setOnShowListener{ | |||
| (autoPlayBinding.rvRepeat.adapter as AdapterBottomDialogSwitch<*>).selectItemIndex = vm.currentTask.value!!.position | |||
| autoPlayBinding.rvRepeat.scrollToPosition( vm.currentTask.value!!.position) | |||
| } | |||
| } | |||
| } | |||
| changeTaskDialog?.show() | |||
| } | |||
| @@ -1,15 +1,46 @@ | |||
| package com.xkl.cdl.module.learn | |||
| import androidx.lifecycle.MutableLiveData | |||
| import com.suliang.common.base.activity.ToastEvent | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| import com.suliang.common.extension.diskIo2Main | |||
| import com.xkl.cdl.data.DataTransferHolder | |||
| import com.xkl.cdl.data.bean.CompositionTaskBean | |||
| 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 io.reactivex.rxjava3.core.Observable | |||
| class LearnCTaskViewModel : BaseViewModel() { | |||
| val lesson = DataTransferHolder.instance.getData<Lesson>() | |||
| val dbControlBase = DbControlBase(lesson.subjectId, lesson.coursePackId, lesson.coursePackType, lesson.courseId, | |||
| lesson.courseType) | |||
| fun queryData(){ | |||
| //是否有切换的练习 | |||
| var isCanSwitch = false | |||
| //数据列表 | |||
| lateinit var mTaskList : List<CompositionTaskBean> | |||
| //当前显示task | |||
| val currentTask = MutableLiveData<CompositionTaskBean>() | |||
| /** | |||
| * 查询数据 | |||
| */ | |||
| fun queryData() { | |||
| Observable.create<List<CompositionTaskBean>> { | |||
| it.onNext(DBCourseManager.queryCompositionTask(dbControlBase, lesson)) | |||
| it.onComplete() | |||
| }.compose(diskIo2Main()).subscribe({ | |||
| isCanSwitch = it.size > 1 | |||
| mTaskList = it | |||
| currentTask.value = mTaskList[0] | |||
| }, { | |||
| it.printStackTrace() | |||
| showToast(ToastEvent(content = "课外练习数据获取异常,请联系管理人员进行处理")) | |||
| }) | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <layout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto"> | |||
| <data> | |||
| </data> | |||
| <com.google.android.material.imageview.ShapeableImageView | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:scaleType="fitXY" | |||
| app:shapeAppearance="@style/roundedCornerStyle" /> | |||
| </layout> | |||
| @@ -100,7 +100,11 @@ ext { | |||
| grpc_protobuf: "io.grpc:grpc-protobuf:1.27.0", | |||
| grpc_stub: "io.grpc:grpc-stub:1.27.0", | |||
| //liveEventBus https://github.com/JeremyLiao/LiveEventBus | |||
| liveEventBus : "io.github.jeremyliao:live-event-bus-x:1.8.0" | |||
| liveEventBus : "io.github.jeremyliao:live-event-bus-x:1.8.0", | |||
| //BigImageViewPager | |||
| // BigImageViewPager : "com.github.SherlockGougou:BigImageViewPager:androidx-7.0.0", | |||
| //XPopup https://github.com/li-xiaojun/XPopup | |||
| XPopup : "com.github.li-xiaojun:XPopup:2.8.0" | |||
| ] | |||
| @@ -16,6 +16,9 @@ import com.bumptech.glide.module.AppGlideModule | |||
| import com.bumptech.glide.module.LibraryGlideModule | |||
| import com.bumptech.glide.request.RequestOptions | |||
| import com.suliang.common.util.file.FileUtil | |||
| import com.bumptech.glide.load.model.GlideUrl | |||
| import java.io.InputStream | |||
| /** | |||
| * author suliang | |||
| @@ -66,5 +69,12 @@ class MyGlideApp : AppGlideModule() { | |||
| // setDefaultRequestOptions(apply) | |||
| } | |||
| } | |||
| override fun registerComponents(context : Context, glide : Glide, registry : Registry) { | |||
| super.registerComponents(context, glide, registry) | |||
| // 替换底层网络框架为okhttp3,这步很重要!如果不添加会无法正常显示原图的加载百分比,或者卡在1% | |||
| // 如果你的app中已经存在了自定义的GlideModule,你只需要把这一行代码,添加到对应的重载方法中即可。 | |||
| // registry.replace(GlideUrl::class.java, InputStream::class.java, Factory(ProgressManager.getOkHttpClient())) | |||
| } | |||
| } | |||