| @@ -67,11 +67,11 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_lesson.xml" value="0.33" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_main.xml" value="0.4979166666666667" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_pack.xml" value="0.34427083333333336" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_review.xml" value="0.12802768166089964" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_review.xml" value="0.67" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_course_total_test.xml" value="0.33" /> | |||
| <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_memo.xml" value="0.20104166666666667" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/fragment_memo.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_control_button.xml" value="0.46467391304347827" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/inc_exam_spell_content.xml" value="0.45153985507246375" /> | |||
| @@ -92,6 +92,7 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_course_lesson.xml" value="0.4785615491009682" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_empty.xml" value="0.4979166666666667" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_historical_route.xml" value="0.4859375" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_memo.xml" value="0.478125" /> | |||
| <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/item_video_adapter.xml" value="0.67" /> | |||
| @@ -103,9 +104,18 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/textview_only.xml" value="0.49773550724637683" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/statedrawable/drawable/ic_nav_learn_center.xml" value="0.44166666666666665" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_arrow_right.xml" value="0.29814814814814816" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_course_composition.xml" value="0.30092592592592593" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_course_discern.xml" value="0.30092592592592593" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_course_literacy.xml" value="0.2972222222222222" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_course_pinying.xml" value="0.30092592592592593" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_course_soundmark.xml" value="0.35555555555555557" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_course_spell.xml" value="0.30092592592592593" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_course_spoken.xml" value="0.30092592592592593" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_course_voice.xml" value="0.35555555555555557" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_discern.xml" value="0.5061538461538462" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_icon___.xml" value="0.287962962962963" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_menu.xml" value="0.4036458333333333" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_more.xml" value="0.35555555555555557" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_learn_center.xml" value="0.44166666666666665" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_memo.xml" value="0.21574074074074073" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_my.xml" value="0.44166666666666665" /> | |||
| @@ -70,3 +70,46 @@ Material Design Components 组件使用 https://www.jianshu.com/p/bc71b4179cb2 | |||
| // jniLibs.srcDirs = ['libs'] | |||
| // } | |||
| // } | |||
| 自建数据库 | |||
| 实现口语收藏、作文笔记收藏 | |||
| CourseMainFragmentViewModel.loadmain() | |||
| 计算当前词汇量,课程完成/重学的时候调用一下;重学直接程序帮忙调用 | |||
| public native long calcCurrentVocabulary(long projectID) throws Exception; | |||
| 计算学习效率(综合学习效率和课程效率) | |||
| public native void calcEfficiency(projectID, packID, courseID) throws Exception; | |||
| 课程学习、时长信息 | |||
| public native String courseDetail(projectID, packID, courseID ) throws Exception; | |||
| 速记课程排序列表 进度 | |||
| public native String courseSorted(long projectID ) throws Exception; | |||
| 词条数统计 近90天词条数统计 | |||
| public native String entityCountList(long projectID, long gradeID ) throws Exception; | |||
| 获取已学课程ID列表 | |||
| public native String getLearnedCourseIDList() throws Exception; | |||
| 获取正确/错误词汇列表 | |||
| public native String getWordList(long projectID, long packID, long courseID, long categroy ) throws Exception; | |||
| 保存数据struct.Record | |||
| public native void parseData(String var1) throws Exception; | |||
| 重学 | |||
| public native void relearn(long var1, long var3, long var5, long var7, long var9, double var11, double var13, double var15) throws Exception; | |||
| 保存已学课程ID | |||
| public native void setLearnedCourseID(long var1, long var3, long var5) throws Exception; | |||
| 设置视频播放点 | |||
| public native void setVideoPoint(long var1, long var3, long var5, long var7, String var9) throws Exception; | |||
| 统计信息 | |||
| public native String statisticsCenter(long var1, long var3) throws Exception; | |||
| 设置课程学习点,同时会更新对应lesson的进度点 | |||
| public native void setLearnPoint(long var1, long var3, long var5, long var7, long var9, long var11) throws Exception; | |||
| GetVideoPoint 获取视频播放点 | |||
| public native String getVideoPoint(long var1, long var3, long var5, long var7) throws Exception; | |||
| public native void gameFinished(long var1, long var3, long var5, long var7, long var9) throws Exception; | |||
| public native void changeLocker(long var, long var3, long var5, long var7, long var9, boolean var11) throws Exception; | |||
| public native void delLearnedCourseID(long var1, long var3, long var5) throws Exception; | |||
| public native void destroy(); | |||
| @@ -0,0 +1,48 @@ | |||
| package com.xkl.cdl; | |||
| import com.suliang.common.util.LogUtil; | |||
| import com.xkl.cdl.module.XKLApplication; | |||
| import java.lang.reflect.InvocationHandler; | |||
| import java.lang.reflect.Method; | |||
| import java.lang.reflect.Proxy; | |||
| import mobile_cache.MobileCache; | |||
| /** | |||
| * author suliang | |||
| * create 2022/6/1 10:58 | |||
| * Describe: | |||
| */ | |||
| public class HookMobileCache { | |||
| public void hook(){ | |||
| try{ | |||
| MobileCache mobileCache = XKLApplication.Companion.getMobileCache(); | |||
| Han han = new Han(mobileCache); | |||
| Object o = Proxy.newProxyInstance(han.getClass().getClassLoader(), mobileCache.getClass().getInterfaces(), han); | |||
| o.hashCode(); | |||
| }catch (Exception e){ | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| class Han implements InvocationHandler{ | |||
| private Object mobileObject ; | |||
| public Han(Object mobileObject) { | |||
| this.mobileObject = mobileObject; | |||
| } | |||
| @Override | |||
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | |||
| //代理前添加操作 | |||
| LogUtil.INSTANCE.e(method.getName()); | |||
| Object value = method.invoke(mobileObject,args); | |||
| LogUtil.INSTANCE.e(value.toString()); | |||
| return value; | |||
| } | |||
| } | |||
| } | |||
| @@ -64,13 +64,13 @@ class AdapterCoursePackWithLearCenter(vm: CoursePackFragmentViewModel) : | |||
| AppConstants.COURSE_TYPE_ENGLISH_VOICE -> { | |||
| imageViewTypeVoice.run { | |||
| visibility = View.VISIBLE | |||
| setImageResource(R.drawable.ic_voice) | |||
| setImageResource(R.drawable.ic_course_voice) | |||
| } | |||
| } | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> { | |||
| imageViewTypeVoice.run{ | |||
| visibility = View.VISIBLE | |||
| setImageResource(R.drawable.ic_spoken) | |||
| setImageResource(R.drawable.ic_course_spoken) | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,170 @@ | |||
| package com.xkl.cdl.adapter | |||
| import android.annotation.SuppressLint | |||
| import android.content.res.ColorStateList | |||
| import android.graphics.Color | |||
| import android.view.View | |||
| import android.view.ViewGroup | |||
| import android.view.ViewTreeObserver | |||
| import androidx.core.content.ContextCompat | |||
| import com.google.android.material.badge.BadgeDrawable | |||
| import com.google.android.material.badge.BadgeUtils | |||
| import com.google.android.material.imageview.ShapeableImageView | |||
| import com.suliang.common.base.adapter.BaseAdapterViewHolder | |||
| import com.suliang.common.base.adapter.BaseRVAdapterVM | |||
| import com.suliang.common.databinding.ItemEmptyBinding | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.bean.MemoCoursePack | |||
| import com.xkl.cdl.data.bean.course.Course | |||
| import com.xkl.cdl.data.binding.BindingAdapter | |||
| import com.xkl.cdl.databinding.ItemMemoBinding | |||
| import com.xkl.cdl.module.m_memo.MemoFragmentViewModel | |||
| /** | |||
| * author suliang | |||
| * create 2022/5/30 16:56 | |||
| * Describe: 备忘本适配器 | |||
| */ | |||
| class AdapterCoursePackWithMemo(viewModel : MemoFragmentViewModel) : | |||
| BaseRVAdapterVM<MemoCoursePack, MemoFragmentViewModel>(viewModel) { | |||
| private val badgeDrawable : BadgeDrawable by lazy { | |||
| BadgeDrawable.create(context).apply { | |||
| badgeGravity = BadgeDrawable.TOP_END | |||
| maxCharacterCount = 3 | |||
| backgroundColor = ContextCompat.getColor(context, R.color.red_1) | |||
| badgeTextColor = ContextCompat.getColor(context, R.color.white) | |||
| } | |||
| } | |||
| override fun onBindEmptyViewHolder(holder : BaseAdapterViewHolder) { | |||
| (holder.binding as ItemEmptyBinding).run { | |||
| //根据监听,显示具体的内容 | |||
| vm.etSearchLiveData.value?.let { | |||
| if (it.isNotEmpty()) { | |||
| imgEmpty.setImageResource(R.mipmap.empty_nothing_search) | |||
| tvContent.text = "没有搜索到任何内容" | |||
| } else { | |||
| imgEmpty.setImageResource(R.mipmap.empty_nothing) | |||
| tvContent.text = "没有数据" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder { | |||
| return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_memo)) | |||
| } | |||
| override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) { | |||
| getItem(position).let { item -> | |||
| (holder.binding as ItemMemoBinding).run { | |||
| //图片 | |||
| BindingAdapter.imageByteArray(imgCoursePackCover, item.coursePack.cover) | |||
| //文字 | |||
| tvCoursePackName.text = item.coursePack.coursePackName | |||
| //图标与角标文字 | |||
| item.coursePack.childrenCourses.forEachIndexed { index : Int, course : Course -> | |||
| when (course.courseType) { | |||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN -> setImageIcon(imgCourseIcon1,course.courseType,item.coursePackChildrenReviewNumber[index].size) | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL -> setImageIcon(imgCourseIcon2,course.courseType,item.coursePackChildrenReviewNumber[index].size) | |||
| AppConstants.COURSE_TYPE_ENGLISH_VOICE -> setImageIcon(imgCourseIcon3,course.courseType,item.coursePackChildrenReviewNumber[index].size) | |||
| //其他只有一个课程类型 | |||
| else -> { | |||
| setImageIcon(imgCourseIcon1,course.courseType,item.coursePackChildrenReviewNumber[index].size) | |||
| imgCourseIcon2.visibility = View.INVISIBLE | |||
| imgCourseIcon3.visibility = View.INVISIBLE | |||
| imgCourseIcon1.visibility = View.VISIBLE | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| /** | |||
| * 设置图片 角标 与 背景,有复习number数据需要设置角标与背景,否则不需要 | |||
| * @param imageView ShapeableImageView 图片 | |||
| * @param courseType Int 课程类型 | |||
| * @param reviewNumber Int 复习数量 | |||
| */ | |||
| @SuppressLint("UnsafeOptInUsageError") | |||
| private fun setImageIcon(imageView : ShapeableImageView, courseType : Int, reviewNumber : Int) { | |||
| var color : Int = Color.parseColor("#00000000") | |||
| //取消角标 | |||
| BadgeUtils.detachBadgeDrawable(badgeDrawable,imageView) | |||
| when (reviewNumber) { | |||
| //没有数据 | |||
| 0 -> { | |||
| imageView.strokeColor = ColorStateList.valueOf(color) | |||
| //设置图标: 灰色,没有背景 | |||
| imageView.setImageResource(getCourseIcon(courseType)) | |||
| imageView.imageTintList = ColorStateList.valueOf(Color.parseColor("#9A9EB3")) | |||
| } | |||
| //有复习数据 | |||
| else -> { | |||
| when (courseType) { | |||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN -> color = Color.parseColor("#1A5082E6") | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL -> color = Color.parseColor("#1AF26255") | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_ENGLISH_VOICE -> color = Color.parseColor( | |||
| "#1A52CC52") | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION, AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK -> color = Color.parseColor( | |||
| "#1A8757E6") | |||
| AppConstants.COURSE_TYPE_CHINESE_PINYIN -> color = Color.parseColor("#1AEB54D8") | |||
| AppConstants.COURSE_TYPE_CHINESE_LITERACY -> color = Color.parseColor("#1AFF8B52") | |||
| } | |||
| imageView.strokeColor = ColorStateList.valueOf(color) | |||
| //设置图标:原图颜色 | |||
| imageView.setImageResource(getCourseIcon(courseType)) | |||
| //添加角标 | |||
| imageView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { | |||
| @SuppressLint("UnsafeOptInUsageError") | |||
| override fun onGlobalLayout() { | |||
| BadgeDrawable.create(context).apply { | |||
| badgeGravity = BadgeDrawable.TOP_END | |||
| number = reviewNumber | |||
| maxCharacterCount = 3 | |||
| backgroundColor = ContextCompat.getColor(context, R.color.red_1) | |||
| badgeTextColor = ContextCompat.getColor(context, R.color.white) | |||
| BadgeUtils.attachBadgeDrawable(this, imageView) | |||
| } | |||
| imageView.viewTreeObserver.removeOnGlobalLayoutListener(this) | |||
| } | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| private fun getCourseIcon(courseType : Int) : Int { | |||
| return when (courseType) { | |||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN -> R.drawable.ic_course_discern | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL -> R.drawable.ic_course_spell | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN -> R.drawable.ic_course_spoken | |||
| AppConstants.COURSE_TYPE_ENGLISH_VOICE -> R.drawable.ic_course_voice | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION -> R.drawable.ic_course_composition | |||
| AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK -> R.drawable.ic_course_soundmark | |||
| AppConstants.COURSE_TYPE_CHINESE_PINYIN -> R.drawable.ic_course_pinying | |||
| AppConstants.COURSE_TYPE_CHINESE_LITERACY -> R.drawable.ic_course_literacy | |||
| else -> 0 | |||
| } | |||
| } | |||
| private fun getCourseIconOriginColor(courseType : Int) : Int { | |||
| return when (courseType) { | |||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN -> Color.parseColor("#5082E6") | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL -> Color.parseColor("#F26255") | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPOKEN, AppConstants.COURSE_TYPE_ENGLISH_VOICE -> Color.parseColor("#52CC52") | |||
| AppConstants.COURSE_TYPE_CHINESE_COMPOSITION, AppConstants.COURSE_TYPE_ENGLISH_SOUNDMARK -> Color.parseColor( | |||
| "#8757E6") | |||
| AppConstants.COURSE_TYPE_CHINESE_PINYIN -> Color.parseColor("#EB54D8") | |||
| AppConstants.COURSE_TYPE_CHINESE_LITERACY -> Color.parseColor("#FF8B52") | |||
| else -> 0 | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.xkl.cdl.data.bean | |||
| import com.xkl.cdl.data.bean.course.CoursePack | |||
| /** | |||
| * author suliang | |||
| * create 2022/5/31 9:53 | |||
| * Describe: 课程包备忘本实体 | |||
| */ | |||
| class MemoCoursePack(val coursePack: CoursePack){ | |||
| //对应课程的备忘本数据 | |||
| var coursePackChildrenMemoNumber = mutableListOf<MutableList<String>>() | |||
| //对应需要复习的数据 | |||
| var coursePackChildrenReviewNumber = mutableListOf<MutableList<String>>() | |||
| } | |||
| @@ -15,17 +15,13 @@ class CourseDetail:Serializable{ | |||
| var st_after: Double = AppConstants.NOT_DOING //学后总成绩 : -1代表没有进行学后总测试 | |||
| var before: HashMap<String, Double> = hashMapOf() //章节学前测试成绩,key=>{chapter_id}_{lesson_id} value=>成绩 | |||
| var after: HashMap<String, Double> = hashMapOf() //章节学后测试成绩,key=>{chapter_id}_{lesson_id} value=>成绩 | |||
| var right = hashMapOf<String, Int>() //正确条目数,key=>{chapter_id}_{lesson_id} value=>条目数量 | |||
| var wrong: HashMap<String, Int> = hashMapOf() //错误条目数,key=>{chapter_id}_{lesson_id} value=>条目数量 | |||
| var right = hashMapOf<String,Long>() //正确条目数,key=>{chapter_id}_{lesson_id} value=>条目数量 | |||
| var wrong: HashMap<String, Long> = hashMapOf() //错误条目数,key=>{chapter_id}_{lesson_id} value=>条目数量 | |||
| var lesson_learn_point: HashMap<String, Long> = hashMapOf() //课时学习点 key=>{chapter_id}_{lesson_id} value=>{entity_id) | |||
| var exam_w_r_list: HashMap<String, Boolean> = hashMapOf() //课程/课时学前测试正确错误列表 key=> {chapter_id}_{lesson_id}_{entity_id} value=>正确 true;错误 false | |||
| var course_learn_point: String = "" //课程学习进度点 {chapter_id}_{lesson_id}_{entity_id} | |||
| var vp: HashMap<Long, String> = hashMapOf() //视频播放点,记录最新的就行 时间点 key=>video_id value=>{时间点} | |||
| var exercise_schedule: HashMap<Long, Double> = hashMapOf() //课程练习进度(仅作文有效) | |||
| var rl: Int = 0 //课程重学次数;有值表示是重新学,默认不是重新学习,对应数值表示第几次重学 用于辨音,拼写判断是否解锁, | |||
| var chapter_rl: HashMap<String, Long> = hashMapOf() //章节重学次数,key =>{chapter_id}_{lesson_id} value=> relearn times | |||
| var ct: Long = 0 //课程已学词汇量 | |||
| var e = 0.0 //学习效率 | |||
| var valid: HashMap<Long, Long> = hashMapOf() //有效学习时长,单位毫秒 key=>分时标记,value=>时长 | |||
| @@ -35,6 +31,10 @@ class CourseDetail:Serializable{ | |||
| var current_week_total_durations: Long = 0 | |||
| var last_e: Double = 0.0 //上周期学习效率 | |||
| var temporary_words: HashMap<String, String> = hashMapOf() //课程错误本,重学会删除 key=>{chapter_id}_{lesson_id}_{entity_id} value=> first learn time | |||
| // var exercise_schedule: HashMap<Long, Double> = hashMapOf() //课程练习进度(仅作文有效) | |||
| // var rl: Int = 0 //课程重学次数;有值表示是重新学,默认不是重新学习,对应数值表示第几次重学 用于辨音,拼写判断是否解锁, | |||
| // var chapter_rl: HashMap<String, Long> = hashMapOf() //章节重学次数,key =>{chapter_id}_{lesson_id} value=> relearn times | |||
| } | |||
| @@ -37,35 +37,38 @@ data class CoursePack( | |||
| field = value | |||
| notifyPropertyChanged(BR.learnProgress) | |||
| } | |||
| override fun equals(other: Any?): Boolean { | |||
| override fun equals(other : Any?) : Boolean { | |||
| if (this === other) return true | |||
| if (javaClass != other?.javaClass) return false | |||
| other as CoursePack | |||
| if (coursePackId != other.coursePackId) return false | |||
| if (coursePackName != other.coursePackName) return false | |||
| if (!cover.contentEquals(other.cover)) return false | |||
| if (summary != other.summary) return false | |||
| if (subjectId != other.subjectId) return false | |||
| if (coursePackType != other.coursePackType) return false | |||
| if (inCoursePackPosition != other.inCoursePackPosition) return false | |||
| if (childrenCourses != other.childrenCourses) return false | |||
| if (learnProgress != other.learnProgress) return false | |||
| return true | |||
| } | |||
| override fun hashCode(): Int { | |||
| override fun hashCode() : Int { | |||
| var result = coursePackId.hashCode() | |||
| result = 31 * result + coursePackName.hashCode() | |||
| result = 31 * result + cover.contentHashCode() | |||
| result = 31 * result + summary.hashCode() | |||
| result = 31 * result + subjectId | |||
| result = 31 * result + coursePackType | |||
| result = 31 * result + inCoursePackPosition | |||
| result = 31 * result + childrenCourses.hashCode() | |||
| result = 31 * result + learnProgress.hashCode() | |||
| return result | |||
| } | |||
| } | |||
| @@ -2,15 +2,20 @@ package com.xkl.cdl.data.manager | |||
| import appApi.AppApi | |||
| import com.suliang.common.util.AppGlobals | |||
| import com.suliang.common.util.DateUtil | |||
| 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.* | |||
| import java.lang.Exception | |||
| import java.math.BigDecimal | |||
| import java.math.RoundingMode | |||
| import java.text.DateFormat | |||
| import java.text.DecimalFormat | |||
| import java.text.SimpleDateFormat | |||
| import java.util.* | |||
| import java.util.zip.ZipFile | |||
| import java.util.zip.ZipInputStream | |||
| @@ -402,4 +407,46 @@ object CourseManager { | |||
| } ?: 0 | |||
| } | |||
| // TODO: 2022/6/2 时间需改为day 开发期间用于调式,故意设此值 | |||
| //间隔时间单位 | |||
| private val reviewIntervalUnit = "day" | |||
| //间隔单位时间 | |||
| private val reviewTime = IntArray(7){ | |||
| when(it){ | |||
| 0 -> 1 | |||
| 1 -> 2 | |||
| 2 -> 3 | |||
| 3 -> 5 | |||
| 4 -> 7 | |||
| 5 -> 10 | |||
| else -> 15 | |||
| } | |||
| } | |||
| /** | |||
| * 计算是否需要仅需复习 | |||
| * @param currentReviewNumber String 当前词复习的次数 | |||
| * @param currentReviewTime String 当前词复习的时间 | |||
| * @param currentTime Long 用以计算的当前时间 | |||
| * @return Boolean true:需要复习 false:不需要复习 | |||
| */ | |||
| fun calculateIsNeedInReview(calendar:Calendar,currentReviewNumber : Int, currentReviewTime : String, currentTime : Long) : Boolean { | |||
| if (currentReviewNumber >= reviewTime.size) return false | |||
| try { | |||
| //转换设置当前的复习时间 | |||
| calendar.time = DateUtil.format(currentReviewTime,DateUtil.FORMAT_1) | |||
| //与当前时间的间隔 | |||
| val intervalTime = currentTime - calendar.timeInMillis | |||
| //单位转换 : 给一个提前量的时间,提前一分钟 | |||
| return intervalTime >= when(reviewIntervalUnit){ | |||
| "minute" -> reviewTime[currentReviewNumber] * 60000 | |||
| "hour" -> reviewTime[currentReviewNumber] * 60 * 60000 | |||
| else -> reviewTime[currentReviewNumber] * 24 * 60 * 60000 | |||
| } - 60000 | |||
| }catch (e:Exception){ | |||
| e.printStackTrace() | |||
| } | |||
| return false | |||
| } | |||
| } | |||
| @@ -111,8 +111,8 @@ object DBCourseManager { | |||
| lessonPositionInList = positionIndex | |||
| this.wordIds = wordIds //内容 | |||
| totalNumber = this.wordIds.size //总数 | |||
| correctNumber = detail.right.getOrElse(key, { 0 }) //正确数 | |||
| errorNumber = detail.wrong.getOrElse(key, { 0 }) //错误数 | |||
| correctNumber = detail.right.getOrElse(key, { 0 }).toInt() //正确数 | |||
| errorNumber = detail.wrong.getOrElse(key, { 0 }).toInt() //错误数 | |||
| beforeTestScore = detail.before.getOrElse(key, { AppConstants.NOT_DOING }) //课时学前测试成绩 | |||
| afterTestScore = detail.after.getOrElse(key, { AppConstants.NOT_DOING }) //课时学后测试成绩 | |||
| this.lessonType = lessonType | |||
| @@ -1,12 +1,16 @@ | |||
| package com.xkl.cdl.data.repository | |||
| import appApi.AppApi | |||
| import com.google.protobuf.ProtocolStringList | |||
| import com.googlecode.protobuf.format.JsonFormat | |||
| import com.suliang.common.extension.io2Io | |||
| import com.suliang.common.util.file.FileUtil | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.bean.course.CourseDetail | |||
| 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.module.XKLApplication | |||
| import io.reactivex.rxjava3.core.Observable | |||
| import mqComsumerV1.Struct | |||
| import java.io.File | |||
| @@ -18,7 +22,8 @@ import java.io.FileOutputStream | |||
| * Describe: 数据提供 | |||
| */ | |||
| object DataRepository { | |||
| // TODO: 2022/6/1 需自己实现获取课程收藏数据,同时需自己处理保存数据 | |||
| /** 获取课程的收藏本 */ | |||
| fun getCourseCollect() : Observable<HashMap<String,Long>>{ | |||
| return Observable.create{ | |||
| @@ -28,27 +33,63 @@ object DataRepository { | |||
| } | |||
| /** 获取复习的内容 */ | |||
| fun getReviewData() : Observable<Array<String>> { | |||
| fun getReviewData(subjectId : Int,coursePackId : Long,courseId : Long) : Observable<ProtocolStringList> { | |||
| return Observable.create{ | |||
| mutableListOf<String>() | |||
| it.onNext(Array<String>(1){"1"}) | |||
| val wordList = XKLApplication.mobileCache.getWordList(subjectId.toLong(), coursePackId, courseId, 2) | |||
| val parseFrom = wordList?.let { | |||
| AppApi.GetWordListResponse.parseFrom(wordList) | |||
| }?: AppApi.GetWordListResponse.newBuilder().build() | |||
| it.onNext(parseFrom.wrongList) | |||
| it.onComplete() | |||
| } | |||
| } | |||
| /** 获取课程的详情 */ | |||
| fun getCourseStatistics(subjectId:Int,coursePackId:Long,courseId:Long): Observable<CourseDetail>{ | |||
| fun getCourseDetails(subjectId:Int, coursePackId:Long, courseId:Long): Observable<CourseDetail>{ | |||
| return Observable.create{ | |||
| // TODO: 2022/5/6 这里自己使用的缓存 | |||
| val file = File(FileUtil.getSaveDirPath("appcache"), "${subjectId}_${coursePackId}_${courseId}") | |||
| val courseDetail = if (file.exists()){ | |||
| FileUtil.bytesToObject(FileUtil.readFile(file)) as CourseDetail | |||
| }else{ | |||
| CourseDetail() | |||
| val pbCourseDetail = XKLApplication.mobileCache.courseDetail(subjectId.toLong(), coursePackId, courseId).let { | |||
| AppApi.CourseDetailResponse .parseFrom(it).detail | |||
| } | |||
| val courseDetail = CourseDetail().apply { | |||
| courseLearnProgress = pbCourseDetail.s //课程学习进度 | |||
| st_before = pbCourseDetail.stBefore //学前总成绩 : -1代表没有进行学前总测试 | |||
| st_after = pbCourseDetail.stAfter //学后总成绩 : -1代表没有进行学后总测试 | |||
| before = HashMap(pbCourseDetail.beforeMap) //章节学前测试成绩,key=>{chapter_id}_{lesson_id} value=>成绩 | |||
| after = HashMap(pbCourseDetail.afterMap) //章节学后测试成绩,key=>{chapter_id}_{lesson_id} value=>成绩 | |||
| right = HashMap(pbCourseDetail.rightMap) //正确条目数,key=>{chapter_id}_{lesson_id} value=>条目数量 | |||
| wrong = HashMap(pbCourseDetail.wrongMap) //错误条目数,key=>{chapter_id}_{lesson_id} value=>条目数量 | |||
| lesson_learn_point = HashMap( | |||
| pbCourseDetail.chapterLpMap) //课时学习点 key=>{chapter_id}_{lesson_id} value=>{entity_id) | |||
| exam_w_r_list = HashMap( | |||
| pbCourseDetail.examWRListMap) //课程/课时学前测试正确错误列表 key=> {chapter_id}_{lesson_id}_{entity_id} value=>正确 true;错误 false | |||
| course_learn_point = pbCourseDetail.lp //课程学习进度点 {chapter_id}_{lesson_id}_{entity_id} | |||
| vp = HashMap(pbCourseDetail.vpMap) //视频播放点,记录最新的就行 时间点 key=>video_id value=>{时间点} | |||
| ct = pbCourseDetail.ct //课程已学词汇量 | |||
| e = pbCourseDetail.e //学习效率 | |||
| valid = HashMap(pbCourseDetail.validMap) //有效学习时长,单位毫秒 key=>分时标记,value=>时长 | |||
| review = HashMap(pbCourseDetail.reviewMap) //有效复习时长,单位毫秒 key=>分时标记,value=>时长 | |||
| total = HashMap(pbCourseDetail.totalMap) //总时长,单位毫秒 key=>分时标记,value=>时长 | |||
| current_week_total_durations = pbCourseDetail.currentWeekTotalDurations | |||
| last_e = pbCourseDetail.lastE //上周期学习效率 | |||
| temporary_words = HashMap( | |||
| pbCourseDetail.temporaryWordsMap) //课程错误本,重学会删除 key=>{chapter_id}_{lesson_id}_{entity_id} value=> first learn time | |||
| // exercise_schedule: HashMap<Long, Double> = HashMap(pbCourseDetail.exerciseScheduleMap) //课程练习进度(仅作文有效) | |||
| // rl: Int = 0 //课程重学次数;有值表示是重新学,默认不是重新学习,对应数值表示第几次重学 用于辨音,拼写判断是否解锁, | |||
| // chapter_rl: HashMap<String, Long> = hashMapOf() //章节重学次数,key =>{chapter_id}_{lesson_id} value=> relearn times | |||
| } | |||
| it.onNext(courseDetail) | |||
| it.onComplete() | |||
| // T这里自己使用的缓存 | |||
| // val file = File(FileUtil.getSaveDirPath("appcache"), "${subjectId}_${coursePackId}_${courseId}") | |||
| // val courseDetail = if (file.exists()){ | |||
| // FileUtil.bytesToObject(FileUtil.readFile(file)) as CourseDetail | |||
| // }else{ | |||
| // CourseDetail() | |||
| // } | |||
| } | |||
| } | |||
| @@ -96,9 +137,15 @@ object DataRepository { | |||
| //计算词汇量和学习效率 | |||
| calcCourseVocabularyAndEfficiency(subjectId,coursePackId,courseId) | |||
| } | |||
| // TODO: 2022/4/29 调用保存方法 | |||
| try { | |||
| XKLApplication.mobileCache.parseData(record.build().toByteArray()) | |||
| } catch (e : Exception) { | |||
| e.printStackTrace() | |||
| return false | |||
| } | |||
| var fileName = "" | |||
| /*var fileName = "" | |||
| //写入文件 | |||
| if (record.examCount > 0 ){ //当前为测试 | |||
| val s = when (record.getExam(0).type.toInt()) { //测试类型 | |||
| @@ -124,20 +171,29 @@ object DataRepository { | |||
| //序列化输出 | |||
| val string = JsonFormat.printToString(record.build()) | |||
| val saveArray = record.build().toByteArray() | |||
| val saveStringName = "${fileName}_jsonString.txt" | |||
| FileUtil.writeBytesToFile(it,saveStringName,string.toByteArray()) | |||
| val saveByteArrayName = "${fileName}_byteArray.txt" | |||
| FileUtil.writeBytesToFile(it,saveByteArrayName,saveArray) | |||
| } | |||
| }*/ | |||
| return true | |||
| } | |||
| // TODO: 2022/6/1 计算词汇量和效率可以在需要的时候进行计算后再获取 | |||
| /** | |||
| * 计算当前词汇量 与 学习效率 | |||
| */ | |||
| public fun calcCourseVocabularyAndEfficiency( projectId:Long, packId:Long, courseId:Long) { | |||
| private fun calcCourseVocabularyAndEfficiency( projectId:Long, packId:Long, courseId:Long) { | |||
| Observable.create<Boolean> { | |||
| try { | |||
| XKLApplication.mobileCache.calcCurrentVocabulary(projectId) //词汇量 | |||
| XKLApplication.mobileCache.calcEfficiency(projectId,packId,courseId) //效率 | |||
| } catch (e : Exception) { | |||
| e.printStackTrace() | |||
| } | |||
| it.onComplete() | |||
| }.compose(io2Io()).subscribe() | |||
| } | |||
| } | |||
| @@ -2,15 +2,16 @@ package com.xkl.cdl.module | |||
| import android.app.Application | |||
| import com.suliang.common.util.LogUtil | |||
| import com.suliang.common.util.file.FileUtil | |||
| import com.tencent.mmkv.MMKV | |||
| import io.reactivex.rxjava3.exceptions.UndeliverableException | |||
| import io.reactivex.rxjava3.functions.Consumer | |||
| import io.reactivex.rxjava3.plugins.RxJavaPlugins | |||
| import mobile_cache.MobileCache | |||
| import mobile_cache.Mobile_cache | |||
| import net.sqlcipher.database.SQLiteDatabase | |||
| import java.io.File | |||
| import java.io.IOException | |||
| import java.lang.IllegalArgumentException | |||
| import java.lang.IllegalStateException | |||
| import java.lang.NullPointerException | |||
| import java.util.* | |||
| /** | |||
| @@ -19,17 +20,26 @@ import java.util.* | |||
| * Describe: | |||
| */ | |||
| class XKLApplication : Application() { | |||
| companion object{ | |||
| val mobileCache:MobileCache by lazy { | |||
| val file : String = File(FileUtil.getSaveDirFile("db"), "mydb").absolutePath | |||
| Mobile_cache.new_(file) | |||
| } | |||
| } | |||
| override fun onCreate() { | |||
| super.onCreate() | |||
| SQLiteDatabase.loadLibs(this) | |||
| // ImageLoader.mStrategy = GlideLoaderStrategy() | |||
| LogUtil.e(UUID.randomUUID().toString().replace("-","")) | |||
| //初始MMKV存储 | |||
| val rootDir = MMKV.initialize(this) | |||
| LogUtil.e(rootDir) | |||
| setRxJavaErrorHandler() | |||
| // HookMobileCache().hook() | |||
| } | |||
| /*** | |||
| * 避免 调用多次onError。正常来说第一次onError会走正常的Observer处理,其他会走ErrorHandler。通过此方法捕捉多次的error | |||
| */ | |||
| @@ -95,7 +95,7 @@ class LearnCReadingViewModel : LearnBaseViewModel() { | |||
| } | |||
| /** | |||
| * 添加到收藏 | |||
| * 取消收藏 | |||
| * @return MutableLiveData<Long> | |||
| */ | |||
| fun removeCollect() : MutableLiveData<Long> { | |||
| @@ -138,7 +138,6 @@ class LearnCReadingViewModel : LearnBaseViewModel() { | |||
| } | |||
| fun saveData() { | |||
| // TODO: 2022/5/20 保存记录点 如果学习完成,则设置进度点和修改为学习完成 | |||
| Observable.create<Boolean> { emitter -> | |||
| val record = Struct.Record.newBuilder().apply { | |||
| addEntity(Struct.LearnEntity.newBuilder().apply { | |||
| @@ -14,8 +14,10 @@ import com.xkl.cdl.data.event.LearnEventData | |||
| 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.XKLApplication | |||
| import com.xkl.videoplayer.bean.PineMediaPlayerBean | |||
| import io.reactivex.rxjava3.core.Observable | |||
| import io.reactivex.rxjava3.functions.BiFunction | |||
| import mqComsumerV1.Struct | |||
| @@ -172,8 +174,20 @@ class LearnCVideoViewModel : LearnBaseViewModel() { | |||
| uploadDate.value = true | |||
| return | |||
| } | |||
| // TODO: 2022/5/26 保存视频的播放时间 | |||
| Observable.create<Boolean> { emitter -> | |||
| Observable.zip( | |||
| Observable.create<Boolean> { | |||
| try { | |||
| //保存视频播放时间 | |||
| XKLApplication.mobileCache.setVideoPoint(lesson.subjectId.toLong(), lesson.coursePackId, lesson.courseId, lesson.wordIds[0], currentPlayTime.toString()) | |||
| it.onNext(true) | |||
| } catch (e : Exception) { | |||
| e.printStackTrace() | |||
| it.onNext(false) | |||
| } | |||
| it.onComplete() | |||
| }, | |||
| Observable.create<Boolean> { emitter -> | |||
| //保存数据 | |||
| val record = Struct.Record.newBuilder().apply { | |||
| //进度点 | |||
| addEntity(Struct.LearnEntity.newBuilder().apply { | |||
| @@ -206,7 +220,13 @@ class LearnCVideoViewModel : LearnBaseViewModel() { | |||
| } | |||
| emitter.onNext(DataRepository.saveRecord(record)) | |||
| emitter.onComplete() | |||
| }.compose(diskIo2Main()).subscribe { | |||
| }, { t1, t2 -> | |||
| t1 && t2 | |||
| }).compose(diskIo2Main()).subscribe { | |||
| if (!it){ | |||
| LogUtil.e("视频数据保存有误") | |||
| return@subscribe | |||
| } | |||
| lesson.videoPlayTime = currentPlayTime | |||
| LogUtil.e("当前播放时间:$currentPlayTime") | |||
| when{ | |||
| @@ -591,7 +591,6 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
| /** 测试完成 : 弹窗显示 */ | |||
| private fun testOver() { | |||
| // TODO: 2022/5/17 作文测试弹窗 | |||
| //对话框信息实体 | |||
| val learnDialogBean = LearnDialogBean(AppConstants.DIALOG_TYPE_EXAM_OVER).apply { | |||
| examType = vm.intentData.examType | |||
| @@ -646,14 +646,9 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
| private fun saveData() { | |||
| showHideLoading(true) | |||
| Observable.create<Boolean> { | |||
| viewModelScope.launch { | |||
| delay(1000) | |||
| DataRepository.saveRecord(record) | |||
| it.onNext(true) | |||
| it.onComplete() | |||
| } | |||
| // TODO: 2022/4/14 传递保存record信息 | |||
| val saveRecord = DataRepository.saveRecord(record) | |||
| it.onNext(saveRecord) | |||
| it.onComplete() | |||
| }.compose(diskIo2Main()).subscribe({ | |||
| showHideLoading(false) | |||
| sendEventBus() //返回发送数据 | |||
| @@ -97,7 +97,7 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
| /** 横幅 自动播放 与 复习时使用 */ | |||
| private fun initBanner() { | |||
| // TODO: 2022/4/25 初始化横幅,默认隐藏 | |||
| // 初始化横幅,默认隐藏 | |||
| if (vm.learnData.isAutoPlay) { | |||
| binding.tvBanner.apply { | |||
| visibility = View.VISIBLE | |||
| @@ -293,11 +293,6 @@ class LearnWordViewModel : LearnBaseViewModel() { | |||
| fun saveData() { | |||
| showHideLoading(true) | |||
| Observable.create<Boolean> { | |||
| viewModelScope.launch { | |||
| delay(1000) | |||
| it.onNext(true) | |||
| } | |||
| // TODO: 2022/4/14 传递保存record信息 | |||
| // record 已经实例化并已经将数据保存 | |||
| if (!saveInit) { | |||
| //自动播放不修改课时信息 | |||
| @@ -314,9 +309,8 @@ class LearnWordViewModel : LearnBaseViewModel() { | |||
| record.addDuration(saveCurrentLearnDuration()) | |||
| saveInit = true | |||
| } | |||
| DataRepository.saveRecord(record) | |||
| // LogUtil.e(JsonFormat.printToString(record.build())) | |||
| it.onNext(DataRepository.saveRecord(record)) | |||
| it.onComplete() | |||
| }.compose(diskIo2Main()).subscribe({ | |||
| showHideLoading(false) | |||
| sendEventBus() //返回发送数据 | |||
| @@ -39,7 +39,7 @@ import com.xkl.cdl.module.m_center_learn.coursechildren.CourseMainFragment | |||
| import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly | |||
| /** | |||
| * 课程中心 | |||
| * 课程包主页中心 | |||
| */ | |||
| class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CoursePackMainActivityViewModel>() { | |||
| @@ -56,9 +56,6 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP | |||
| //子课程对应的Fragment | |||
| private var childFragments = mutableListOf<Fragment>() | |||
| // //更多按钮的弹窗显示 | |||
| // private var moreDialog : BottomSheetDialog? = null | |||
| //自动播放次数的弹窗选择 | |||
| private var autoPlaySeletDialog : BottomSheetDialog? = null | |||
| @@ -295,14 +292,6 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP | |||
| } | |||
| } | |||
| override fun onStop() { | |||
| super.onStop() | |||
| //如果是中文项目,更新课程包的进度 | |||
| if (vm.coursePack.subjectId == AppConstants.SUBJECT_CHINESE) { | |||
| vm.coursePack.learnProgress = vm.coursePack.childrenCourses[0].courseLearnProgress | |||
| } | |||
| } | |||
| /** ViewModel Factory工厂 */ | |||
| inner class ViewModelFactory(private val subjectId : Int, private val coursePackInPosition : Int) : | |||
| @@ -1,7 +1,9 @@ | |||
| package com.xkl.cdl.module.m_center_learn | |||
| import androidx.lifecycle.LifecycleOwner | |||
| import androidx.lifecycle.MutableLiveData | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.manager.CourseManager | |||
| /** | |||
| @@ -17,4 +19,11 @@ class CoursePackMainActivityViewModel(subjectId: Int , coursePackInPosition : In | |||
| //设置显示当前课程的进度值和显示内容 | |||
| val currentCourseProgress = MutableLiveData<Double>() | |||
| override fun onStop(owner : LifecycleOwner) { | |||
| super.onStop(owner) | |||
| //如果是中文项目,更新课程包的进度 | |||
| if (coursePack.subjectId == AppConstants.SUBJECT_CHINESE) { | |||
| coursePack.learnProgress = coursePack.childrenCourses[0].courseLearnProgress | |||
| } | |||
| } | |||
| } | |||
| @@ -85,9 +85,8 @@ class LearnCenterFragment : BaseFragmentVM<FragmentLearnCenterBinding, LearnCent | |||
| //TabLayout 与 ViewPager2 进行关联, 并进行tab设值 | |||
| TabLayoutMediator(binding.tabLayout, binding.viewPager2) { tab, position -> | |||
| tab.setCustomView(layoutInflater.inflate(R.layout.textview_only,binding.tabLayout,false)) | |||
| tab.customView?.let { | |||
| (it as TextView).setText(vm.mProjectTitles[position]) | |||
| tab.customView = layoutInflater.inflate(R.layout.textview_only, binding.tabLayout, false).apply { | |||
| (this as TextView).setText(vm.mProjectTitles[position]) | |||
| } | |||
| }.attach() | |||
| @@ -104,20 +103,6 @@ class LearnCenterFragment : BaseFragmentVM<FragmentLearnCenterBinding, LearnCent | |||
| } | |||
| } | |||
| // /** 搜索框 输入监听 */ | |||
| // binding.editTextSearch.setOnEditorActionListener { v, actionId, event -> | |||
| // if (actionId == EditorInfo.IME_ACTION_SEARCH){ | |||
| // //隐藏键盘 | |||
| // KeyboardUtil.hideKeyboard(v) | |||
| // //赋值,通知 子 Fragment 进行更新 | |||
| // v.text.toString() | |||
| // | |||
| // true | |||
| // }else { | |||
| // false | |||
| // } | |||
| // } | |||
| } | |||
| override fun loadData() { | |||
| @@ -81,7 +81,7 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| vm.allLesson[learnEventData.leesonPositionIndex].let { | |||
| val key = "${it.chapterId}_${it.lessonId}" | |||
| vm.courseDetail.before.put(key, it.beforeTestScore) | |||
| vm.courseDetail.wrong.put(key, it.errorNumber) | |||
| vm.courseDetail.wrong.put(key, it.errorNumber.toLong()) | |||
| learnEventData.newErrorMap?.let { it1 -> | |||
| vm.courseDetail.exam_w_r_list.putAll(it1) | |||
| } | |||
| @@ -98,8 +98,8 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| //单词类 | |||
| AppConstants.LESSON_TYPE_WORD ->{ | |||
| vm.courseDetail.run { | |||
| wrong[key] = it.errorNumber //更新错误数 | |||
| right[key] = it.correctNumber //更新正确数 | |||
| wrong[key] = it.errorNumber.toLong() //更新错误数 | |||
| right[key] = it.correctNumber.toLong() //更新正确数 | |||
| lesson_learn_point[key] = it.wordIds[it.learnedIndex] //更新课时学习点 | |||
| course_learn_point = "${key}_${it.wordIds[it.learnedIndex]}" //更新课程学习点 | |||
| } | |||
| @@ -130,8 +130,8 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| } | |||
| //知识点 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_KNOWLEDGE -> vm.courseDetail.run { | |||
| wrong[key] = it.errorNumber //更新错误数 | |||
| right[key] = it.correctNumber //更新正确数 | |||
| wrong[key] = it.errorNumber.toLong() //更新错误数 | |||
| right[key] = it.correctNumber.toLong() //更新正确数 | |||
| lesson_learn_point[key] = it.wordIds[it.learnedIndex] //更新课时学习点 | |||
| course_learn_point = "${key}_${it.wordIds[it.learnedIndex]}" //更新课程学习点 | |||
| } | |||
| @@ -205,7 +205,6 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| //学后测试结束,弹窗动作 课时重新学习 | |||
| AppConstants.ACTION_LESSON_AFTER_TEST_RELEARN -> { | |||
| //课时学后测试,点击重学 | |||
| // TODO: 2022/4/22 清除当前课时数据,并更新当前课时,后进入学习界面 | |||
| vm.allLesson[learnEventData.leesonPositionIndex].learnIsOver = false | |||
| vm.relearnLesson(learnEventData.leesonPositionIndex).observe(this) { | |||
| //item更新 | |||
| @@ -390,7 +389,7 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| dialog.dismissAllowingStateLoss() | |||
| when (action) { | |||
| AppConstants.DIALOG_START_LEARN -> { //跳过测试 继续学习 | |||
| // TODO: 2022/4/21 进入学习界面 | |||
| // TODO: 2022/4/21 进入学习界面 ,不能跳过测试,必须学习 | |||
| } | |||
| // 开始测试 | |||
| AppConstants.DIALOG_START_TEST -> startLessonTest(lesson, AppConstants.TEST_TYPE_BEFORE, it) | |||
| @@ -115,9 +115,9 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
| * 改变加载的子Fragment 单词类 | |||
| */ | |||
| private fun changeChildrenFragmentForWord() { | |||
| // TODO: 2022/5/5 复习页面加载 | |||
| currentFragment = vm.courseDetail.let { | |||
| when{ | |||
| vm.reviewDataList.size > 0 -> CourseReviewFragment.newInstanceFromLearningCenter() //有复习 | |||
| it.st_before == AppConstants.NOT_DOING -> CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_BEFORE_TOTAL) //学前总测未做 | |||
| it.courseLearnProgress != AppConstants.DOING_OVER -> CourseLessonFragment.newInstance() //学习未完成 | |||
| else -> CourseTotalTestFragment.newInstance(AppConstants.TEST_TYPE_AFTER_TOTAL) //学习完成,学后总测试界面 | |||
| @@ -130,9 +130,9 @@ class CourseMainFragment : BaseFragmentVM<FragmentCourseMainBinding, CourseMainF | |||
| * 改变加载的子Fragment 作文 | |||
| */ | |||
| private fun changeChildrenFragmentForComposition() { | |||
| // TODO: 2022/5/5 复习页面加载 CourseReviewFragment.newInstance() | |||
| currentFragment = vm.courseDetail.let { | |||
| CourseLessonFragment.newInstance() | |||
| currentFragment = when { | |||
| vm.reviewDataList.size > 0 -> CourseReviewFragment.newInstanceFromLearningCenter() //复习 | |||
| else -> CourseLessonFragment.newInstance() //目录 | |||
| } | |||
| replaceFragment(R.id.layout_root, currentFragment) | |||
| } | |||
| @@ -3,6 +3,7 @@ package com.xkl.cdl.module.m_center_learn.coursechildren | |||
| import androidx.lifecycle.LifecycleOwner | |||
| import androidx.lifecycle.MutableLiveData | |||
| import com.google.common.base.Joiner | |||
| import com.google.protobuf.ProtocolStringList | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| import com.suliang.common.extension.diskIo2DiskIo | |||
| import com.suliang.common.extension.diskIo2Main | |||
| @@ -22,15 +23,15 @@ import com.xkl.cdl.data.manager.CourseManager | |||
| 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.XKLApplication | |||
| 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.Scheduler | |||
| import io.reactivex.rxjava3.functions.BiFunction | |||
| import io.reactivex.rxjava3.schedulers.Schedulers | |||
| import mqComsumerV1.Struct | |||
| import java.io.File | |||
| import java.util.* | |||
| import kotlin.collections.ArrayList | |||
| import kotlin.collections.HashMap | |||
| /** | |||
| @@ -54,7 +55,10 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| lateinit var collectList : HashMap<String, Long> | |||
| /** 复习的数据 */ | |||
| lateinit var reviewDataList : MutableList<String> | |||
| lateinit var reviewDataList : MutableList<LearnWord> | |||
| /* 复习数据是否进行了内容赋值,如果进行了赋值,则直接复习,如果没有进行赋值,需要在复习前,进行查询赋值 */ | |||
| var isAssignmentReviewData = false | |||
| /** 课程所有课时 */ | |||
| lateinit var allLesson : List<Lesson> | |||
| @@ -68,7 +72,7 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| //获取课程的章节数据 | |||
| fun loadMain() : MutableLiveData<Boolean> { | |||
| val mutableLiveData = MutableLiveData<Boolean>() | |||
| Observable.zip(DataRepository.getCourseStatistics(course.subjectId, course.coursePackId, course.courseId).flatMap { | |||
| Observable.zip(DataRepository.getCourseDetails(course.subjectId, course.coursePackId, course.courseId).flatMap { | |||
| courseDetail = it | |||
| course.courseLearnProgress = it.courseLearnProgress | |||
| coursePackMainActivityVM.currentCourseProgress.postValue(course.courseLearnProgress) | |||
| @@ -76,9 +80,12 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| }.flatMap { | |||
| allLesson = it | |||
| return@flatMap DataRepository.getCourseCollect() | |||
| }, DataRepository.getReviewData(), | |||
| BiFunction<HashMap<String, Long>, Array<String>, Boolean> { t1 : HashMap<String, Long>?, t2 : Array<String>? -> | |||
| // TODO: 2022/5/5 初始化需要复习的数据 | |||
| }, DataRepository.getReviewData(course.subjectId, course.coursePackId, course.courseId), | |||
| BiFunction<HashMap<String, Long>, ProtocolStringList, Boolean> { t1 : HashMap<String, Long>, t2 : ProtocolStringList -> | |||
| //保存课程的收藏本数据 | |||
| collectList = t1 | |||
| //初始化保存需要复习的数据 | |||
| initReviewData(t2) | |||
| true | |||
| }).compose(diskIo2Main()).subscribe { | |||
| mutableLiveData.value = it | |||
| @@ -86,6 +93,38 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| return mutableLiveData | |||
| } | |||
| /** | |||
| * 初始化复习的数据 | |||
| * @param rev Array<String> 需要复习的数据 value 格式 {project_id}_{pack_id}_{course_id}_{chapter_id}_{lesson_id}_{entity_id}_{review_num}_{Y-m-d H:i:s}_{Y-m-d H:i:s} | |||
| */ | |||
| private fun initReviewData(rev : ProtocolStringList) { | |||
| if (!this::reviewDataList.isInitialized) { | |||
| reviewDataList = mutableListOf() | |||
| } | |||
| reviewDataList.clear() | |||
| //当前时间戳 | |||
| val currentTime = System.currentTimeMillis() | |||
| //日历 | |||
| val calendar = Calendar.getInstance(Locale.CHINA) | |||
| //循环赋值保存需要复习的数据 | |||
| rev.forEach { | |||
| val splitValue = it.split("_") //拆分为数组 | |||
| //判断是否能够进入复习 | |||
| val isNeedInReview = CourseManager.calculateIsNeedInReview(calendar, splitValue[6].toInt(), splitValue[7], | |||
| currentTime) | |||
| if (isNeedInReview) { | |||
| // lessonType为非准确的赋值,需要在查询具体详情数据的时候,在对lessonType进行具体赋值 | |||
| reviewDataList.add( | |||
| LearnWord(splitValue[0].toInt(), splitValue[1].toLong(), splitValue[2].toLong(), course.coursePackType, | |||
| course.courseType, splitValue[3].toLong(), splitValue[4].toLong(), splitValue[5].toLong(), true, | |||
| 0).apply { | |||
| reviewNum = splitValue[6].toInt() | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| /** 课程包主页上的更多按钮点击是否有效 */ | |||
| fun showMoreIsEnable() : Boolean { | |||
| return when (course.courseType) { | |||
| @@ -119,7 +158,7 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| fun modifyLessonErrorNumber() { | |||
| //重设detail中的错误数, exam_w_r_list为所有的测试前错误,因为时学前总测,所以,可以根据这个字段来计算每个课时的错误数 | |||
| //记录错误数: key :chapterId_lessonId value :错误数 | |||
| val errorNumber = hashMapOf<String, Int>() | |||
| val errorNumber = hashMapOf<String, Long>() | |||
| courseDetail.exam_w_r_list.keys.forEach { | |||
| val split = it.split("_") | |||
| if (split.size == 3) { | |||
| @@ -131,7 +170,7 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| //更新lesson中的错误数 | |||
| allLesson.forEach { | |||
| val key = "${it.chapterId}_${it.lessonId}" | |||
| it.errorNumber = errorNumber.getOrElse(key, { 0 }) | |||
| it.errorNumber = errorNumber.getOrElse(key, { 0 }).toInt() | |||
| } | |||
| } | |||
| @@ -213,8 +252,9 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| val subjectProgress = CourseManager.calculateSubjectProgressWithCourseLessonRelearn(course.subjectId, course.coursePackId, | |||
| course.courseId, courseProgress) | |||
| Observable.fromCallable { | |||
| // TODO: 2022/5/5 进行数据清除和保存 | |||
| XKLApplication.mobileCache.relearn(course.subjectId.toLong(),course.coursePackId,course.courseId,allLesson[lessonPositionIndex].chapterId, | |||
| allLesson[lessonPositionIndex].lessonId, | |||
| courseProgress,0.0,subjectProgress) | |||
| }.compose(diskIo2DiskIo()).subscribe { | |||
| //更新lesson | |||
| val lesson = allLesson[lessonPositionIndex].apply { | |||
| @@ -268,49 +308,30 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| val subjectProgress = CourseManager.calculateSubjectProgressWithCourseRelearn(course.subjectId, course.coursePackId, | |||
| course.courseId) | |||
| Observable.fromCallable { | |||
| // TODO: 2022/5/5 调用课程重学 重新请求详情 | |||
| val file = File(FileUtil.getSaveDirPath("appcache"), "${course.subjectId}_${course.coursePackId}_${course.courseId}") | |||
| if (file.exists()) { | |||
| file.delete() | |||
| } | |||
| // 调用课程重学 后会再次重新调用loadMain | |||
| XKLApplication.mobileCache.relearn(course.subjectId.toLong(),course.coursePackId,course.courseId,0, | |||
| 0, 0.0,0.0,subjectProgress) | |||
| }.compose(diskIo2DiskIo()).subscribe { | |||
| courseDetail.run { | |||
| courseLearnProgress = 0.0 | |||
| st_before = AppConstants.NOT_DOING | |||
| st_after = AppConstants.NOT_DOING | |||
| before.clear() | |||
| after.clear() | |||
| right.clear() | |||
| wrong.clear() | |||
| lesson_learn_point.clear() | |||
| exam_w_r_list.clear() | |||
| course_learn_point = "" | |||
| rl += 1 | |||
| temporary_words.clear() | |||
| vp.clear() | |||
| exercise_schedule.clear() | |||
| } | |||
| result.postValue(true) | |||
| } | |||
| return result | |||
| } | |||
| // TODO: 2022/5/6 这里在退出时进行了统计数据缓存保存,在loadMain的时候进行了read缓存 ,后期需要清除 | |||
| override fun onDestroy(owner : LifecycleOwner) { | |||
| if (owner is CourseMainFragment) { | |||
| LogUtil.e("${this.toString().substringAfterLast(".")} 保存文件 onDestroy") | |||
| try { | |||
| val objectToBytes = FileUtil.objectToBytes(courseDetail) | |||
| FileUtil.writeBytesToFile(FileUtil.getSaveDirPath("appcache"), | |||
| "${course.subjectId}_${course.coursePackId}_${course.courseId}", objectToBytes) | |||
| } catch (e : Exception) { | |||
| e.printStackTrace() | |||
| LogUtil.e("${javaClass} 保存文件异常失败") | |||
| } | |||
| } | |||
| super.onDestroy(owner) | |||
| } | |||
| /* // 这里在退出时进行了统计数据缓存保存,在loadMain的时候进行了read缓存 ,后期需要清除 | |||
| override fun onDestroy(owner : LifecycleOwner) { | |||
| if (owner is CourseMainFragment) { | |||
| LogUtil.e("${this.toString().substringAfterLast(".")} 保存文件 onDestroy") | |||
| try { | |||
| val objectToBytes = FileUtil.objectToBytes(courseDetail) | |||
| FileUtil.writeBytesToFile(FileUtil.getSaveDirPath("appcache"), | |||
| "${course.subjectId}_${course.coursePackId}_${course.courseId}", objectToBytes) | |||
| } catch (e : Exception) { | |||
| e.printStackTrace() | |||
| LogUtil.e("${javaClass} 保存文件异常失败") | |||
| } | |||
| } | |||
| super.onDestroy(owner) | |||
| }*/ | |||
| /** | |||
| * 单词类,自动播放,查询自动播放使用的单词 | |||
| @@ -373,7 +394,7 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() { | |||
| // TODO: 2022/5/19 查询课堂练习的收藏数据 需要给 CompositionReadingBean设置收藏id | |||
| Observable.create<List<CompositionReadingBean>> { | |||
| result.postValue(LearnData(entity).apply { | |||
| readingList = DBCourseManager.queryCompositionReading(dbControlBase,entity) | |||
| readingList = DBCourseManager.queryCompositionReading(dbControlBase, entity) | |||
| }) | |||
| it.onComplete() | |||
| }.compose(diskIo2Main()).subscribe() | |||
| @@ -1,43 +1,112 @@ | |||
| package com.xkl.cdl.module.m_center_learn.coursechildren | |||
| import android.os.Bundle | |||
| import android.view.View | |||
| import androidx.lifecycle.ViewModelProvider | |||
| import com.suliang.common.AppConfig | |||
| import com.suliang.common.base.fragment.BaseFragmentVM | |||
| import com.suliang.common.extension.click | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.databinding.FragmentCourseReviewBinding | |||
| /** | |||
| * 课程章节目录 | |||
| * @property param1 String? | |||
| * @property param2 String? | |||
| * 复习界面 | |||
| * 加载此界面,需要传参 从备忘本进入的主页加载此界面,还是从学习中心进入的主页加载此界面,其显示将不同 | |||
| */ | |||
| class CourseReviewFragment : BaseFragmentVM<FragmentCourseReviewBinding, CourseMainFragmentViewModel>() { | |||
| companion object { | |||
| /** | |||
| * 新建实例 | |||
| * @param pageSource Int 页面来源,默认为0,从学习中心加载, 1为从备忘本加载 | |||
| * @return CourseReviewFragment | |||
| */ | |||
| @JvmStatic | |||
| fun newInstanceFromLearningCenter() : CourseReviewFragment { | |||
| return CourseReviewFragment() | |||
| } | |||
| /** | |||
| * 新建实例 | |||
| * @param pageSource Int 页面来源,默认为0,从学习中心加载, 1为从备忘本加载 | |||
| * @return CourseReviewFragment | |||
| */ | |||
| @JvmStatic | |||
| fun newInstance() = CourseReviewFragment() | |||
| fun newInstanceFromMemo() : CourseReviewFragment { | |||
| val args = Bundle() | |||
| args.putInt(AppConfig.INTENT_1, 1) | |||
| val fragment = CourseReviewFragment() | |||
| fragment.arguments = args | |||
| return fragment | |||
| } | |||
| } | |||
| override fun initFragment() { | |||
| //获取页面来源 0 : 学习中心进入的加载复习界面 其他为备忘本进入加载的复习数据界面 | |||
| val pageSource : Int = arguments?.getInt(AppConfig.INTENT_1) ?: 0 | |||
| when (vm.reviewDataList.size) { | |||
| 0 -> if (pageSource != 0) { | |||
| //备忘本无复习数据加载 | |||
| initMemoData() | |||
| binding.tvStartReview.visibility = View.GONE | |||
| binding.tvSeeMemo.visibility = View.VISIBLE | |||
| } | |||
| else -> { //有复习数据加载 | |||
| initReviewData() | |||
| when (pageSource) { | |||
| 0 -> { | |||
| binding.tvSeeMemo.visibility = View.GONE | |||
| binding.tvStartReview.visibility = View.VISIBLE | |||
| } | |||
| else -> { | |||
| binding.tvStartReview.visibility = View.VISIBLE | |||
| binding.tvSeeMemo.visibility = View.VISIBLE | |||
| } | |||
| } | |||
| } | |||
| } | |||
| //开始复习 | |||
| binding.tvStartReview.click { | |||
| showToast("开始复习") | |||
| if (vm.isAssignmentReviewData){ //已经赋值,直接赋值跳转 | |||
| }else{ //没有赋值,需要进行 | |||
| } | |||
| } | |||
| //查看备忘本 | |||
| binding.tvSeeMemo.click { | |||
| showToast("查看备忘本") | |||
| } | |||
| } | |||
| private fun initReviewData() { | |||
| //根据课程类型进行显示 | |||
| binding.tvTips.text = when(vm.course.courseType){ | |||
| binding.tvTips.text = when (vm.course.courseType) { | |||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN -> "你将进行 [认读] 课程智能复习" | |||
| AppConstants.COURSE_TYPE_ENGLISH_VOICE -> "你将进行 [辨音] 课程智能复习" | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL -> "你将进行 [拼写] 课程智能复习" | |||
| else -> "你将进行课程智能复习" | |||
| } | |||
| binding.tvReviewCount.text = String.format("本次智能复习数%d",vm.course.courseReviewNumber) | |||
| //开始测试事件 | |||
| binding.tvStartReview.click { | |||
| showToast("开始复习") | |||
| binding.tvReviewCount.text = String.format("本次智能复习数%d", vm.reviewDataList.size) | |||
| } | |||
| private fun initMemoData() { | |||
| binding.tvTips.text = when (vm.course.courseType) { | |||
| AppConstants.COURSE_TYPE_ENGLISH_DISCERN -> "[认读] 课程加入备忘本数据共123条" | |||
| AppConstants.COURSE_TYPE_ENGLISH_VOICE -> "[辨音] 课程加入备忘本数据共123条" | |||
| AppConstants.COURSE_TYPE_ENGLISH_SPELL -> "[拼写] 课程加入备忘本数据共123条" | |||
| else -> "课程加入备忘本数据共123条" | |||
| } | |||
| binding.tvReviewCount.visibility = View.GONE | |||
| } | |||
| override fun loadData() { | |||
| } | |||
| override fun initViewModel(): CourseMainFragmentViewModel { | |||
| override fun initViewModel() : CourseMainFragmentViewModel { | |||
| return ViewModelProvider(requireParentFragment())[CourseMainFragmentViewModel::class.java] | |||
| } | |||
| } | |||
| @@ -1,71 +1,51 @@ | |||
| package com.xkl.cdl.module.m_memo | |||
| import android.os.Bundle | |||
| import androidx.fragment.app.Fragment | |||
| import android.view.LayoutInflater | |||
| import android.view.View | |||
| import android.view.ViewGroup | |||
| import androidx.lifecycle.ViewModelProvider | |||
| import androidx.recyclerview.widget.StaggeredGridLayoutManager | |||
| import com.suliang.common.base.fragment.BaseFragmentVM | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.databinding.FragmentMemoBinding | |||
| import com.xkl.cdl.databinding.FragmentMyBinding | |||
| import com.xkl.cdl.module.main.MainActivityViewModel | |||
| // TODO: Rename parameter arguments, choose names that match | |||
| // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER | |||
| private const val ARG_PARAM1 = "param1" | |||
| private const val ARG_PARAM2 = "param2" | |||
| /** | |||
| * A simple [Fragment] subclass. | |||
| * Use the [MemoFragment.newInstance] factory method to | |||
| * create an instance of this fragment. | |||
| */ | |||
| class MemoFragment : BaseFragmentVM<FragmentMemoBinding, MainActivityViewModel>(){ | |||
| // TODO: Rename and change types of parameters | |||
| private var param1: String? = null | |||
| private var param2: String? = null | |||
| override fun onCreate(savedInstanceState: Bundle?) { | |||
| super.onCreate(savedInstanceState) | |||
| arguments?.let { | |||
| param1 = it.getString(ARG_PARAM1) | |||
| param2 = it.getString(ARG_PARAM2) | |||
| } | |||
| } | |||
| override fun onCreateView( | |||
| inflater: LayoutInflater, container: ViewGroup?, | |||
| savedInstanceState: Bundle? | |||
| ): View? { | |||
| // Inflate the layout for this fragment | |||
| return inflater.inflate(R.layout.fragment_memo, container, false) | |||
| } | |||
| * author suliang | |||
| * create 2022/5/30 16:08 | |||
| * Describe: 备忘本 | |||
| */ | |||
| class MemoFragment : BaseFragmentVM<FragmentMemoBinding, MemoFragmentViewModel>(){ | |||
| companion object { | |||
| /** | |||
| * Use this factory method to create a new instance of | |||
| * this fragment using the provided parameters. | |||
| * | |||
| * @param param1 Parameter 1. | |||
| * @param param2 Parameter 2. | |||
| * @return A new instance of fragment MemoFragment. | |||
| */ | |||
| // TODO: Rename and change types and number of parameters | |||
| @JvmStatic | |||
| fun newInstance() = MemoFragment() | |||
| } | |||
| override fun initViewModel(): MemoFragmentViewModel { | |||
| return ViewModelProvider(this)[MemoFragmentViewModel::class.java].apply { | |||
| mainActivityViewModel = ViewModelProvider(requireActivity())[MainActivityViewModel::class.java] | |||
| } | |||
| } | |||
| override fun initFragment() { | |||
| binding.rv.run { | |||
| layoutManager = StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL) | |||
| adapter = vm.adapter | |||
| } | |||
| } | |||
| override fun loadData() { | |||
| /** 搜索内容变化监听 */ | |||
| vm.etSearchLiveData.observe(this){ | |||
| } | |||
| } | |||
| override fun initViewModel(): MainActivityViewModel { | |||
| return ViewModelProvider(requireActivity())[MainActivityViewModel::class.java] | |||
| override fun onResume() { | |||
| super.onResume() | |||
| //查询数据 | |||
| vm.queryData() | |||
| } | |||
| } | |||
| @@ -0,0 +1,56 @@ | |||
| package com.xkl.cdl.module.m_memo | |||
| import androidx.lifecycle.MutableLiveData | |||
| import appApi.AppApi | |||
| import com.googlecode.protobuf.format.JsonFormat | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| import com.suliang.common.extension.diskIo2DiskIo | |||
| import com.suliang.common.util.LogUtil | |||
| import com.xkl.cdl.adapter.AdapterCoursePackWithMemo | |||
| import com.xkl.cdl.data.bean.MemoCoursePack | |||
| import com.xkl.cdl.module.XKLApplication | |||
| import com.xkl.cdl.module.main.MainActivity | |||
| import com.xkl.cdl.module.main.MainActivityViewModel | |||
| import io.reactivex.rxjava3.core.Observable | |||
| /** | |||
| * author suliang | |||
| * create 2022/5/30 16:47 | |||
| * Describe: 备忘本的VM 持有activity的viewmodel | |||
| */ | |||
| class MemoFragmentViewModel : BaseViewModel() { | |||
| lateinit var mainActivityViewModel : MainActivityViewModel | |||
| /* 课程包的备忘本数据 */ | |||
| val memoCoursePackList = mutableListOf<MemoCoursePack>() | |||
| val etSearchLiveData = MutableLiveData<String>() | |||
| val adapter = AdapterCoursePackWithMemo(this).apply { | |||
| needShowEmptyView = true | |||
| } | |||
| /** | |||
| * 查询课程的错误数据结果 | |||
| */ | |||
| fun queryData() { | |||
| Observable.create<ByteArray> { | |||
| //参数: 科目 课程包id 课程id category 1 获取正确的,2获取错误的,3正确错误都获取 | |||
| val wordList = XKLApplication.mobileCache.getWordList(0, 0, 0, 2) | |||
| it.onNext(wordList) | |||
| it.onComplete() | |||
| }.compose(diskIo2DiskIo()).subscribe({ | |||
| AppApi.GetWordListResponse.parseFrom(it).wrongList?.forEach { wrong -> | |||
| } | |||
| //赋值集合 | |||
| //列表刷新 | |||
| }, { | |||
| it.printStackTrace() | |||
| }) | |||
| } | |||
| } | |||
| @@ -36,8 +36,6 @@ class MainActivity : BaseActivityVM<ActivityMainBinding, MainActivityViewModel>( | |||
| override fun initActivity(savedInstanceState: Bundle?) { | |||
| //加载Fragment并显示 | |||
| loadFragment(R.id.layout_container, -1, mMemo, mStatistics, mLearnCenter, mServiceCenter, mMy) | |||
| //点击事件 | |||
| binding.layoutNavContainer.setOnCheckedChangeListener { group, checkedId -> | |||
| when (checkedId) { | |||
| @@ -51,10 +49,9 @@ class MainActivity : BaseActivityVM<ActivityMainBinding, MainActivityViewModel>( | |||
| } | |||
| override fun loadData() { | |||
| //加载初始化数据 | |||
| vm.loadInit().observe(this){ | |||
| binding.layoutNavContainer.check(R.id.nav_learnCenter) | |||
| } | |||
| //加载Fragment并显示 | |||
| loadFragment(R.id.layout_container, 2, mMemo, mStatistics, mLearnCenter, mServiceCenter, mMy) | |||
| binding.layoutNavContainer.check(R.id.nav_learnCenter) | |||
| } | |||
| } | |||
| @@ -1,14 +1,7 @@ | |||
| package com.xkl.cdl.module.main | |||
| import androidx.fragment.app.Fragment | |||
| import androidx.lifecycle.MutableLiveData | |||
| import androidx.lifecycle.viewModelScope | |||
| import com.suliang.common.base.viewmodel.BaseViewModel | |||
| import com.suliang.common.util.LogUtil | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.module.m_center_learn.LearnCenterFragment | |||
| import kotlinx.coroutines.delay | |||
| import kotlinx.coroutines.launch | |||
| /** | |||
| * author suliang | |||
| @@ -17,14 +10,12 @@ import kotlinx.coroutines.launch | |||
| * 进行用户信息的判断,加载用户课程 | |||
| */ | |||
| class MainActivityViewModel: BaseViewModel() { | |||
| /** 进入程序加载完毕监听 */ | |||
| val mainInitLiveData = MutableLiveData<Boolean>() | |||
| /*** | |||
| * 进行初始化加载 | |||
| */ | |||
| fun loadInit(): MutableLiveData<Boolean> { | |||
| // fun loadInit(): MutableLiveData<Boolean> { | |||
| //1、查询用户的课程数据 | |||
| //设置进入课程管理类中 | |||
| // viewModelScope.launch { | |||
| @@ -35,8 +26,8 @@ class MainActivityViewModel: BaseViewModel() { | |||
| // showHideLoading(false) | |||
| // //通知Activity 加载完成 | |||
| // } | |||
| return MutableLiveData<Boolean>().apply { postValue(true) } | |||
| } | |||
| // return MutableLiveData<Boolean>().apply { postValue(true) } | |||
| // } | |||
| //2、计算用户的复习数据 | |||
| //3、统计数据 | |||
| @@ -2,57 +2,55 @@ package com.xkl.cdl.module.splash | |||
| import android.annotation.SuppressLint | |||
| import android.os.Bundle | |||
| import appApi.AppApi | |||
| import com.googlecode.protobuf.format.JsonFormat | |||
| import com.suliang.common.base.activity.BaseActivity | |||
| import com.suliang.common.util.LogUtil | |||
| import com.suliang.common.util.thread.AppExecutors | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.manager.CourseManager | |||
| import com.xkl.cdl.data.manager.db.DbCoursePackManager | |||
| import com.xkl.cdl.databinding.ActivitySplashBinding | |||
| import com.xkl.cdl.module.XKLApplication | |||
| import com.xkl.cdl.module.main.MainActivity | |||
| import io.reactivex.rxjava3.core.Observable | |||
| import java.util.* | |||
| import java.util.concurrent.TimeUnit | |||
| @SuppressLint("CustomSplashScreen") | |||
| class SplashActivity : BaseActivity<ActivitySplashBinding>(){ | |||
| override fun onCreate(savedInstanceState: Bundle?) { | |||
| class SplashActivity : BaseActivity<ActivitySplashBinding>() { | |||
| override fun onCreate(savedInstanceState : Bundle?) { | |||
| if (!isTaskRoot) { | |||
| finish() | |||
| return | |||
| } | |||
| super.onCreate(savedInstanceState) | |||
| } | |||
| override fun initActivity(savedInstanceState: Bundle?) { | |||
| override fun initActivity(savedInstanceState : Bundle?) { | |||
| } | |||
| override fun loadData() { | |||
| // try { | |||
| // val file: String = File(FileUtil.getSaveDirFile("db"), "mydb").absolutePath | |||
| // val mobile = Mobile_cache.new_(file) | |||
| // mobile.delete() | |||
| // println(" -------------> " + mobile.get("abc")) | |||
| // | |||
| // } catch (e: Exception) { | |||
| // e.printStackTrace() | |||
| // } | |||
| // learnDialog = LearnDialog().apply { | |||
| // show(supportFragmentManager,javaClass.name) | |||
| // } | |||
| // LogUtil.e("Dialog -- > ${learnDialog.hashCode()}") | |||
| // SpUtils.instance.encode("my","abcdefgxxxxx") | |||
| // SpUtils.instance.remove("my") | |||
| // println("---------------" + SpUtils.instance.decode("my",String::class.java)) | |||
| // | |||
| // if (true) return | |||
| showHideLoading(true) | |||
| AppExecutors.io.execute { | |||
| AppExecutors.diskIO.execute { | |||
| //读取课程数据 | |||
| // TODO: 2022/5/16 读取课程的sort信息进行保存,针对作文课程,同时需要设置其课程包的进度 | |||
| //用户的sort信息 | |||
| XKLApplication.mobileCache.courseSorted(AppConstants.SUBJECT_ENGLISH.toLong())?.let { value -> | |||
| val parseFrom = AppApi.CourseSortedResponse.parseFrom(value).toBuilder() | |||
| CourseManager.mSortInfoList.put(AppConstants.SUBJECT_ENGLISH, parseFrom.listBuilderList) | |||
| } | |||
| XKLApplication.mobileCache.courseSorted(AppConstants.SUBJECT_CHINESE.toLong())?.let { value -> | |||
| val parseFrom = AppApi.CourseSortedResponse.parseFrom(value).toBuilder() | |||
| CourseManager.mSortInfoList.put(AppConstants.SUBJECT_CHINESE, parseFrom.listBuilderList) | |||
| } | |||
| // TODO: 2022/3/22 读取当前app绑定的课程数据, | |||
| DbCoursePackManager().queryBindingCoursePack("262,261,264,136,547,615,516,411") | |||
| //复制课程的数据库到对应位置 | |||
| @@ -67,22 +65,4 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>(){ | |||
| }, 1, TimeUnit.SECONDS) | |||
| } | |||
| } | |||
| // 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) | |||
| // } | |||
| // | |||
| // override fun leftClick() { | |||
| // learnDialog1.dismiss() | |||
| // } | |||
| } | |||
| @@ -44,22 +44,43 @@ | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="4dp" | |||
| android:textColor="@color/main_text_color" | |||
| android:textColor="@color/theme_color" | |||
| android:textSize="@dimen/smallSize" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/tv_tips" | |||
| tools:text="本次智能复习数%d" /> | |||
| <Button | |||
| <com.google.android.material.button.MaterialButton | |||
| android:id="@+id/tv_start_review" | |||
| style="@style/common_button_style" | |||
| android:text="@string/start_review" | |||
| app:cornerRadius="8dp" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toEndOf="@+id/tv_see_memo" | |||
| app:layout_constraintTop_toBottomOf="@+id/tv_tips" | |||
| android:layout_marginStart="12dp" | |||
| app:layout_goneMarginStart="38dp" | |||
| /> | |||
| <com.google.android.material.button.MaterialButton | |||
| android:id="@+id/tv_see_memo" | |||
| style="@style/common_button_style" | |||
| android:text="@string/start_see_memo" | |||
| app:cornerRadius="8dp" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/tv_tips" /> | |||
| app:layout_constraintEnd_toStartOf="@+id/tv_start_review" | |||
| app:layout_constraintTop_toBottomOf="@+id/tv_tips" | |||
| android:layout_marginEnd="0dp" | |||
| app:layout_goneMarginEnd="38dp" | |||
| android:backgroundTint="@color/translation" | |||
| app:strokeColor="@color/theme_color" | |||
| app:strokeWidth="@dimen/line_height" | |||
| android:textColor="@color/theme_color" | |||
| android:visibility="gone" | |||
| tools:visibility="visible" | |||
| /> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -1,14 +1,72 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| <layout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| tools:context=".module.m_memo.MemoFragment"> | |||
| xmlns:app="http://schemas.android.com/apk/res-auto"> | |||
| <!-- TODO: Update blank fragment layout --> | |||
| <TextView | |||
| <data> | |||
| <variable | |||
| name="vm" | |||
| type="com.xkl.cdl.module.m_memo.MemoFragmentViewModel" /> | |||
| </data> | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:text="@string/hello_blank_fragment" /> | |||
| tools:context=".module.m_memo.MemoFragment"> | |||
| <View | |||
| android:id="@+id/v_top" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="@dimen/title_bar_height" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| android:background="@color/white" /> | |||
| <TextView | |||
| android:id="@+id/tv_memo" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintTop_toTopOf="@+id/v_top" | |||
| app:layout_constraintBottom_toBottomOf="@+id/v_top" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| android:layout_marginStart="@dimen/global_spacing" | |||
| android:textColor="@color/main_text_color" | |||
| android:textStyle="bold" | |||
| android:text="@string/memo" /> | |||
| <com.suliang.common.widget.InputSearchEditText | |||
| android:id="@+id/edit_text_search" | |||
| android:layout_width="0dp" | |||
| android:layout_height="36dp" | |||
| app:layout_constraintStart_toEndOf="@+id/tv_memo" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintTop_toTopOf="@+id/v_top" | |||
| app:layout_constraintBottom_toBottomOf="@+id/v_top" | |||
| android:layout_marginStart="@dimen/global_spacing" | |||
| android:layout_marginEnd="@dimen/global_spacing" | |||
| android:background="@drawable/et_search_bg" | |||
| android:paddingStart="@dimen/global_spacing" | |||
| android:paddingEnd="@dimen/global_spacing" | |||
| android:gravity="center_vertical" | |||
| android:maxLines="1" | |||
| android:singleLine="true" | |||
| android:drawablePadding="4dp" | |||
| android:textColor="@color/main_text_color" | |||
| android:textSize="@dimen/smallSize" | |||
| android:hint="请输入课程名称..." | |||
| android:text="@={vm.etSearchLiveData}" | |||
| /> | |||
| <androidx.recyclerview.widget.RecyclerView | |||
| android:id="@+id/rv" | |||
| android:layout_width="0dp" | |||
| android:layout_height="0dp" | |||
| app:layout_constraintTop_toBottomOf="@+id/v_top" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| android:background="@color/white_1" | |||
| android:overScrollMode="never" | |||
| android:scrollbars="none" /> | |||
| </FrameLayout> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| </layout> | |||
| @@ -18,7 +18,7 @@ | |||
| android:id="@+id/tab_discern" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="match_parent" | |||
| android:drawableStart="@drawable/ic_discern" | |||
| android:drawableStart="@drawable/ic_course_discern" | |||
| android:text="认读" | |||
| bind:svgColor="@{@color/theme_color}" | |||
| android:textColor="@color/theme_color" | |||
| @@ -48,7 +48,7 @@ | |||
| android:id="@+id/tab_spell" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="match_parent" | |||
| android:drawableStart="@drawable/ic_spell" | |||
| android:drawableStart="@drawable/ic_course_spell" | |||
| bind:svgColor="@{@color/theme_color}" | |||
| android:textColor="@color/theme_color" | |||
| android:text="拼写" | |||
| @@ -77,7 +77,7 @@ | |||
| android:id="@+id/tab_voice" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="match_parent" | |||
| android:drawableStart="@drawable/ic_voice" | |||
| android:drawableStart="@drawable/ic_course_voice" | |||
| android:text="辨音" | |||
| bind:svgColor="@{@color/theme_color}" | |||
| android:textColor="@color/theme_color" | |||
| @@ -0,0 +1,74 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:layout_margin="5dp" | |||
| android:background="@drawable/shape_rounder_12_white" | |||
| android:padding="8dp" | |||
| android:paddingBottom="12dp"> | |||
| <com.google.android.material.imageview.ShapeableImageView | |||
| android:id="@+id/img_course_pack_cover" | |||
| style="@style/roundedCornerStyle" | |||
| android:layout_width="0dp" | |||
| android:layout_height="0dp" | |||
| app:layout_constraintHeight_min="128dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:scaleType="centerCrop" | |||
| app:layout_constraintDimensionRatio="h,102:136" | |||
| app:layout_constraintEnd_toStartOf="@+id/barrier" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| <androidx.constraintlayout.widget.Barrier | |||
| android:id="@+id/barrier" | |||
| android:layout_width="1dp" | |||
| android:layout_height="wrap_content" | |||
| app:barrierDirection="right" | |||
| app:constraint_referenced_ids="img_course_pack_cover" /> | |||
| <com.google.android.material.imageview.ShapeableImageView | |||
| android:id="@+id/img_course_icon_1" | |||
| android:layout_width="40dp" | |||
| android:layout_height="40dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toEndOf="@+id/barrier" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:layout_constraintBottom_toTopOf="@+id/img_course_icon_2" | |||
| app:strokeWidth="10dp" | |||
| style="@style/roundedCornerStyle"/> | |||
| <com.google.android.material.imageview.ShapeableImageView | |||
| android:id="@+id/img_course_icon_2" | |||
| android:layout_width="40dp" | |||
| android:layout_height="40dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toEndOf="@+id/barrier" | |||
| app:layout_constraintTop_toBottomOf="@+id/img_course_icon_1" | |||
| app:layout_constraintBottom_toTopOf="@+id/img_course_icon_3" | |||
| app:strokeWidth="10dp" | |||
| android:visibility="invisible" | |||
| style="@style/roundedCornerStyle"/> | |||
| <com.google.android.material.imageview.ShapeableImageView | |||
| android:id="@+id/img_course_icon_3" | |||
| android:layout_width="40dp" | |||
| android:layout_height="40dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toEndOf="@+id/barrier" | |||
| app:layout_constraintTop_toBottomOf="@+id/img_course_icon_2" | |||
| app:layout_constraintBottom_toBottomOf="@+id/img_course_pack_cover" | |||
| app:strokeWidth="10dp" | |||
| android:visibility="invisible" | |||
| style="@style/roundedCornerStyle" | |||
| /> | |||
| <TextView | |||
| android:id="@+id/tv_course_pack_name" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintTop_toBottomOf="@+id/img_course_pack_cover" /> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -62,7 +62,7 @@ | |||
| android:layout_marginTop="8dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:padding="3dp" | |||
| android:src="@drawable/ic_discern" | |||
| android:src="@drawable/ic_course_discern" | |||
| android:visibility="gone" | |||
| app:layout_constraintEnd_toStartOf="@+id/image_view_type_spell" | |||
| app:layout_constraintHorizontal_bias="0" | |||
| @@ -81,7 +81,7 @@ | |||
| android:layout_marginTop="8dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:padding="3dp" | |||
| android:src="@drawable/ic_spell" | |||
| android:src="@drawable/ic_course_spell" | |||
| android:visibility="gone" | |||
| app:layout_constraintEnd_toStartOf="@+id/image_view_type_voice" | |||
| app:layout_constraintStart_toEndOf="@+id/image_view_type_discern" | |||
| @@ -98,7 +98,7 @@ | |||
| android:layout_marginTop="8dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:padding="3dp" | |||
| android:src="@drawable/ic_voice" | |||
| android:src="@drawable/ic_course_voice" | |||
| android:visibility="gone" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toEndOf="@+id/image_view_type_spell" | |||
| @@ -85,5 +85,6 @@ | |||
| <string name="quit_auto_play_title">你确定要退出自动播放吗?</string> | |||
| <string name="quit_auto_play_title_over">本课程自动播放完毕</string> | |||
| <string name="course_introduction">课程简介</string> | |||
| <string name="start_see_memo">查看备忘本</string> | |||
| </resources> | |||
| @@ -0,0 +1,12 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="18dp" | |||
| android:height="18dp" | |||
| android:viewportWidth="28" | |||
| android:viewportHeight="28"> | |||
| <path | |||
| android:pathData="M24.1111,1.8667C25.228,1.8667 26.1333,2.772 26.1333,3.8889L26.1333,24.1111C26.1333,25.228 25.228,26.1333 24.1111,26.1333L3.8889,26.1333C2.772,26.1333 1.8667,25.228 1.8667,24.1111L1.8667,3.8889C1.8667,2.772 2.772,1.8667 3.8889,1.8667L24.1111,1.8667ZM10.0247,11.2095L5.9111,11.2095L5.9111,12.9379L8.279,12.9379L8.279,19.0737C8.279,19.4367 8.1235,19.7478 7.8123,20.007L8.5037,21.6317C9.8346,20.8194 11.0963,19.9033 12.2716,18.8663L11.7877,16.9823C11.1654,17.5527 10.5778,18.054 10.0247,18.5033L10.0247,11.2095ZM20.758,6.7675L12.2543,6.7675L12.2543,8.4959L19.0296,8.4959L19.0296,11.9527L12.963,11.9527L12.963,19.7996C12.963,20.9577 13.516,21.5453 14.6568,21.5453L19.1679,21.5453C20.2741,21.5453 20.9827,21.2688 21.3111,20.7502C21.6568,20.1972 21.916,19.0219 22.0889,17.2416L20.2914,16.6539L20.257,17.1922C20.1895,18.1274 20.0972,18.7799 19.9802,19.1601C19.842,19.6095 19.4617,19.8515 18.8741,19.8515L15.4519,19.8515C14.9679,19.8169 14.7259,19.5577 14.7259,19.0737L14.7259,13.6811L20.758,13.6811L20.758,6.7675ZM8.3136,6.2144L7.0346,7.4589C8.2617,8.3577 9.2123,9.2046 9.8691,9.9996L11.1136,8.7379C10.3358,7.9083 9.4025,7.0614 8.3136,6.2144Z" | |||
| android:strokeWidth="1" | |||
| android:fillColor="#8757E6" | |||
| android:fillType="evenOdd" | |||
| android:strokeColor="#00000000"/> | |||
| </vector> | |||
| @@ -0,0 +1,12 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="18dp" | |||
| android:height="18dp" | |||
| android:viewportWidth="22" | |||
| android:viewportHeight="22"> | |||
| <path | |||
| android:fillColor="#F7874F" | |||
| android:fillType="evenOdd" | |||
| android:pathData="M18.9444,1.4667C19.822,1.4667 20.5333,2.178 20.5333,3.0556L20.5333,18.9444C20.5333,19.822 19.822,20.5333 18.9444,20.5333L3.0556,20.5333C2.178,20.5333 1.4667,19.822 1.4667,18.9444L1.4667,3.0556C1.4667,2.178 2.178,1.4667 3.0556,1.4667L18.9444,1.4667ZM11.2645,12.5261C10.6269,13.9912 9.8537,15.2529 8.9584,16.2974L10.2064,17.2063C11.1017,16.0668 11.8614,14.6967 12.5126,13.123L11.2645,12.5261ZM14.968,12.4583L13.8149,13.1909C14.7916,14.6288 15.5649,15.9447 16.1346,17.1385L17.3556,16.2839C16.7994,15.1986 15.999,13.9234 14.968,12.4583ZM7.846,8.5649L4.6444,8.5649L4.6444,9.9215L6.4758,9.9215L6.4758,14.7374C6.4758,15.0222 6.3537,15.2664 6.1231,15.4699L6.6657,16.7451C7.6018,16.1075 8.4836,15.3885 9.3246,14.5746L8.9312,13.0959C8.5514,13.4757 8.1851,13.8285 7.846,14.1405L7.846,8.5649ZM16.4873,5.2142L9.8537,5.2142L9.8537,11.97L16.4873,11.97L16.4873,5.2142ZM15.0629,6.5843L15.0629,10.6134L11.2645,10.6134L11.2645,6.5843L15.0629,6.5843ZM6.5437,4.6444L5.5533,5.6212C6.503,6.3266 7.2491,6.9778 7.7646,7.6018L8.7277,6.625C8.1308,5.9739 7.3983,5.3092 6.5437,4.6444Z" | |||
| android:strokeWidth="1" | |||
| android:strokeColor="#00000000" /> | |||
| </vector> | |||
| @@ -0,0 +1,14 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="18dp" | |||
| android:height="18dp" | |||
| android:viewportWidth="112" | |||
| android:viewportHeight="112"> | |||
| <path | |||
| android:fillColor="#EB54D8" | |||
| android:pathData="M10.5,10.5l-2.5,2.4 0,43.1 0,43.1 2.5,2.4 2.4,2.5 43.1,-0 43.1,-0 2.4,-2.5 2.5,-2.4 0,-43.1 0,-43.1 -2.5,-2.4 -2.4,-2.5 -43.1,-0 -43.1,-0 -2.4,2.5zM64,28.5c-5.3,5.4 -7,6.5 -9.7,6.5 -1.8,-0 -3.3,-0.2 -3.3,-0.5 0,-0.3 1.7,-3.2 3.7,-6.5l3.8,-6 6,-0 5.9,-0 -6.4,6.5zM68.7,40c7.5,3.4 8.3,5.8 8.3,25.7l0,17.3 -4.5,-0c-3.8,-0 -4.5,-0.3 -4.5,-1.9 0,-1.8 -0.3,-1.8 -4.6,0.6 -5.6,3.1 -15.1,3.6 -20.1,1 -6.3,-3.3 -8.3,-12.5 -4,-18.7 3.5,-5.2 8.7,-7.3 18.9,-7.8 9.8,-0.5 10.8,-1.4 6.7,-6.6 -1.7,-2.2 -2.8,-2.6 -7,-2.6 -4.9,-0 -8.9,2.3 -8.9,5.1 0,1.1 -9.4,0.8 -10.6,-0.3 -0.8,-0.9 2.1,-7.1 4.4,-9.1 5.3,-4.8 18.4,-6.2 25.9,-2.7z" | |||
| android:strokeColor="#00000000" /> | |||
| <path | |||
| android:fillColor="#EB54D8" | |||
| android:pathData="M53,64.7c-3,1.1 -5,3.7 -5,6.4 0,7.2 13.7,6.3 17.5,-1.1 2.7,-5.3 2.2,-6 -4.7,-5.9 -3.5,-0 -7,0.3 -7.8,0.6z" | |||
| android:strokeColor="#00000000" /> | |||
| </vector> | |||
| @@ -0,0 +1,18 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="18dp" | |||
| android:height="18dp" | |||
| android:viewportWidth="112" | |||
| android:viewportHeight="112"> | |||
| <path | |||
| android:fillColor="#8757E6" | |||
| android:pathData="M10,10c-1.9,1.9 -2,3.3 -2,46 0,42.7 0.1,44.1 2,46 1.9,1.9 3.3,2 46,2 42.7,-0 44.1,-0.1 46,-2 1.9,-1.9 2,-3.3 2,-46 0,-42.7 -0.1,-44.1 -2,-46 -1.9,-1.9 -3.3,-2 -46,-2 -42.7,-0 -44.1,0.1 -46,2zM54.3,34.8l3.5,2 4.9,-2.5c4,-2.1 5.8,-2.4 11,-2 7.7,0.6 12.9,3.2 17,8.6 5.4,7 5.6,15.1 0.3,17.8 -1.6,0.8 -7,1.3 -15.2,1.3 -12.2,-0 -12.8,0.1 -12.8,2 0,9.8 12.9,13.3 20.9,5.7 4.3,-4.1 6.4,-5 8.4,-3.4 2.5,2.1 2.6,4.5 0.4,7.9 -6.3,9.3 -23.4,11.6 -33.9,4.4l-3.6,-2.4 -6.1,3.1c-8.2,4 -15.5,4.7 -21.3,1.8 -5.4,-2.6 -7.8,-6.5 -7.8,-12.6 0,-4.1 0.5,-5.2 3.5,-8 3.4,-3.3 5.1,-3.9 22.5,-7.6 5.1,-1.1 5.1,-1.1 4.5,-4.2 -0.9,-4.1 -3,-5.7 -7.6,-5.7 -5.2,-0 -7.9,1.5 -11.1,6.1 -3.3,4.7 -6.9,5.2 -9.4,1.4 -1.5,-2.2 -1.5,-3 -0.3,-6.2 2.7,-6.9 10.5,-10.5 21.6,-10 4.9,0.3 8.2,1 10.6,2.5z" | |||
| android:strokeColor="#00000000" /> | |||
| <path | |||
| android:fillColor="#8757E6" | |||
| android:pathData="M68.5,42.2c-3,1.6 -5.5,5 -5.5,7.5 0,2.2 0.3,2.3 10,2.3 6.6,-0 10,-0.4 10,-1.1 0,-0.6 -0.7,-2.6 -1.5,-4.4 -2.4,-4.9 -8.3,-6.9 -13,-4.3z" | |||
| android:strokeColor="#00000000" /> | |||
| <path | |||
| android:fillColor="#8757E6" | |||
| android:pathData="M43,59.2c-10.6,2.7 -13.3,5.8 -9.2,10.6 2.9,3.4 8.8,3.3 13.1,-0.3 2.2,-1.9 3.2,-3.8 3.7,-7.1 0.3,-2.5 0.5,-4.6 0.3,-4.8 -0.2,-0.2 -3.8,0.6 -7.9,1.6z" | |||
| android:strokeColor="#00000000" /> | |||
| </vector> | |||
| @@ -1,10 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="18dp" | |||
| android:height="18dp" | |||
| android:tint="#40A540" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="@android:color/white" | |||
| android:fillColor="#40A540" | |||
| android:pathData="M12,15c1.66,0 2.99,-1.34 2.99,-3L15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6v6c0,1.66 1.34,3 3,3zM17.3,12c0,3 -2.54,5.1 -5.3,5.1S6.7,15 6.7,12L5,12c0,3.42 2.72,6.23 6,6.72L11,22h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z" /> | |||
| </vector> | |||
| @@ -10,6 +10,11 @@ import io.reactivex.rxjava3.schedulers.Schedulers | |||
| * create 2022/4/1 18:02 | |||
| * Describe: https://yuzhiqiang.blog.csdn.net/article/details/88233525 | |||
| */ | |||
| /** | |||
| * 单线程 : 只有diskIo单线程 | |||
| * @return ObservableTransformer<T, T> | |||
| */ | |||
| fun <T> diskIo2DiskIo(): ObservableTransformer<T, T> { | |||
| return ObservableTransformer { upstream -> | |||
| upstream!!.subscribeOn(Schedulers.from(AppExecutors.diskIO)) | |||
| @@ -17,6 +22,27 @@ fun <T> diskIo2DiskIo(): ObservableTransformer<T, T> { | |||
| } | |||
| } | |||
| /** | |||
| * 多线程 | |||
| * @return ObservableTransformer<T, T> | |||
| */ | |||
| fun <T> io2Io(): ObservableTransformer<T, T> { | |||
| return ObservableTransformer { upstream -> | |||
| upstream!!.subscribeOn(Schedulers.from(AppExecutors.io)) | |||
| .observeOn(Schedulers.from(AppExecutors.io)) | |||
| } | |||
| } | |||
| /** | |||
| * 多线程 | |||
| * @return ObservableTransformer<T, T> | |||
| */ | |||
| fun <T> io2Main(): ObservableTransformer<T, T> { | |||
| return ObservableTransformer { upstream -> | |||
| upstream!!.subscribeOn(Schedulers.from(AppExecutors.io)) | |||
| .observeOn(Schedulers.from(AppExecutors.mainThread)) | |||
| } | |||
| } | |||
| fun <T> diskIo2Main(): ObservableTransformer<T, T> { | |||
| return ObservableTransformer { upstream -> | |||
| upstream!!.subscribeOn(Schedulers.from(AppExecutors.diskIO)) | |||
| @@ -43,6 +43,19 @@ class DateUtil { | |||
| fun format(formatValue : Long, format : String) : String { | |||
| return SimpleDateFormat(format).format(formatValue) | |||
| } | |||
| /** | |||
| * 格式化字符串格式时间为日期格式 | |||
| * @param formatValue String 字符串格式的日期 | |||
| * @param format String 字符串格式样式,如 "yyyy-MM-dd HH:mm:ss" | |||
| * @return Date 日期格式 | |||
| */ | |||
| @SuppressLint("SimpleDateFormat") | |||
| @Throws(Exception::class) | |||
| @JvmStatic | |||
| fun format(formatValue:String, format : String):Date{ | |||
| return SimpleDateFormat(format).parse(formatValue) | |||
| } | |||
| /** | |||
| * 获取时间段 | |||