| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_memo_list_detail.xml" value="0.3621621621621622" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_memo_list_detail.xml" value="0.3621621621621622" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_memo_test.xml" value="0.25" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_memo_test.xml" value="0.25" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_setting.xml" value="0.22847011144883486" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_setting.xml" value="0.22847011144883486" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" /> | |||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4857142857142857" /> | |||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_subject_statistics.xml" value="0.23454913880445796" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_subject_statistics.xml" value="0.23454913880445796" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_test_detail.xml" value="0.3348694316436252" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_test_detail.xml" value="0.3348694316436252" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_test_score.xml" value="0.31521739130434784" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_test_score.xml" value="0.31521739130434784" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/statedrawable/drawable/ic_nav_learn_center.xml" value="0.44166666666666665" /> | <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_arrow_right.xml" value="0.29814814814814816" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_checked_1.xml" value="0.49538461538461537" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_checked_1.xml" value="0.49538461538461537" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_clock.xml" value="0.2635" /> | |||||
| <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_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_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_literacy.xml" value="0.2972222222222222" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_pause.xml" value="0.503125" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_pause.xml" value="0.503125" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_stop.xml" value="0.4973958333333333" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_stop.xml" value="0.4973958333333333" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_right.xml" value="0.4425925925925926" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_right.xml" value="0.4425925925925926" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_rubbish.xml" value="0.2635" /> | |||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_setting.xml" value="0.2635" /> | |||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_spell.xml" value="0.5061538461538462" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_spell.xml" value="0.5061538461538462" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_spoken.xml" value="0.5061538461538462" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_spoken.xml" value="0.5061538461538462" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_star.xml" value="0.49538461538461537" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_star.xml" value="0.49538461538461537" /> |
| public native void gameFinished(long var1, long var3, long var5, long var7, long var9) 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 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 delLearnedCourseID(long var1, long var3, long var5) throws Exception; | ||||
| public native void destroy(); | |||||
| public native void destroy(); | |||||
| 启动: | |||||
| - 检查uuid 生成uuid | |||||
| - 检查licence,没有licence 跳转进行设备账号激活 | |||||
| - 激活成功 拉取绑定的课程列表 | |||||
| - 拉取成功后保存本地,拉取失败,使用本地数据,本地没有数据,提示重新拉取,否则无法进入项目 | |||||
| - 下载课程、复制本地数据(词典、测词汇量) | |||||
| - 进入课程 |
| android:roundIcon="@mipmap/ic_launcher_round" | android:roundIcon="@mipmap/ic_launcher_round" | ||||
| android:supportsRtl="true" | android:supportsRtl="true" | ||||
| android:theme="@style/Theme.XklLocal" | android:theme="@style/Theme.XklLocal" | ||||
| tools:ignore="LockedOrientationActivity"> | |||||
| android:usesCleartextTraffic="true" | |||||
| tools:ignore="LockedOrientationActivity" | |||||
| android:manageSpaceActivity=".module.m_my.CacheClearActivity"> | |||||
| <activity | <activity | ||||
| android:name=".module.splash.ActivateActivity" | android:name=".module.splash.ActivateActivity" | ||||
| android:exported="true" /> | android:exported="true" /> |
| */ | */ | ||||
| object AppConstants { | object AppConstants { | ||||
| const val DEVICE_ID = "deviceId" | |||||
| const val LICENSE_ID = "licenceId" | |||||
| const val DEVICE_ID = "device_key" | |||||
| const val LICENSE_ID = "license_key" | |||||
| const val ACTIVE = "http://api.offline.xuekaole.com/active" | |||||
| const val BIND_COURSE = "http://api.offline.xuekaole.com/courses" | |||||
| /** 项目: 英语 */ | /** 项目: 英语 */ | ||||
| const val SUBJECT_ENGLISH = 3 | const val SUBJECT_ENGLISH = 3 | ||||
| const val DIALOG_AFTER_TOTAL_TEST_AGAIN = 7 | const val DIALOG_AFTER_TOTAL_TEST_AGAIN = 7 | ||||
| /**弹窗动作: 备忘本测试结束查看详情*/ | /**弹窗动作: 备忘本测试结束查看详情*/ | ||||
| const val DIALOG_MEMO_TEST_DETAIL = 8 | const val DIALOG_MEMO_TEST_DETAIL = 8 | ||||
| /** 每门课程每天复习的最大数 */ | |||||
| const val TODAY_MAX_REVIEW_COUNT = 200 | |||||
| } | } |
| var learnIsOver = false | var learnIsOver = false | ||||
| /**是否是最后一个课时 */ | /**是否是最后一个课时 */ | ||||
| var lastLesson : Boolean = false | var lastLesson : Boolean = false | ||||
| //是否跳过学前测试 | |||||
| var isSkipLessonBeforeTest = false | |||||
| //总数量 | //总数量 | ||||
| @get:Bindable | @get:Bindable |
| package com.xkl.cdl.data.manager | package com.xkl.cdl.data.manager | ||||
| import com.suliang.common.util.DateUtil | |||||
| import com.suliang.common.util.SpUtils | import com.suliang.common.util.SpUtils | ||||
| import com.suliang.common.util.file.FileUtil | import com.suliang.common.util.file.FileUtil | ||||
| import com.suliang.common.util.thread.AppExecutors | import com.suliang.common.util.thread.AppExecutors | ||||
| /** | /** | ||||
| * 检查绑定的课程信息 | * 检查绑定的课程信息 | ||||
| */ | */ | ||||
| fun getBindCourse() : List<CoursePack>? { | |||||
| return SpUtils.instance.decodeList("bindCourse") | |||||
| fun getBindCourse() : String { | |||||
| return SpUtils.instance.decode("bindCourse",String::class.java,"") | |||||
| } | } | ||||
| /** | /** | ||||
| * 存放绑定的课程 | * 存放绑定的课程 | ||||
| */ | */ | ||||
| fun putBindCourse(coursePackList : List<CoursePack>) { | |||||
| return SpUtils.instance.encodeList("bindCourse",coursePackList) | |||||
| fun putBindCourse(coursePackList : String) { | |||||
| return SpUtils.instance.encode("bindCourse",coursePackList) | |||||
| } | } | ||||
| /** 保存是否过期 */ | |||||
| fun putIsExpiration(isExpiration : Boolean) { | |||||
| SpUtils.instance.encode("isExpiration",isExpiration) | |||||
| } | |||||
| /** 获取是否过期 */ | |||||
| fun getIsExpiration() : Boolean { | |||||
| return SpUtils.instance.decode("isExpiration",Boolean::class.java,false) | |||||
| } | |||||
| /** 保存绑定到期时间 */ | |||||
| fun putExpirationTime(expirationTime : String) { | |||||
| SpUtils.instance.encode("expiration_time",expirationTime) | |||||
| } | |||||
| /** 获取绑定到期时间 */ | |||||
| fun getExpirationTime(): String{ | |||||
| return SpUtils.instance.decode("expiration_time",String::class.java,"") | |||||
| } | |||||
| /** 获取今天需要复习的数量 */ | |||||
| fun getTodayNeedReviewCount(projectId :Int, coursePackId:Long, courseId : Long): Int { | |||||
| val key = "${projectId}_${coursePackId}_${courseId}_reviewFlag" | |||||
| val time = DateUtil.format(System.currentTimeMillis(),DateUtil.FORMAT_4) //今天的复习的时间 | |||||
| val spReviewFlag = SpUtils.instance.decode(key,String::class.java,"") | |||||
| if (spReviewFlag.isNotEmpty()){ | |||||
| val s = spReviewFlag.split("_") | |||||
| if (s.size == 2 && s[0] == time){ | |||||
| return AppConstants.TODAY_MAX_REVIEW_COUNT - Integer.parseInt(s[1]) | |||||
| } | |||||
| } | |||||
| return AppConstants.TODAY_MAX_REVIEW_COUNT | |||||
| } | |||||
| /** 存放当前课程复习了的数量 */ | |||||
| fun putTodayReviewCount(projectId : Int,coursePackId: Long,courseId : Long,reviewCount:Int){ | |||||
| val key = "${projectId}_${coursePackId}_${courseId}_reviewFlag" | |||||
| val time = DateUtil.format(System.currentTimeMillis(),DateUtil.FORMAT_4) //今天的复习的时间 | |||||
| val todayNeedReviewCount = getTodayNeedReviewCount(projectId, coursePackId, courseId) | |||||
| //上次本地保存的数量 = 每天能复习的数量 - 今天需要复习的最大数量 | |||||
| //本次本地需要保存的数量 = 上次本地保存的数量 + 本次复习了的数量 | |||||
| val reviewedCount = AppConstants.TODAY_MAX_REVIEW_COUNT - todayNeedReviewCount + reviewCount | |||||
| SpUtils.instance.encode(key,"${time}_$reviewedCount") | |||||
| } | |||||
| } | } |
| */ | */ | ||||
| object DBCourseManager { | object DBCourseManager { | ||||
| private var currentBase = DbControlBase() | private var currentBase = DbControlBase() | ||||
| private const val NORMAL = "XKL_COURSE_DATA_KEY" | |||||
| private const val NORMAL = "XKL_LOCAL_COURSE_DATA_KEY" | |||||
| private const val SPOKEN = "XKL_SPOKEN_COURSE_DATA_KEY" | private const val SPOKEN = "XKL_SPOKEN_COURSE_DATA_KEY" | ||||
| private const val COMPOSITION = "XKL_LOCAL_COMPOSITION_DATA_KEY" | private const val COMPOSITION = "XKL_LOCAL_COMPOSITION_DATA_KEY" | ||||
| private const val LITERACY_PINYING = "XKL_LOCAL_CHINESE_COURSE_KEY" | private const val LITERACY_PINYING = "XKL_LOCAL_CHINESE_COURSE_KEY" |
| tvLessonName.text = learnDialogBean.chapter_lesson_name | tvLessonName.text = learnDialogBean.chapter_lesson_name | ||||
| tvCountTime.text = learnDialogBean.showTimeCount | tvCountTime.text = learnDialogBean.showTimeCount | ||||
| tvRight.text = "开始测试" | tvRight.text = "开始测试" | ||||
| tvLeft.text = "跳过,去学习" | |||||
| tvLeft.visibility = View.VISIBLE | |||||
| vSplit.visibility = View.VISIBLE | |||||
| imgIv.setImageResource(if (Random.nextBoolean()) R.mipmap.boy_1 else R.mipmap.girl_1) | imgIv.setImageResource(if (Random.nextBoolean()) R.mipmap.boy_1 else R.mipmap.girl_1) | ||||
| //开始测试,进入课时学前测试界面 | //开始测试,进入课时学前测试界面 | ||||
| binding.tvRight.click { | binding.tvRight.click { | ||||
| onDialogListener(AppConstants.DIALOG_START_TEST, this@LearnDialog) | onDialogListener(AppConstants.DIALOG_START_TEST, this@LearnDialog) | ||||
| } | } | ||||
| binding.tvLeft.click { | |||||
| onDialogListener(AppConstants.DIALOG_START_LEARN,this@LearnDialog) | |||||
| } | |||||
| binding.ivClose.click { dismissAllowingStateLoss() } | binding.ivClose.click { dismissAllowingStateLoss() } | ||||
| } | } | ||||
| } | } | ||||
| tvTitle.text = "恭喜你,完成了知识点测试!" | tvTitle.text = "恭喜你,完成了知识点测试!" | ||||
| tvLeft.visibility = View.VISIBLE | tvLeft.visibility = View.VISIBLE | ||||
| tvLeft.text = "再测一次" | tvLeft.text = "再测一次" | ||||
| vSplit.visibility = View.VISIBLE | |||||
| tvRight.text = "完成" | tvRight.text = "完成" | ||||
| } | } | ||||
| binding.tvLeft.click { onDialogListener(AppConstants.DIALOG_START_TEST,this) } | binding.tvLeft.click { onDialogListener(AppConstants.DIALOG_START_TEST,this) } | ||||
| tvRight.text = "完成" | tvRight.text = "完成" | ||||
| tvLeft.visibility = View.VISIBLE | tvLeft.visibility = View.VISIBLE | ||||
| tvLeft.text = "再测一次" | tvLeft.text = "再测一次" | ||||
| vSplit.visibility = View.VISIBLE | |||||
| } | } | ||||
| initScore() | initScore() | ||||
| initNumber() | initNumber() |
| import com.xkl.cdl.data.bean.intentdata.LearnData | import com.xkl.cdl.data.bean.intentdata.LearnData | ||||
| import com.xkl.cdl.data.event.LearnEventData | import com.xkl.cdl.data.event.LearnEventData | ||||
| import com.xkl.cdl.data.manager.CourseManager | import com.xkl.cdl.data.manager.CourseManager | ||||
| import com.xkl.cdl.data.manager.UserInfoManager | |||||
| import com.xkl.cdl.data.manager.db.DBCourseManager | import com.xkl.cdl.data.manager.db.DBCourseManager | ||||
| import com.xkl.cdl.data.manager.db.DbControlBase | import com.xkl.cdl.data.manager.db.DbControlBase | ||||
| import com.xkl.cdl.data.repository.DataRepository | import com.xkl.cdl.data.repository.DataRepository | ||||
| .post(LearnEventData(learnData.lesson.subjectId, learnData.lesson.courseId, AppConstants.DATA_REVIEW).apply { | .post(LearnEventData(learnData.lesson.subjectId, learnData.lesson.courseId, AppConstants.DATA_REVIEW).apply { | ||||
| this.leesonPositionIndex = currentLessonLearnedPosition | this.leesonPositionIndex = currentLessonLearnedPosition | ||||
| }) | }) | ||||
| //保存复习数据 | |||||
| UserInfoManager.instance.putTodayReviewCount(learnData.lesson.subjectId,learnData.lesson.coursePackId,learnData.lesson.courseId,currentLessonLearnedPosition+1) | |||||
| return | return | ||||
| } | } | ||||
| //学习发送结果 | //学习发送结果 |
| tabSpell.click { binding.viewPager2.currentItem = 1 } | tabSpell.click { binding.viewPager2.currentItem = 1 } | ||||
| tabVoice.click { binding.viewPager2.currentItem = 2 } | tabVoice.click { binding.viewPager2.currentItem = 2 } | ||||
| } | } | ||||
| } | } | ||||
| private val onLessonClick : (v : View, position : Int, lesson : Lesson) -> Unit = { view, position, entity -> | private val onLessonClick : (v : View, position : Int, lesson : Lesson) -> Unit = { view, position, entity -> | ||||
| when (entity.lessonType) { | when (entity.lessonType) { | ||||
| AppConstants.LESSON_TYPE_WORD -> { | AppConstants.LESSON_TYPE_WORD -> { | ||||
| if (entity.beforeTestScore == AppConstants.NOT_DOING) { //课时学前测试,没有做 | |||||
| if (entity.beforeTestScore == AppConstants.NOT_DOING && !entity.isSkipLessonBeforeTest) { //课时学前测试,没有做 | |||||
| //弹窗显示学前测试提示 | //弹窗显示学前测试提示 | ||||
| showLessonBeforeTestStartDialog(entity) | showLessonBeforeTestStartDialog(entity) | ||||
| } else if (!entity.learnIsOver) { //当前课时未学完,直接开始学习 | } else if (!entity.learnIsOver) { //当前课时未学完,直接开始学习 | ||||
| entity.isSkipLessonBeforeTest = false | |||||
| startLearn(entity) | startLearn(entity) | ||||
| } else if (entity.afterTestScore == AppConstants.NOT_DOING) { | } else if (entity.afterTestScore == AppConstants.NOT_DOING) { | ||||
| entity.isSkipLessonBeforeTest = false | |||||
| loadLessonAfterTest(entity,true) | loadLessonAfterTest(entity,true) | ||||
| } else { //当前课时学习完成的弹窗 | } else { //当前课时学习完成的弹窗 | ||||
| entity.isSkipLessonBeforeTest = false | |||||
| showLessonAllOverDialog(entity) | showLessonAllOverDialog(entity) | ||||
| } | } | ||||
| } | } | ||||
| dialog.dismissAllowingStateLoss() | dialog.dismissAllowingStateLoss() | ||||
| when (action) { | when (action) { | ||||
| AppConstants.DIALOG_START_LEARN -> { //跳过测试 继续学习 | AppConstants.DIALOG_START_LEARN -> { //跳过测试 继续学习 | ||||
| // 进入学习界面 ,不能跳过测试,必须学习 | |||||
| lesson.isSkipLessonBeforeTest = true | |||||
| //手动调用点击item点击 | |||||
| onLessonClick.invoke(View(context),lesson.lessonPositionInList,lesson) | |||||
| } | } | ||||
| // 开始测试 | // 开始测试 | ||||
| AppConstants.DIALOG_START_TEST -> startLessonTest(lesson, AppConstants.TEST_TYPE_BEFORE, it) | AppConstants.DIALOG_START_TEST -> startLessonTest(lesson, AppConstants.TEST_TYPE_BEFORE, it) |
| import com.xkl.cdl.data.bean.course.Lesson | import com.xkl.cdl.data.bean.course.Lesson | ||||
| import com.xkl.cdl.data.bean.intentdata.LearnData | import com.xkl.cdl.data.bean.intentdata.LearnData | ||||
| import com.xkl.cdl.data.manager.CourseManager | import com.xkl.cdl.data.manager.CourseManager | ||||
| import com.xkl.cdl.data.manager.UserInfoManager | |||||
| import com.xkl.cdl.data.manager.db.DBCourseManager | import com.xkl.cdl.data.manager.db.DBCourseManager | ||||
| import com.xkl.cdl.data.manager.db.DbControlBase | import com.xkl.cdl.data.manager.db.DbControlBase | ||||
| import com.xkl.cdl.data.repository.DataRepository | import com.xkl.cdl.data.repository.DataRepository | ||||
| * @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} | * @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) { | private fun initReviewData(rev : ProtocolStringList) { | ||||
| if (!this::reviewDataList.isInitialized) { | |||||
| reviewDataList = mutableListOf() | |||||
| } | |||||
| reviewDataList.clear() | |||||
| //更新备忘本数据 | //更新备忘本数据 | ||||
| if (coursePackMainActivityVM.isMemoSource){ | if (coursePackMainActivityVM.isMemoSource){ | ||||
| coursePackMainActivityVM.coursePackChildrenMemo[course.courseId] = rev.toMutableList() | coursePackMainActivityVM.coursePackChildrenMemo[course.courseId] = rev.toMutableList() | ||||
| } | } | ||||
| //当前时间戳 | |||||
| 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() | |||||
| }) | |||||
| //今天需要复习的数据 | |||||
| val todayNeedReviewDataList = mutableListOf<LearnWord>() | |||||
| val todayNeedReviewCount = UserInfoManager.instance.getTodayNeedReviewCount(course.subjectId,course.coursePackId,course.courseId) | |||||
| if (todayNeedReviewCount > 0 ){ | |||||
| //当前时间戳 | |||||
| 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 && todayNeedReviewDataList.size < todayNeedReviewCount) { | |||||
| // lessonType为非准确的赋值,需要在查询具体详情数据的时候,在对lessonType进行具体赋值 | |||||
| todayNeedReviewDataList.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() | |||||
| }) | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| reviewDataList = todayNeedReviewDataList | |||||
| } | } | ||||
| /** 课程包主页上的更多按钮点击是否有效 */ | /** 课程包主页上的更多按钮点击是否有效 */ |
| import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
| import com.xkl.cdl.data.bean.MemoCoursePack | import com.xkl.cdl.data.bean.MemoCoursePack | ||||
| import com.xkl.cdl.data.manager.CourseManager | import com.xkl.cdl.data.manager.CourseManager | ||||
| import com.xkl.cdl.data.manager.UserInfoManager | |||||
| import com.xkl.cdl.module.XKLApplication | import com.xkl.cdl.module.XKLApplication | ||||
| import com.xkl.cdl.module.main.MainActivityViewModel | import com.xkl.cdl.module.main.MainActivityViewModel | ||||
| import io.reactivex.rxjava3.core.Observable | import io.reactivex.rxjava3.core.Observable | ||||
| resultMemoCoursePack.sortBy { | resultMemoCoursePack.sortBy { | ||||
| it.coursePack.coursePackId | it.coursePack.coursePackId | ||||
| } | } | ||||
| //重新定义用于复习的数据, 限制每天复习的大小 | |||||
| memoCoursePackList.forEach{ | |||||
| it.coursePack.childrenCourses.forEach { course -> | |||||
| //需要复习的数量 | |||||
| val v = it.coursePackChildrenReview[course.courseId] | |||||
| if ( v != null){ | |||||
| val todayNeedReviewNumber = UserInfoManager.instance.getTodayNeedReviewCount(course.subjectId,course.coursePackId,course.courseId) | |||||
| if ( v > todayNeedReviewNumber){ | |||||
| it.coursePackChildrenReview[course.courseId] = todayNeedReviewNumber | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| memoCoursePackList.addAll(resultMemoCoursePack) | memoCoursePackList.addAll(resultMemoCoursePack) | ||||
| } | } | ||||
| return@fromCallable true | return@fromCallable true |
| package com.xkl.cdl.module.m_my | package com.xkl.cdl.module.m_my | ||||
| import android.content.ClipData | |||||
| import android.content.ClipboardManager | |||||
| import android.content.Context | |||||
| import android.view.View | import android.view.View | ||||
| import android.view.ViewGroup | import android.view.ViewGroup | ||||
| import android.widget.TextView | import android.widget.TextView | ||||
| import com.huantansheng.easyphotos.callback.SelectCallback | import com.huantansheng.easyphotos.callback.SelectCallback | ||||
| import com.huantansheng.easyphotos.models.album.entity.Photo | import com.huantansheng.easyphotos.models.album.entity.Photo | ||||
| import com.lxj.xpopup.XPopup | import com.lxj.xpopup.XPopup | ||||
| import com.suliang.common.base.activity.ToastEvent | |||||
| import com.suliang.common.base.adapter.BaseAdapterViewHolder | import com.suliang.common.base.adapter.BaseAdapterViewHolder | ||||
| import com.suliang.common.base.adapter.BaseRVAdapter | import com.suliang.common.base.adapter.BaseRVAdapter | ||||
| import com.suliang.common.base.fragment.BaseFragmentVM | import com.suliang.common.base.fragment.BaseFragmentVM | ||||
| import com.xkl.cdl.data.manager.UserInfoManager | import com.xkl.cdl.data.manager.UserInfoManager | ||||
| import com.xkl.cdl.databinding.FragmentMyBinding | import com.xkl.cdl.databinding.FragmentMyBinding | ||||
| import com.xkl.cdl.databinding.ItemMySettingBinding | import com.xkl.cdl.databinding.ItemMySettingBinding | ||||
| import com.xkl.cdl.dialog.CommonDialog | |||||
| import com.xkl.cdl.dialog.CommonDialogBean | |||||
| import com.xkl.cdl.module.main.MainActivity | import com.xkl.cdl.module.main.MainActivity | ||||
| import com.xkl.cdl.util.EasyPhotoEngine | import com.xkl.cdl.util.EasyPhotoEngine | ||||
| import java.io.File | import java.io.File | ||||
| } | } | ||||
| private val rvData = mutableListOf<MyFragment.Item>( | private val rvData = mutableListOf<MyFragment.Item>( | ||||
| // Item("个性化", R.drawable.ic_palette,0), | // Item("个性化", R.drawable.ic_palette,0), | ||||
| Item("设置", R.drawable.ic_setting,1), | |||||
| Item("关于我们", R.drawable.ic_about_we,2)) | |||||
| // Item("到期时间",R.drawable.ic_clock,3), | |||||
| // Item("设置", R.drawable.ic_setting,1), | |||||
| // Item("关于我们", R.drawable.ic_about_we,2)) | |||||
| Item("到期时间",0,false), | |||||
| Item("我的激活码",1), | |||||
| Item("设置", 2), | |||||
| Item("关于我们", 3)) | |||||
| override fun initViewModel() : MyViewModel { | override fun initViewModel() : MyViewModel { | ||||
| (holder.binding as ItemMySettingBinding).run { | (holder.binding as ItemMySettingBinding).run { | ||||
| getItem(position).let { | getItem(position).let { | ||||
| tvName.text = it.name | tvName.text = it.name | ||||
| tvName.setCompoundDrawablesWithIntrinsicBounds(it.icon, 0, 0, 0) | |||||
| // tvName.setCompoundDrawablesWithIntrinsicBounds(it.icon, 0, 0, 0) | |||||
| ivArrowRight.visibility = if (it.isShowArrow) View.VISIBLE else View.GONE | ivArrowRight.visibility = if (it.isShowArrow) View.VISIBLE else View.GONE | ||||
| //到期时间 | |||||
| when(position){ | |||||
| 0 -> { | |||||
| tvMoreInfo.text = UserInfoManager.instance.getExpirationTime() | |||||
| tvMoreInfo.visibility = View.VISIBLE | |||||
| } | |||||
| else -> tvMoreInfo.visibility = View.GONE | |||||
| } | |||||
| } | } | ||||
| root.click { | root.click { | ||||
| when(getItem(position).itemFlag){ | when(getItem(position).itemFlag){ | ||||
| 0 -> "" //个性化 | |||||
| 1 -> startActivity(SettingActivity::class.java) //设置 | |||||
| 2 -> startActivity(AboutUsActivity::class.java) //关于我们 | |||||
| 0 -> "" //到期时间 | |||||
| 1 -> { | |||||
| val commonDialogBean = CommonDialogBean(titleTextValue = "激活码", | |||||
| contentTextValue = UserInfoManager.instance.getLicence(), | |||||
| leftTextValue = "复制", rightTextValue = "取消") | |||||
| CommonDialog.newInstance(commonDialogBean).apply { | |||||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||||
| if (!isRightClick) { | |||||
| //复制到剪切板 | |||||
| val clipboardManager = requireContext().getSystemService( | |||||
| Context.CLIPBOARD_SERVICE) as? ClipboardManager | |||||
| val clipData = ClipData.newPlainText("Name", commonDialogBean.contentTextValue) | |||||
| clipboardManager?.setPrimaryClip(clipData) | |||||
| showToast(ToastEvent("复制成功")) | |||||
| } | |||||
| dialog.dismissAllowingStateLoss() | |||||
| } | |||||
| }.show(childFragmentManager,"licence") | |||||
| } | |||||
| 2 -> startActivity(SettingActivity::class.java) //设置 | |||||
| 3 -> startActivity(AboutUsActivity::class.java) //关于我们 | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| tvUnit.text = item.unit | tvUnit.text = item.unit | ||||
| } | } | ||||
| data class Item(val name : String, val icon : Int,val itemFlag : Int , var isShowArrow : Boolean = true) | |||||
| // data class Item(val name : String, val icon : Int,val itemFlag : Int , var isShowArrow : Boolean = true) | |||||
| data class Item(val name : String, val itemFlag : Int , var isShowArrow : Boolean = true) | |||||
| } | } |
| import android.os.Bundle | import android.os.Bundle | ||||
| import androidx.activity.viewModels | import androidx.activity.viewModels | ||||
| import androidx.core.content.ContextCompat | |||||
| import androidx.core.widget.addTextChangedListener | import androidx.core.widget.addTextChangedListener | ||||
| import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
| import com.suliang.common.base.activity.BaseActivityVM | import com.suliang.common.base.activity.BaseActivityVM | ||||
| import com.suliang.common.base.activity.ToastEvent | import com.suliang.common.base.activity.ToastEvent | ||||
| import com.suliang.common.extension.click | import com.suliang.common.extension.click | ||||
| import com.suliang.common.util.StringUtil | |||||
| import com.xkl.cdl.R | import com.xkl.cdl.R | ||||
| import com.xkl.cdl.databinding.ActivityActivateBinding | import com.xkl.cdl.databinding.ActivityActivateBinding | ||||
| import com.xkl.cdl.dialog.CommonDialog | import com.xkl.cdl.dialog.CommonDialog | ||||
| import com.xkl.cdl.dialog.CommonDialogBean | import com.xkl.cdl.dialog.CommonDialogBean | ||||
| /** | /** | ||||
| * author: suliang | |||||
| * 2022/12/5 15:17 | |||||
| * describe : 激活设备Activity | |||||
| */ | |||||
| class ActivateActivity : BaseActivityVM<ActivityActivateBinding,ActivateViewModel>() { | |||||
| * author: suliang | |||||
| * 2022/12/5 15:17 | |||||
| * describe : 激活设备Activity | |||||
| */ | |||||
| class ActivateActivity : BaseActivityVM<ActivityActivateBinding, ActivateViewModel>() { | |||||
| override fun initActivity(savedInstanceState : Bundle?) { | override fun initActivity(savedInstanceState : Bundle?) { | ||||
| binding.titleBar.onBackClick = { | |||||
| setResult(RESULT_CANCELED) | |||||
| finish() | |||||
| } | |||||
| binding.etLicence.addTextChangedListener { | binding.etLicence.addTextChangedListener { | ||||
| binding.btActive.isEnabled = !it.isNullOrEmpty() && !it.isNullOrBlank() //不为空、空格 则激活按钮可用 | binding.btActive.isEnabled = !it.isNullOrEmpty() && !it.isNullOrBlank() //不为空、空格 则激活按钮可用 | ||||
| } | } | ||||
| } | } | ||||
| override fun loadData() { | override fun loadData() { | ||||
| vm.activeMutable.observe(this){ requestResult -> | |||||
| val commonDialogBean = CommonDialogBean(titleText = R.string.activate_result_title, rightText = R.string.sure ) | |||||
| when{ | |||||
| requestResult -> commonDialogBean.contentTextValue = "激活成功" | |||||
| else -> commonDialogBean.contentTextValue = "激活失败,失败原因:\n ${vm.errorRequestMsg}" | |||||
| vm.activeMutable.observe(this) { requestResult -> | |||||
| val commonDialogBean : CommonDialogBean | |||||
| when { | |||||
| requestResult -> { | |||||
| commonDialogBean = CommonDialogBean(titleText = R.string.activate_result_success, rightText = R.string.sure) | |||||
| commonDialogBean.contentTextValue = StringUtil.highString("到期时间: ${vm.expirationTime}", | |||||
| vm.expirationTime, | |||||
| ContextCompat.getColor(this@ActivateActivity, | |||||
| R.color.theme_color)) | |||||
| .toString() | |||||
| } | |||||
| else -> { | |||||
| commonDialogBean = CommonDialogBean(titleText = R.string.activate_result_failed, rightText = R.string.sure) | |||||
| commonDialogBean.contentTextValue = "${vm.errorRequestMsg}" | |||||
| } | |||||
| } | } | ||||
| CommonDialog.newInstance(commonDialogBean).apply { | CommonDialog.newInstance(commonDialogBean).apply { | ||||
| onCommonDialogButtonClickListener = { dialog : CommonDialog, _ -> | |||||
| onCommonDialogButtonClickListener = { dialog : CommonDialog, _ -> | |||||
| dialog.dismissAllowingStateLoss() | dialog.dismissAllowingStateLoss() | ||||
| if (requestResult) { //激活成功、关闭界面 | if (requestResult) { //激活成功、关闭界面 | ||||
| setResult(RESULT_OK) | |||||
| finish() //关闭界面 | finish() //关闭界面 | ||||
| } | } | ||||
| } | } |
| package com.xkl.cdl.module.splash | package com.xkl.cdl.module.splash | ||||
| import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||
| import com.google.gson.Gson | |||||
| import com.google.gson.GsonBuilder | |||||
| import com.google.gson.JsonObject | |||||
| import com.squareup.okhttp.MediaType | |||||
| import com.suliang.common.base.viewmodel.BaseViewModel | import com.suliang.common.base.viewmodel.BaseViewModel | ||||
| import com.suliang.common.util.DateUtil | |||||
| import com.suliang.common.util.net.HttpUtil | |||||
| import com.suliang.common.util.net.NetObserver | import com.suliang.common.util.net.NetObserver | ||||
| import com.suliang.common.util.thread.AppExecutors | import com.suliang.common.util.thread.AppExecutors | ||||
| import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
| import com.xkl.cdl.net.RequestUtil | import com.xkl.cdl.net.RequestUtil | ||||
| import io.reactivex.rxjava3.disposables.Disposable | import io.reactivex.rxjava3.disposables.Disposable | ||||
| import io.reactivex.rxjava3.schedulers.Schedulers | import io.reactivex.rxjava3.schedulers.Schedulers | ||||
| import okhttp3.FormBody | |||||
| import okhttp3.ResponseBody | |||||
| import okhttp3.* | |||||
| import okhttp3.MediaType.Companion.toMediaType | |||||
| import okhttp3.RequestBody.Companion.toRequestBody | |||||
| import org.json.JSONObject | |||||
| import java.io.IOException | |||||
| /** | /** | ||||
| * author: suliang | * author: suliang | ||||
| val activeMutable = MutableLiveData<Boolean>() | val activeMutable = MutableLiveData<Boolean>() | ||||
| var errorRequestMsg : String? = null | var errorRequestMsg : String? = null | ||||
| var expirationTime : String = "" | |||||
| /** | /** | ||||
| * 激活 | * 激活 | ||||
| showHideLoading(true) | showHideLoading(true) | ||||
| val deviceId = UserInfoManager.instance.getUuid() | val deviceId = UserInfoManager.instance.getUuid() | ||||
| val body = FormBody.Builder().add(AppConstants.DEVICE_ID, deviceId) | |||||
| .add(AppConstants.LICENSE_ID,licence).build() | |||||
| RequestUtil.postRequest<ResponseBody>("", body) | |||||
| .subscribeOn(Schedulers.from(AppExecutors.io)) | |||||
| .observeOn(Schedulers.from(AppExecutors.mainThread)) | |||||
| .subscribe(object : NetObserver<ResponseBody>(){ | |||||
| override fun success(t : ResponseBody) { | |||||
| //保存licence | |||||
| UserInfoManager.instance.putLicence(licence) | |||||
| activeMutable.value = true | |||||
| showHideLoading(false) | |||||
| } | |||||
| override fun failure(errorMsg : String?) { | |||||
| errorRequestMsg = errorMsg | |||||
| showHideLoading(false) | |||||
| } | |||||
| }) | |||||
| val mediaType = "application/json;charset=utf-8".toMediaType() | |||||
| val requestBody = "{\"${AppConstants.DEVICE_ID}\":\"$deviceId\",\"${AppConstants.LICENSE_ID}\":\"$licence\"}".toRequestBody(mediaType) | |||||
| val request = Request.Builder().url(AppConstants.ACTIVE).post(requestBody).build() | |||||
| HttpUtil.instance.getOkhttpClient().newCall(request).enqueue(object : Callback { | |||||
| override fun onFailure(call : Call, e : IOException) { | |||||
| e.printStackTrace() | |||||
| errorRequestMsg = "网络连接错误!" | |||||
| showHideLoading(false) | |||||
| } | |||||
| override fun onResponse(call : Call, response : Response) { | |||||
| if (response.isSuccessful){ | |||||
| val result = response.body!!.string() | |||||
| val jsonObject = JSONObject(result) | |||||
| if (jsonObject.has("expiry_time")) { //成功 | |||||
| expirationTime = jsonObject.getString("expiry_time") | |||||
| //保存licence | |||||
| UserInfoManager.instance.putLicence(licence) | |||||
| UserInfoManager.instance.putExpirationTime(expirationTime) | |||||
| showHideLoading(false) | |||||
| activeMutable.postValue(true) | |||||
| }else{ | |||||
| showHideLoading(false) | |||||
| errorRequestMsg = jsonObject.getString("error") | |||||
| showHideLoading(false) | |||||
| activeMutable.postValue(false) | |||||
| } | |||||
| }else{ | |||||
| showHideLoading(false) | |||||
| errorRequestMsg = "${response.code}" | |||||
| showHideLoading(false) | |||||
| activeMutable.postValue(false) | |||||
| } | |||||
| } | |||||
| }) | |||||
| } | } | ||||
| } | } |
| package com.xkl.cdl.module.splash | package com.xkl.cdl.module.splash | ||||
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||
| import android.content.Intent | |||||
| import android.os.Bundle | import android.os.Bundle | ||||
| import android.os.Handler | |||||
| import android.os.Looper | |||||
| import android.os.Message | |||||
| import androidx.activity.result.ActivityResult | |||||
| import androidx.activity.result.contract.ActivityResultContracts | |||||
| import androidx.databinding.Observable | |||||
| import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
| import appApi.AppApi | import appApi.AppApi | ||||
| import com.suliang.common.base.activity.BaseActivityVM | import com.suliang.common.base.activity.BaseActivityVM | ||||
| import com.suliang.common.base.activity.ToastEvent | |||||
| import com.suliang.common.util.LogUtil | import com.suliang.common.util.LogUtil | ||||
| import com.suliang.common.util.file.FileUtil | |||||
| import com.suliang.common.util.thread.AppExecutors | import com.suliang.common.util.thread.AppExecutors | ||||
| import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
| import com.xkl.cdl.data.manager.CourseManager | import com.xkl.cdl.data.manager.CourseManager | ||||
| import com.xkl.cdl.data.manager.FilePathManager | |||||
| import com.xkl.cdl.data.manager.UserInfoManager | import com.xkl.cdl.data.manager.UserInfoManager | ||||
| import com.xkl.cdl.data.manager.db.DbCoursePackManager | |||||
| import com.xkl.cdl.databinding.ActivitySplashBinding | import com.xkl.cdl.databinding.ActivitySplashBinding | ||||
| import com.xkl.cdl.dialog.CommonDialog | import com.xkl.cdl.dialog.CommonDialog | ||||
| import com.xkl.cdl.dialog.CommonDialogBean | import com.xkl.cdl.dialog.CommonDialogBean | ||||
| import com.xkl.cdl.module.XKLApplication | import com.xkl.cdl.module.XKLApplication | ||||
| import com.xkl.cdl.module.main.MainActivity | import com.xkl.cdl.module.main.MainActivity | ||||
| import java.util.* | import java.util.* | ||||
| import java.util.concurrent.TimeUnit | |||||
| import kotlin.system.exitProcess | import kotlin.system.exitProcess | ||||
| @SuppressLint("CustomSplashScreen") | @SuppressLint("CustomSplashScreen") | ||||
| class SplashActivity : BaseActivityVM<ActivitySplashBinding,SplashViewModel>() { | |||||
| class SplashActivity : BaseActivityVM<ActivitySplashBinding, SplashViewModel>() { | |||||
| /** 激活返回 */ | |||||
| private val requestDataLauncher = registerForActivityResult( | |||||
| ActivityResultContracts.StartActivityForResult()) { result : ActivityResult -> | |||||
| if (result.resultCode == RESULT_OK) { //激活成功 | |||||
| checkResource() | |||||
| } else { //未激活 | |||||
| showToast(ToastEvent("未激活,退出应用")) | |||||
| Handler(Looper.getMainLooper()).postDelayed({ | |||||
| finish() | |||||
| exitProcess(0) | |||||
| }, 1000) | |||||
| } | |||||
| } | |||||
| override fun onCreateOwn(savedInstanceState : Bundle?) { | override fun onCreateOwn(savedInstanceState : Bundle?) { | ||||
| if (!isTaskRoot) { | if (!isTaskRoot) { | ||||
| } | } | ||||
| override fun initActivity(savedInstanceState : Bundle?) { | override fun initActivity(savedInstanceState : Bundle?) { | ||||
| vm.handler = Handler(Looper.getMainLooper(),object :Handler.Callback{ | |||||
| override fun handleMessage(msg : Message) : Boolean { | |||||
| when(msg.what){ | |||||
| 1 -> { //进度 | |||||
| println("接收:${ msg.obj as String}") | |||||
| binding.tvShowMsg.text = msg.obj as String | |||||
| } | |||||
| 2 -> { //数据合并完成 | |||||
| println("接收:合并完成") | |||||
| binding.tvShowMsg.text = "资源数据检测中......" | |||||
| vm.copyResource() | |||||
| } | |||||
| } | |||||
| return true | |||||
| } | |||||
| }) | |||||
| //账号过期 | |||||
| vm.isExpirationMutableLiveData.observe(this) { | |||||
| expirationDialog.show(supportFragmentManager, "expiration") | |||||
| } | |||||
| //获取到绑定的课程 | |||||
| vm.bindCoursePackResult.observe(this) { | |||||
| if (vm.bindCoursePackList.isNullOrEmpty()) { //如果绑定的数据拉取为空 | |||||
| notCourseDialog.show(supportFragmentManager, "binder") | |||||
| } else { //检查数据 | |||||
| vm.checkCoursePack() | |||||
| } | |||||
| } | |||||
| // 课程下载失败的提示 | |||||
| vm.downLoadResult.observe(this) { | |||||
| downLoadFailedDialog.show(supportFragmentManager, "download") | |||||
| } | |||||
| // vm.downLoadNameAndProgress.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback(){ | |||||
| // override fun onPropertyChanged(sender : Observable?, propertyId : Int) { | |||||
| // AppExecutors.mainThread.execute { | |||||
| // binding.tvShowMsg.text = vm.downLoadNameAndProgress.get() | |||||
| // } | |||||
| // } | |||||
| // | |||||
| // }) | |||||
| vm.downLoadNameAndProgress.observe(this){ | |||||
| println("Activity 接收: $it") | |||||
| binding.tvShowMsg.text = it | |||||
| } | |||||
| // | |||||
| //课程包合并结束 | |||||
| vm.mergeCoursePackResult.observe(this) { | |||||
| binding.tvShowMsg.text = "资源数据检测中......" | |||||
| vm.copyResource() | |||||
| } | |||||
| //所有数据都处理完成,跳转主页 | |||||
| vm.allOver.observe(this) { | |||||
| startActivity(MainActivity::class.java) | |||||
| finish() | |||||
| } | |||||
| } | } | ||||
| // TODO: 激活返回跳转处理 | |||||
| override fun loadData() { | override fun loadData() { | ||||
| //检测唯一码、检测激活码 | //检测唯一码、检测激活码 | ||||
| val uuid = UserInfoManager.instance.getUuid() | val uuid = UserInfoManager.instance.getUuid() | ||||
| if (uuid.isEmpty()) { | if (uuid.isEmpty()) { | ||||
| //生成唯一码,存入本地 | //生成唯一码,存入本地 | ||||
| val newUuid = UUID.randomUUID().toString() | |||||
| val newUuid = UUID.randomUUID().toString().replace("-","") | |||||
| LogUtil.e(this::class.java.name, newUuid) | |||||
| UserInfoManager.instance.putUuid(newUuid) | UserInfoManager.instance.putUuid(newUuid) | ||||
| //跳转激活,绑定激活码 | //跳转激活,绑定激活码 | ||||
| startActivity(ActivateActivity::class.java) | |||||
| Handler(Looper.getMainLooper()).postDelayed({ | |||||
| requestDataLauncher.launch(Intent(this, ActivateActivity::class.java)) | |||||
| }, 1000) | |||||
| return | return | ||||
| } | } | ||||
| //检测激活码是否存在 | //检测激活码是否存在 | ||||
| if (UserInfoManager.instance.getLicence().isEmpty()) { | if (UserInfoManager.instance.getLicence().isEmpty()) { | ||||
| //激活码不存在,跳转到激活界面 | //激活码不存在,跳转到激活界面 | ||||
| startActivity(ActivateActivity::class.java) | |||||
| //跳转激活,绑定激活码 | |||||
| Handler(Looper.getMainLooper()).postDelayed({ | |||||
| requestDataLauncher.launch(Intent(this, ActivateActivity::class.java)) | |||||
| }, 1000) | |||||
| return | return | ||||
| } | } | ||||
| //检查用户下载的课程,拉取到新的直接替换,没有拉取到则使用缓存,如果缓存也没有则弹窗提示 | |||||
| binding.tvShowMsg.text = "检查绑定课程中......" | |||||
| vm.loadBindCourse() | |||||
| vm.bindCoursePackResult.observe(this){ | |||||
| if (vm.bindCoursePackList.isNullOrEmpty()) { //如果绑定的数据拉取为空 | |||||
| val commonDialogBean = CommonDialogBean(titleTextValue = "提示", contentTextValue = "未查询到绑定的课程\n请检查网络或联系业务员!", | |||||
| rightTextValue = "重新拉取", leftTextValue = "退出") | |||||
| CommonDialog.newInstance(commonDialogBean).apply { | |||||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||||
| dialog.dismissAllowingStateLoss() | |||||
| if (isRightClick){ | |||||
| vm.loadBindCourse() | |||||
| }else{ //退出应用 | |||||
| finish() | |||||
| exitProcess(0) | |||||
| } | |||||
| } | |||||
| }.show(supportFragmentManager,"binder") | |||||
| }else{ //检查数据 | |||||
| binding.tvShowMsg.text = "开始下载课程......" | |||||
| vm.checkCoursePack() | |||||
| } | |||||
| //唯一码 与 激活码都存在,那就开始检查是否过期 | |||||
| if (UserInfoManager.instance.getIsExpiration()){ //过期 | |||||
| vm.isExpirationMutableLiveData.value = true | |||||
| return | |||||
| } | } | ||||
| /* 下载失败的提示 */ | |||||
| vm.downLoadResult.observe(this){ | |||||
| val commonDialogBean = CommonDialogBean(titleTextValue = "提示", contentTextValue = "课程下载失败,是否重新下载?", | |||||
| rightTextValue = "继续", leftTextValue = "重新下载") | |||||
| CommonDialog.newInstance(commonDialogBean).apply { | |||||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||||
| dialog.dismissAllowingStateLoss() | |||||
| if (isRightClick){ | |||||
| vm.checkCoursePack() | |||||
| }else{ //继续 | |||||
| } | |||||
| } | |||||
| }.show(supportFragmentManager,"download") | |||||
| //检查课程 | |||||
| checkResource() | |||||
| } | |||||
| override fun initViewModel() : SplashViewModel { | |||||
| return ViewModelProvider(this)[SplashViewModel::class.java] | |||||
| } | |||||
| /** | |||||
| * 检查资源 | |||||
| */ | |||||
| private fun checkResource() { | |||||
| AppExecutors.diskIO.execute { | |||||
| //用户的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) | |||||
| } | |||||
| AppExecutors.mainThread.execute{ | |||||
| //检查用户下载的课程,拉取到新的直接替换,没有拉取到则使用缓存,如果缓存也没有则弹窗提示 | |||||
| binding.tvShowMsg.text = "检查绑定课程中......" | |||||
| vm.loadBindCourse() | |||||
| } | |||||
| } | } | ||||
| vm.mergeCoursePackResult.observe(this){ | |||||
| binding.tvShowMsg.text = "资源数据检测中......" | |||||
| vm.copyResource() | |||||
| } | |||||
| override fun onBackPressed() { | |||||
| } | |||||
| /** | |||||
| * 课程已过期 | |||||
| */ | |||||
| private val expirationDialog : CommonDialog by lazy { | |||||
| val commonDialogBean = CommonDialogBean(titleTextValue = "警告", | |||||
| contentTextValue = "激活码已于 ${UserInfoManager.instance.getExpirationTime()} 到期,请联系业务员续期", | |||||
| rightTextValue = "退出") | |||||
| CommonDialog.newInstance(commonDialogBean).apply { | |||||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||||
| dialog.dismissAllowingStateLoss() | |||||
| finish() | |||||
| exitProcess(0) | |||||
| } | |||||
| } | } | ||||
| vm.allOver.observe(this){ | |||||
| startActivity(MainActivity::class.java) | |||||
| finish() | |||||
| } | |||||
| //未拉取到课程弹窗 | |||||
| private val notCourseDialog : CommonDialog by lazy { | |||||
| val commonDialogBean = CommonDialogBean(titleTextValue = "提示", contentTextValue = "未查询到绑定的课程\n请检查网络或联系业务员!", | |||||
| rightTextValue = "重新拉取", leftTextValue = "退出") | |||||
| CommonDialog.newInstance(commonDialogBean).apply { | |||||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||||
| dialog.dismissAllowingStateLoss() | |||||
| if (isRightClick) { | |||||
| vm.loadBindCourse() | |||||
| } else { //退出应用 | |||||
| finish() | |||||
| exitProcess(0) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | |||||
| // | |||||
| // showHideLoading(true) | |||||
| // | |||||
| // AppExecutors.diskIO.execute { | |||||
| // //读取课程数据 | |||||
| // //用户的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) | |||||
| // } | |||||
| // | |||||
| // //复制词典 和 词汇量测试 | |||||
| // val dictionary = FilePathManager.getDictionaryDbPath() | |||||
| // if (!dictionary.exists() || dictionary.length() != FileUtil.getAssetFileSize("dictionary.db")) { | |||||
| // LogUtil.e("复制dictionary.db") | |||||
| // FileUtil.copyAsset("dictionary.db", dictionary) | |||||
| // } | |||||
| // val vocabulary = FilePathManager.getVocabularyDbPath() | |||||
| // if (!vocabulary.exists() || vocabulary.length() != FileUtil.getAssetFileSize("vocabulary.db")) { | |||||
| // LogUtil.e("复制vocabulary.db") | |||||
| // FileUtil.copyAsset("vocabulary.db", vocabulary) | |||||
| // } | |||||
| // | |||||
| // // TODO: 2022/3/22 读取当前app绑定的课程数据, | |||||
| //// DbCoursePackManager().queryBindingCoursePack("262,261,264,136,547,615,516,411") | |||||
| // //暂时不用口语课程 | |||||
| // DbCoursePackManager().queryBindingCoursePack("262,261,264,136,547,516,411") | |||||
| // //复制课程的数据库到对应位置 | |||||
| // CourseManager.checkCourseDb() | |||||
| // //定时跳跃到住主界面 | |||||
| // AppExecutors.scheduledExecutor.schedule({ | |||||
| // AppExecutors.mainThread.execute { | |||||
| // showHideLoading(false) | |||||
| // startActivity(MainActivity::class.java) | |||||
| // finish() | |||||
| // } | |||||
| // }, 1, TimeUnit.SECONDS) | |||||
| // } | |||||
| //下载失败弹窗 | |||||
| private val downLoadFailedDialog : CommonDialog by lazy { | |||||
| val commonDialogBean = CommonDialogBean(titleTextValue = "提示", contentTextValue = "课程下载失败,是否重新下载?", leftTextValue = "下一步", | |||||
| rightTextValue = "重新下载") | |||||
| CommonDialog.newInstance(commonDialogBean).apply { | |||||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||||
| dialog.dismissAllowingStateLoss() | |||||
| if (isRightClick) { | |||||
| vm.checkCoursePack() //重新下载 | |||||
| } else { // 下一步 | |||||
| vm.copyResource() //复制资源 | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| override fun initViewModel() : SplashViewModel { | |||||
| return ViewModelProvider(this)[SplashViewModel::class.java] | |||||
| override fun finish() { | |||||
| vm.handler.removeCallbacksAndMessages(null) | |||||
| super.finish() | |||||
| } | } | ||||
| package com.xkl.cdl.module.splash | package com.xkl.cdl.module.splash | ||||
| import android.os.Handler | |||||
| import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||
| import com.suliang.common.base.viewmodel.BaseViewModel | import com.suliang.common.base.viewmodel.BaseViewModel | ||||
| import com.suliang.common.util.LogUtil | import com.suliang.common.util.LogUtil | ||||
| import com.suliang.common.util.file.FileUtil | import com.suliang.common.util.file.FileUtil | ||||
| import com.suliang.common.util.net.DownLoadFileListener | import com.suliang.common.util.net.DownLoadFileListener | ||||
| import com.suliang.common.util.net.HttpUtil | import com.suliang.common.util.net.HttpUtil | ||||
| import com.suliang.common.util.net.NetObserver | |||||
| import com.suliang.common.util.thread.AppExecutors | import com.suliang.common.util.thread.AppExecutors | ||||
| import com.xkl.cdl.data.AppConstants | import com.xkl.cdl.data.AppConstants | ||||
| import com.xkl.cdl.data.bean.course.Course | |||||
| import com.xkl.cdl.data.bean.course.CoursePack | import com.xkl.cdl.data.bean.course.CoursePack | ||||
| import com.xkl.cdl.data.manager.CourseManager | import com.xkl.cdl.data.manager.CourseManager | ||||
| import com.xkl.cdl.data.manager.FilePathManager | import com.xkl.cdl.data.manager.FilePathManager | ||||
| import com.xkl.cdl.data.manager.UserInfoManager | import com.xkl.cdl.data.manager.UserInfoManager | ||||
| import com.xkl.cdl.net.RequestUtil | |||||
| import io.reactivex.rxjava3.schedulers.Schedulers | |||||
| import okhttp3.FormBody | |||||
| import okhttp3.Call | |||||
| import okhttp3.Callback | |||||
| import okhttp3.MediaType.Companion.toMediaType | |||||
| import okhttp3.Request | |||||
| import okhttp3.RequestBody.Companion.toRequestBody | |||||
| import okhttp3.Response | |||||
| import org.json.JSONObject | |||||
| import java.io.BufferedOutputStream | import java.io.BufferedOutputStream | ||||
| import java.io.File | import java.io.File | ||||
| import java.io.FileInputStream | |||||
| import java.io.FileOutputStream | import java.io.FileOutputStream | ||||
| import java.util.zip.ZipInputStream | |||||
| import java.io.IOException | |||||
| import java.util.zip.ZipFile | |||||
| /** | /** | ||||
| * author: suliang | * author: suliang | ||||
| */ | */ | ||||
| class SplashViewModel : BaseViewModel() { | class SplashViewModel : BaseViewModel() { | ||||
| lateinit var handler : Handler | |||||
| //账号是否过期 | |||||
| var isExpirationMutableLiveData = MutableLiveData<Boolean>() | |||||
| //绑定课程的结果 | //绑定课程的结果 | ||||
| var bindCoursePackList : List<CoursePack>? = null | var bindCoursePackList : List<CoursePack>? = null | ||||
| val bindCoursePackResult : MutableLiveData<Boolean> = MutableLiveData() | val bindCoursePackResult : MutableLiveData<Boolean> = MutableLiveData() | ||||
| //下载课程与进度 | //下载课程与进度 | ||||
| val downLoadNameProgress : MutableLiveData<String> = MutableLiveData() | |||||
| val downLoadNameAndProgress : MutableLiveData<String> = MutableLiveData() | |||||
| // val downLoadNameAndProgress = ObservableField<String>() | |||||
| var progress = "" | |||||
| //下载课程失败 | //下载课程失败 | ||||
| val downLoadResult : MutableLiveData<Boolean> = MutableLiveData() | val downLoadResult : MutableLiveData<Boolean> = MutableLiveData() | ||||
| /** | /** | ||||
| * 拉取绑定的课程 | * 拉取绑定的课程 | ||||
| * const ( | |||||
| * NotActive = 20001 //未激活 | |||||
| * OutOfExpiry = 20002 //已过期 | |||||
| * InvalidData = 20003 //非法数据 | |||||
| * RepeatActivation = 20004 //重复激活 | |||||
| * ) | |||||
| * 400 参数错误 | |||||
| * 500 服务器错误 | |||||
| * @return | * @return | ||||
| */ | */ | ||||
| fun loadBindCourse() { | fun loadBindCourse() { | ||||
| val deviceId = UserInfoManager.instance.getUuid() | val deviceId = UserInfoManager.instance.getUuid() | ||||
| val licenceId = UserInfoManager.instance.getLicence() | val licenceId = UserInfoManager.instance.getLicence() | ||||
| val body = FormBody.Builder().add(AppConstants.DEVICE_ID, deviceId).add(AppConstants.LICENSE_ID, licenceId).build() | |||||
| // TODO: 绑定课程的转换 | |||||
| RequestUtil.postRequest<List<CoursePack>>("", body) | |||||
| .observeOn(Schedulers.from(AppExecutors.io)) | |||||
| .observeOn(Schedulers.from(AppExecutors.mainThread)) | |||||
| .subscribe(object : NetObserver<List<CoursePack>>() { | |||||
| override fun success(t : List<CoursePack>) { | |||||
| UserInfoManager.instance.putBindCourse(t) | |||||
| bindCoursePackList = t | |||||
| bindCoursePackResult.value = true | |||||
| } | |||||
| override fun failure(errorMsg : String?) { | |||||
| bindCoursePackList = UserInfoManager.instance.getBindCourse() | |||||
| bindCoursePackResult.value = false | |||||
| val mediaType = "application/json;charset=utf-8".toMediaType() | |||||
| val requestBody = "{\"${AppConstants.DEVICE_ID}\":\"$deviceId\",\"${AppConstants.LICENSE_ID}\":\"$licenceId\"}".toRequestBody( | |||||
| mediaType) | |||||
| val request = Request.Builder().url(AppConstants.BIND_COURSE).post(requestBody).build() | |||||
| HttpUtil.instance.getOkhttpClient().newCall(request).enqueue(object : Callback { | |||||
| override fun onFailure(call : Call, e : IOException) { | |||||
| e.printStackTrace() | |||||
| bindCoursePackResult.value = false | |||||
| } | |||||
| override fun onResponse(call : Call, response : Response) { | |||||
| if (response.isSuccessful) { | |||||
| try { | |||||
| val body = response.body!!.string() | |||||
| val jo = JSONObject(body) | |||||
| when (jo.getInt("code")) { | |||||
| 200 -> { | |||||
| val result = decodeBinderCourseJson(body) | |||||
| //结果为空 | |||||
| UserInfoManager.instance.putBindCourse(body) //保存 | |||||
| UserInfoManager.instance.putIsExpiration(false) //成功 账号没有过期 | |||||
| bindCoursePackList = result | |||||
| bindCoursePackResult.postValue(true) | |||||
| return | |||||
| } | |||||
| 20002 -> { | |||||
| isExpirationMutableLiveData.postValue(true) | |||||
| UserInfoManager.instance.putIsExpiration(true) //账号已经过期了 | |||||
| return | |||||
| } | |||||
| } | |||||
| } catch (e : Exception) { | |||||
| e.printStackTrace() | |||||
| } | } | ||||
| }) | |||||
| } | |||||
| //其他错误,拿本地数据 | |||||
| LogUtil.e("$response") | |||||
| bindCoursePackList = decodeBinderCourseJson(UserInfoManager.instance.getBindCourse()) | |||||
| bindCoursePackResult.postValue(false) | |||||
| } | |||||
| }) | |||||
| } | |||||
| private fun decodeBinderCourseJson(json : String) : MutableList<CoursePack>? { | |||||
| if (json.isEmpty()) return null | |||||
| val result = mutableListOf<CoursePack>() | |||||
| val jo = JSONObject(json) | |||||
| val cpList = jo.getJSONArray("course_pack") //课程包数组 | |||||
| for (i in 0 until cpList.length()) { | |||||
| val cp = cpList.getJSONObject(i) // 课程包 | |||||
| val subject_id = cp.getInt("subject_id") | |||||
| val coursePackId = cp.getLong("id") | |||||
| val coursePackName = cp.getString("title") | |||||
| val summary = cp.getString("summary") | |||||
| val coursePackType = cp.getInt("category_id") | |||||
| //定义课程包 | |||||
| val coursePack = CoursePack(coursePackId, coursePackName, summary, subject_id, coursePackType) | |||||
| //定义课程列表集合 | |||||
| val courseList = mutableListOf<Course>() | |||||
| val courseMap = mutableMapOf<Int, Course>() // key : typeId value : Course | |||||
| val csList = cp.getJSONArray("courses") //课程列表 | |||||
| for (j in 0 until csList.length()) { | |||||
| val cs = csList.getJSONObject(j) //课程 | |||||
| val courseId = cs.getLong("id") | |||||
| val courseTitle = cs.getString("title") | |||||
| val courseType = cs.getInt("type_id") | |||||
| val totalWords = cs.getInt("total_words") | |||||
| val dbName = File(FilePathManager.getDbRootPath(), "$subject_id/$coursePackId/$courseId/course.db").path | |||||
| if (j == 0) { | |||||
| coursePack.downLoadZipUrl = cs.getString("download_url") | |||||
| } | |||||
| //定义好课程 | |||||
| val course = Course(subject_id, coursePackId, coursePackType, courseId, courseTitle, courseType, totalWords, | |||||
| dbName) | |||||
| if (coursePackType == AppConstants.COURSEPACK_TYPE_ENGLISH_WORD) { | |||||
| courseMap[courseType] = course | |||||
| } else { | |||||
| courseList.add(course) | |||||
| } | |||||
| } | |||||
| //单词类课程按顺序添加 | |||||
| if (coursePackType == AppConstants.COURSEPACK_TYPE_ENGLISH_WORD) { | |||||
| courseMap[AppConstants.COURSE_TYPE_ENGLISH_DISCERN]?.let { | |||||
| courseList.add(it) | |||||
| } | |||||
| courseMap[AppConstants.COURSE_TYPE_ENGLISH_SPELL]?.let { | |||||
| courseList.add(it) | |||||
| } | |||||
| courseMap[AppConstants.COURSE_TYPE_ENGLISH_VOICE]?.let { | |||||
| courseList.add(it) | |||||
| } | |||||
| } | |||||
| coursePack.childrenCourses = courseList | |||||
| result.add(coursePack) | |||||
| } | |||||
| return result | |||||
| } | } | ||||
| /** | /** | ||||
| * 开始检查绑定的课程数据,是否进行了下载 | * 开始检查绑定的课程数据,是否进行了下载 | ||||
| */ | */ | ||||
| fun checkCoursePack() { | fun checkCoursePack() { | ||||
| //需要下载的数量 | |||||
| var needCount = 0 | |||||
| AppExecutors.io.execute { | AppExecutors.io.execute { | ||||
| //需要下载的数量 | |||||
| var needCount = 0 | |||||
| //判断该课程的数据的数据包是否存在 | //判断该课程的数据的数据包是否存在 | ||||
| bindCoursePackList?.forEach { coursePackInfo : CoursePack -> | bindCoursePackList?.forEach { coursePackInfo : CoursePack -> | ||||
| var isAdd = false | var isAdd = false | ||||
| //课程id 判断数据库是否存在 | //课程id 判断数据库是否存在 | ||||
| coursePackInfo.childrenCourses.forEach { courseId -> | |||||
| val courseID = coursePackInfo.childrenCourses[0] | |||||
| coursePackInfo.childrenCourses.forEach { course_ -> | |||||
| //数据库不存在 | //数据库不存在 | ||||
| val dbFile = File(FilePathManager.getDbRootPath(), | val dbFile = File(FilePathManager.getDbRootPath(), | ||||
| "${coursePackInfo.subjectId}/${coursePackInfo.coursePackId}/$courseID/course.db") | |||||
| "${coursePackInfo.subjectId}/${coursePackInfo.coursePackId}/${course_.courseId}/course.db") | |||||
| if (!dbFile.exists() && !isAdd) { | if (!dbFile.exists() && !isAdd) { | ||||
| coursePackInfo.isDown = false | coursePackInfo.isDown = false | ||||
| needCount++ | needCount++ | ||||
| val lock : String = "" | val lock : String = "" | ||||
| //默认下载成功,当有false时,说明下载有失败 | //默认下载成功,当有false时,说明下载有失败 | ||||
| var downLoadSuccess = true | var downLoadSuccess = true | ||||
| println("开始下载") | |||||
| //下载课程 | //下载课程 | ||||
| bindCoursePackList?.forEach { coursePackBaseInfo -> | bindCoursePackList?.forEach { coursePackBaseInfo -> | ||||
| if (!coursePackBaseInfo.isDown) { | if (!coursePackBaseInfo.isDown) { | ||||
| val saveFile = File(FilePathManager.getZipRootPath(), | val saveFile = File(FilePathManager.getZipRootPath(), | ||||
| coursePackBaseInfo.downLoadZipUrl!!.substringAfterLast("/")) | |||||
| HttpUtil.instance.downLoadAsync(coursePackBaseInfo.downLoadZipUrl!!, saveFile, object : DownLoadFileListener { | |||||
| val coursePack = coursePackBaseInfo | |||||
| coursePackBaseInfo.downLoadZipUrl.substringAfterLast("/")) | |||||
| val listener = object : DownLoadFileListener { | |||||
| override fun downFileSize(fileSize : Long) { | override fun downFileSize(fileSize : Long) { | ||||
| } | } | ||||
| override fun downFileProgress(progress : Int) { | override fun downFileProgress(progress : Int) { | ||||
| downLoadNameProgress.postValue("${coursePack.coursePackName} : $progress%") | |||||
| // println("发送:${coursePackBaseInfo.coursePackName} : $progress% ") | |||||
| val obtainMessage = handler.obtainMessage() | |||||
| obtainMessage.obj = "${coursePackBaseInfo.coursePackName} : $progress%" | |||||
| obtainMessage.what = 1 | |||||
| handler.sendMessage(obtainMessage) | |||||
| } | } | ||||
| override fun downFileResult(saveFile : File?) { | override fun downFileResult(saveFile : File?) { | ||||
| downLoadSuccess = false | downLoadSuccess = false | ||||
| } | } | ||||
| } | } | ||||
| println("${coursePackBaseInfo.coursePackName} : 下载成功") | |||||
| //下载成功,解压到对应的文件 | //下载成功,解压到对应的文件 | ||||
| saveFile?.let { file -> | saveFile?.let { file -> | ||||
| coursePack.let { | |||||
| val inputStream = FileInputStream(file) | |||||
| val zipInputStream = ZipInputStream(inputStream) | |||||
| var count = 0 | |||||
| val buffer = ByteArray(5120) | |||||
| var nextEntry = zipInputStream.nextEntry | |||||
| val courseId = coursePack.childrenCourses[0] | |||||
| //如果是作文,作文需要检查数据库,同时需要检查其下的图片、视频和关键帧 | |||||
| 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) | |||||
| while (nextEntry != null) { | |||||
| when { | |||||
| nextEntry.name.endsWith((".icon.png")) -> File(FilePathManager.getIconRootPath(), | |||||
| "${it.subjectId}/${coursePack.coursePackId}/icon") | |||||
| nextEntry.name.endsWith(".mp4.png") -> File(FilePathManager.getMp4PngRootPath(), | |||||
| "${it.subjectId}/${coursePack.coursePackId}/$courseId/${ | |||||
| nextEntry.name.substringAfterLast( | |||||
| "/") | |||||
| }") | |||||
| nextEntry.name.endsWith(".mp4") -> File(FilePathManager.getMp4RootPath(), | |||||
| "${it.subjectId}/${coursePack.coursePackId}/$courseId/${ | |||||
| nextEntry.name.substringAfterLast("/") | |||||
| }") | |||||
| nextEntry.name.endsWith(".png") -> File(FilePathManager.getPngRootPath(), | |||||
| "${it.subjectId}/${coursePack.coursePackId}/$courseId/${ | |||||
| nextEntry.name.substringAfterLast("/") | |||||
| }") | |||||
| nextEntry.name.endsWith(".db") -> File(FilePathManager.getDbRootPath(), | |||||
| "${it.subjectId}/${coursePack.coursePackId}/$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 { //其他的项目只需要复制和检查数据库 | |||||
| while (nextEntry != null) { | |||||
| when { | |||||
| nextEntry.name.endsWith(".db") -> File(FilePathManager.getDbRootPath(), | |||||
| "${it.subjectId}/${coursePack.coursePackId}/$courseId/course.db") | |||||
| nextEntry.name.endsWith((".icon.png")) -> File(FilePathManager.getIconRootPath(), | |||||
| "${it.subjectId}/${coursePack.coursePackId}/icon") | |||||
| else -> null | |||||
| }?.let { | |||||
| if (!it.exists()) { | |||||
| it.parentFile?.let { parentFile -> | |||||
| if (!parentFile.exists()) parentFile.mkdirs() | |||||
| } | |||||
| val outputStream = BufferedOutputStream(FileOutputStream(it)) | |||||
| while ((zipInputStream.read(buffer).also { count = it }) != -1) { | |||||
| outputStream.write(buffer, 0, count) | |||||
| } | |||||
| outputStream.close() | |||||
| } | |||||
| } | |||||
| nextEntry = zipInputStream.nextEntry | |||||
| } | |||||
| coursePackBaseInfo.let { | |||||
| try { | |||||
| unZipFile_1(file, it) | |||||
| it.isDown = true //修改标记 | |||||
| } catch (e : Exception) { | |||||
| e.printStackTrace() | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| }) | |||||
| } | |||||
| // 等待下载完成 | |||||
| while (downLoadOverSize != needCount) { | |||||
| } | |||||
| HttpUtil.instance.downLoadSync(coursePackBaseInfo.downLoadZipUrl, saveFile, listener); | |||||
| } | } | ||||
| //判断下载是否成功 | |||||
| if (!downLoadSuccess) { //下载失败、弹窗提示 | |||||
| downLoadResult.postValue(false) | |||||
| return@execute | |||||
| } | |||||
| println("开始判断是否下载完成") | |||||
| //判断下载是否成功 | |||||
| if (!downLoadSuccess) { //下载失败、弹窗提示 | |||||
| AppExecutors.mainThread.execute { | |||||
| downLoadResult.value = false | |||||
| } | } | ||||
| //组合用户的课程包信息 | |||||
| //英语课程包 | |||||
| val englishCoursePack = mutableListOf<CoursePack>() | |||||
| //语文课程包 | |||||
| val chineseCoursePack = mutableListOf<CoursePack>() | |||||
| bindCoursePackList?.forEach { | |||||
| if (it.isDown) { | |||||
| it.cover = File(FilePathManager.getIconRootPath(), "${it.subjectId}/${it.coursePackId}/icon").path | |||||
| //初始作文设置进度 | |||||
| if (it.subjectId == AppConstants.SUBJECT_CHINESE){ | |||||
| //语文初始需要给设置一下其进度,用于显示 | |||||
| run m@{ | |||||
| CourseManager.mSortInfoList[AppConstants.SUBJECT_CHINESE]?.forEach { sortInfo -> | |||||
| if (it.coursePackId == sortInfo.packId && it.childrenCourses[0].courseId == sortInfo.courseId ){ | |||||
| it.childrenCourses[0].courseLearnProgress = sortInfo.s //设置进度 | |||||
| return@m | |||||
| } | |||||
| return@execute | |||||
| } | |||||
| //组合用户的课程包信息 | |||||
| //英语课程包 | |||||
| val englishCoursePack = mutableListOf<CoursePack>() | |||||
| //语文课程包 | |||||
| val chineseCoursePack = mutableListOf<CoursePack>() | |||||
| bindCoursePackList?.forEach { | |||||
| if (it.isDown) { | |||||
| it.cover = File(FilePathManager.getIconRootPath(), "${it.subjectId}/${it.coursePackId}/icon").path | |||||
| //初始作文设置进度 | |||||
| if (it.subjectId == AppConstants.SUBJECT_CHINESE) { | |||||
| //语文初始需要给设置一下其进度,用于显示 | |||||
| run m@{ | |||||
| CourseManager.mSortInfoList[AppConstants.SUBJECT_CHINESE]?.forEach { sortInfo -> | |||||
| if (it.coursePackId == sortInfo.packId && it.childrenCourses[0].courseId == sortInfo.courseId) { | |||||
| it.childrenCourses[0].courseLearnProgress = sortInfo.s //设置进度 | |||||
| return@m | |||||
| } | } | ||||
| } | } | ||||
| it.inCoursePackPosition = chineseCoursePack.size | |||||
| chineseCoursePack.add(it) | |||||
| }else{ | |||||
| it.inCoursePackPosition = englishCoursePack.size | |||||
| englishCoursePack.add(it) | |||||
| } | } | ||||
| it.inCoursePackPosition = chineseCoursePack.size | |||||
| chineseCoursePack.add(it) | |||||
| } else { | |||||
| it.inCoursePackPosition = englishCoursePack.size | |||||
| englishCoursePack.add(it) | |||||
| } | } | ||||
| } | } | ||||
| CourseManager.subjectWithCoursePackMap[AppConstants.SUBJECT_ENGLISH] = englishCoursePack.toList() | |||||
| CourseManager.subjectWithCoursePackMap[AppConstants.SUBJECT_CHINESE] = chineseCoursePack.toList() | |||||
| mergeCoursePackResult.postValue(true) | |||||
| } | } | ||||
| CourseManager.subjectWithCoursePackMap[AppConstants.SUBJECT_ENGLISH] = englishCoursePack.toList() | |||||
| CourseManager.subjectWithCoursePackMap[AppConstants.SUBJECT_CHINESE] = chineseCoursePack.toList() | |||||
| println("发送:合并完成! ") | |||||
| handler.sendEmptyMessage(2) | |||||
| /* AppExecutors.mainThread.execute { | |||||
| mergeCoursePackResult.value = true | |||||
| } | |||||
| */ | |||||
| } | } | ||||
| } | } | ||||
| fun copyResource() { | fun copyResource() { | ||||
| AppExecutors.diskIO.run { | AppExecutors.diskIO.run { | ||||
| //复制词典 和 词汇量测试 | //复制词典 和 词汇量测试 | ||||
| LogUtil.e("复制vocabulary.db") | LogUtil.e("复制vocabulary.db") | ||||
| FileUtil.copyAsset("vocabulary.db", vocabulary) | FileUtil.copyAsset("vocabulary.db", vocabulary) | ||||
| } | } | ||||
| AppExecutors.mainThread.execute { | |||||
| allOver.value = true | |||||
| } | |||||
| } | |||||
| } | |||||
| @Throws(IOException::class) | |||||
| private fun unZipFile(f : File, coursePack : CoursePack) { | |||||
| val zipFile = ZipFile(f) | |||||
| val entries = zipFile.entries() | |||||
| while (entries.hasMoreElements()) { | |||||
| val nextEntry = entries.nextElement() | |||||
| val courseId = coursePack.childrenCourses[0].courseId | |||||
| //如果是作文,作文需要检查数据库,同时需要检查其下的图片、视频和关键帧 | |||||
| if (coursePack.subjectId == AppConstants.SUBJECT_CHINESE && coursePack.coursePackType == AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION) { | |||||
| when { | |||||
| nextEntry.name.endsWith(("cover.png")) -> File(FilePathManager.getIconRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/icon") | |||||
| nextEntry.name.endsWith(".mp4.png") -> File(FilePathManager.getMp4PngRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/$courseId/${ | |||||
| nextEntry.name.substringAfterLast("/") | |||||
| }") | |||||
| nextEntry.name.endsWith(".mp4") -> File(FilePathManager.getMp4RootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/$courseId/${ | |||||
| nextEntry.name.substringAfterLast("/") | |||||
| }") | |||||
| nextEntry.name.endsWith(".png") -> File(FilePathManager.getPngRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/$courseId/${ | |||||
| nextEntry.name.substringAfterLast("/") | |||||
| }") | |||||
| nextEntry.name.endsWith(".db") -> File(FilePathManager.getDbRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/$courseId/course.db") | |||||
| else -> null | |||||
| }?.let { file -> | |||||
| //文件不存在 ,复制当前文件进入该文件 | |||||
| if (!file.exists()) { | |||||
| file.parentFile?.let { parentFile -> | |||||
| if (!parentFile.exists()) parentFile.mkdirs() | |||||
| } | |||||
| val zipInputStream = zipFile.getInputStream(nextEntry) | |||||
| val outputStream = BufferedOutputStream(FileOutputStream(file)) | |||||
| val buffer = ByteArray(1024 * 1024 * 5) | |||||
| var count = 0 | |||||
| while ((zipInputStream.read(buffer).also { count = it }) != -1) { | |||||
| outputStream.write(buffer, 0, count) | |||||
| } | |||||
| outputStream.close() | |||||
| } | |||||
| } | |||||
| } else { //其他的项目只需要复制和检查数据库 | |||||
| when { | |||||
| nextEntry.name.endsWith(".db") -> File(FilePathManager.getDbRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/$courseId/course.db") | |||||
| nextEntry.name.endsWith(("cover.png")) -> File(FilePathManager.getIconRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/icon") | |||||
| else -> null | |||||
| }?.let { | |||||
| if (!it.exists()) { | |||||
| it.parentFile?.let { parentFile -> | |||||
| if (!parentFile.exists()) parentFile.mkdirs() | |||||
| } | |||||
| val zipInputStream = zipFile.getInputStream(nextEntry) | |||||
| val outputStream = BufferedOutputStream(FileOutputStream(it)) | |||||
| val buffer = ByteArray(1024 * 1024 * 5) | |||||
| var count = 0 | |||||
| while ((zipInputStream.read(buffer).also { count = it }) != -1) { | |||||
| outputStream.write(buffer, 0, count) | |||||
| } | |||||
| outputStream.flush() | |||||
| outputStream.close() | |||||
| if (nextEntry.name.endsWith(".db")) { | |||||
| coursePack.childrenCourses.forEachIndexed { index, course -> | |||||
| val dest = File(FilePathManager.getDbRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/${course.courseId}/course.db") | |||||
| FileUtil.copyFile(it, dest, false) //复制课程包 | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @Throws(IOException::class) | |||||
| private fun unZipFile_1(f : File, coursePack : CoursePack) { | |||||
| val zipFile = ZipFile(f) | |||||
| val entries = zipFile.entries() | |||||
| while (entries.hasMoreElements()) { | |||||
| val nextEntry = entries.nextElement() | |||||
| val zipInputStream = zipFile.getInputStream(nextEntry) | |||||
| val buffer = ByteArray(1024 * 1024 * 5) | |||||
| var count = 0 | |||||
| val outPut : Array<BufferedOutputStream> = when { | |||||
| nextEntry.name.endsWith(("cover.png")) -> { | |||||
| Array<BufferedOutputStream>(1) { | |||||
| val fileName = File(FilePathManager.getIconRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/icon") | |||||
| if (!fileName.exists()) { | |||||
| fileName.parentFile?.let { parentFile -> | |||||
| if (!parentFile.exists()) parentFile.mkdirs() | |||||
| } | |||||
| } | |||||
| BufferedOutputStream(FileOutputStream(fileName)) | |||||
| } | |||||
| } | |||||
| nextEntry.name.endsWith(".mp4.png") -> { | |||||
| Array<BufferedOutputStream>(1) { | |||||
| val fileName = File(FilePathManager.getMp4PngRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/${coursePack.childrenCourses[it].courseId}/${ | |||||
| nextEntry.name.substringAfterLast("/") | |||||
| }") | |||||
| if (!fileName.exists()) { | |||||
| fileName.parentFile?.let { parentFile -> | |||||
| if (!parentFile.exists()) parentFile.mkdirs() | |||||
| } | |||||
| } | |||||
| BufferedOutputStream(FileOutputStream(fileName)) | |||||
| } | |||||
| } | |||||
| nextEntry.name.endsWith(".mp4") -> { | |||||
| Array<BufferedOutputStream>(1) { | |||||
| val fileName = File(FilePathManager.getMp4RootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/${coursePack.childrenCourses[it].courseId}/${ | |||||
| nextEntry.name.substringAfterLast("/") | |||||
| }") | |||||
| if (!fileName.exists()) { | |||||
| fileName.parentFile?.let { parentFile -> | |||||
| if (!parentFile.exists()) parentFile.mkdirs() | |||||
| } | |||||
| } | |||||
| BufferedOutputStream(FileOutputStream(fileName)) | |||||
| } | |||||
| } | |||||
| nextEntry.name.endsWith(".png") -> { | |||||
| Array<BufferedOutputStream>(1) { | |||||
| val fileName = File(FilePathManager.getPngRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/${coursePack.childrenCourses[it].courseId}/${ | |||||
| nextEntry.name.substringAfterLast("/") | |||||
| }") | |||||
| if (!fileName.exists()) { | |||||
| fileName.parentFile?.let { parentFile -> | |||||
| if (!parentFile.exists()) parentFile.mkdirs() | |||||
| } | |||||
| } | |||||
| BufferedOutputStream(FileOutputStream(fileName)) | |||||
| } | |||||
| } | |||||
| //写入数据库,英语单词类型需要写入三个文件 | |||||
| nextEntry.name.endsWith(".db") -> { | |||||
| Array<BufferedOutputStream>(coursePack.childrenCourses.size) { | |||||
| val fileName = File(FilePathManager.getDbRootPath(), | |||||
| "${coursePack.subjectId}/${coursePack.coursePackId}/${coursePack.childrenCourses[it].courseId}/course.db") //需要写入的文件 | |||||
| if (!fileName.exists()) { | |||||
| fileName.parentFile?.let { parentFile -> | |||||
| if (!parentFile.exists()) parentFile.mkdirs() | |||||
| } | |||||
| } | |||||
| BufferedOutputStream(FileOutputStream(fileName)) | |||||
| } | |||||
| } | |||||
| else -> emptyArray<BufferedOutputStream>() | |||||
| } | |||||
| if (outPut.isNotEmpty()) { | |||||
| while ((zipInputStream.read(buffer).also { count = it }) != -1) { | |||||
| outPut.forEach { | |||||
| it.write(buffer, 0, count) | |||||
| } | |||||
| } | |||||
| outPut.forEach { | |||||
| it.close() | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| companion object{ | companion object{ | ||||
| private val apiService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { | |||||
| val apiService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { | |||||
| HttpUtil.instance.getRetrofitService(ApiService::class.java) | HttpUtil.instance.getRetrofitService(ApiService::class.java) | ||||
| } | } | ||||
| /** | |||||
| * 有参请求 | |||||
| * @param url 路径 | |||||
| * @param body 内容 | |||||
| * @return | |||||
| */ | |||||
| fun <T> postRequest(url : String, body : RequestBody) : Observable<T> { | |||||
| return apiService.postRequest(url,body) | |||||
| } | |||||
| /* *//** | |||||
| * 有参请求 带header | |||||
| * @param url 路径 | |||||
| * @param body 内容 | |||||
| * @return | |||||
| *//* | |||||
| fun postRequestHasToken(url : String?, body : RequestBody) : ResponseBody { | |||||
| // val token : String = SPUtils.getInstance().getString(SPUtils.USER_TOKEN) | |||||
| return apiService.postRequest(url,token,body) | |||||
| }*/ | |||||
| /** | |||||
| * 有参请求 带header | |||||
| * @param url 路径 | |||||
| * @param body 内容 | |||||
| * @return | |||||
| */ | |||||
| fun <T> postRequestHasToken(url : String, body : RequestBody, token : String) : Observable<T> { | |||||
| return apiService.postRequest(url, token, body) | |||||
| } | |||||
| /** | |||||
| * 无参请求 | |||||
| * | |||||
| * @param url 路径 | |||||
| * @return | |||||
| */ | |||||
| fun <T> postRequest(url : String?) : Observable<T> { | |||||
| return apiService.postRequest(url) | |||||
| } | |||||
| /** | |||||
| * 无参请求,带header token | |||||
| * | |||||
| * @param url 路径 | |||||
| * @param token header token | |||||
| * @return | |||||
| */ | |||||
| fun <T> postRequest(url : String, token : String) : Observable<T> { | |||||
| return apiService.postRequest(url, token) | |||||
| } | |||||
| /** | |||||
| * 上传头像 | |||||
| */ | |||||
| fun <T> updateImage(token : String, requestBody : MultipartBody.Part) : Observable<T> { | |||||
| return apiService.updateImage(token, requestBody) | |||||
| } | |||||
| /* *//** | |||||
| * 无参请求,带header token | |||||
| * | |||||
| * @param url 路径 | |||||
| * @param token header token | |||||
| * @return | |||||
| *//* | |||||
| fun postRequest(url : String?, token : String?) : Observable<ResponseBody?>? { | |||||
| return RetrofitClient.getApiService().postRequest(url, token) | |||||
| } | |||||
| *//** | |||||
| * 上传头像 | |||||
| *//* | |||||
| fun updateImage(token : String?, requestBody : Part?) : Observable<ResponseBody?>? { | |||||
| return RetrofitClient.getApiService().updateImage(token, requestBody) | |||||
| } | |||||
| */ | |||||
| } | } | ||||
| } | } |
| <EditText | <EditText | ||||
| android:id="@+id/et_licence" | android:id="@+id/et_licence" | ||||
| android:layout_width="0dp" | android:layout_width="0dp" | ||||
| android:layout_height="wrap_content" | |||||
| android:layout_height="80dp" | |||||
| app:layout_constraintStart_toStartOf="@+id/tv_flag_1" | app:layout_constraintStart_toStartOf="@+id/tv_flag_1" | ||||
| app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
| app:layout_constraintTop_toBottomOf="@+id/tv_flag_1" | app:layout_constraintTop_toBottomOf="@+id/tv_flag_1" | ||||
| android:textColor="@color/main_text_color" | android:textColor="@color/main_text_color" | ||||
| android:layout_marginTop="@dimen/global_spacing" | android:layout_marginTop="@dimen/global_spacing" | ||||
| android:layout_marginEnd="@dimen/global_spacing" | android:layout_marginEnd="@dimen/global_spacing" | ||||
| android:maxLines="1" | |||||
| android:gravity="bottom" | |||||
| android:hint="请填入激活码"/> | android:hint="请填入激活码"/> | ||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||
| <layout 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"> | |||||
| xmlns:tools="http://schemas.android.com/tools" | |||||
| xmlns:app="http://schemas.android.com/apk/res-auto"> | |||||
| <data> | <data> | ||||
| </data> | </data> | ||||
| <androidx.appcompat.widget.LinearLayoutCompat | |||||
| <androidx.constraintlayout.widget.ConstraintLayout | |||||
| android:layout_width="match_parent" | android:layout_width="match_parent" | ||||
| android:layout_height="40dp" | |||||
| android:orientation="horizontal" | |||||
| android:gravity="center_vertical" | |||||
| android:layout_marginBottom="10dp"> | |||||
| android:layout_height="wrap_content" | |||||
| android:minHeight="40dp"> | |||||
| <TextView | <TextView | ||||
| android:id="@+id/tv_name" | android:id="@+id/tv_name" | ||||
| android:layout_width="match_parent" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="wrap_content" | android:layout_height="wrap_content" | ||||
| android:layout_weight="1" | |||||
| android:textColor="@color/main_text_color" | android:textColor="@color/main_text_color" | ||||
| android:textSize="@dimen/normalSize" | android:textSize="@dimen/normalSize" | ||||
| android:layout_marginLeft="@dimen/global_spacing" | |||||
| android:drawablePadding="8dp" | |||||
| tools:text="设置" | |||||
| tools:drawableStart="@drawable/ic_setting"/> | |||||
| tools:text = "设置" | |||||
| app:layout_constraintStart_toStartOf="parent" | |||||
| app:layout_constraintTop_toTopOf="parent" | |||||
| app:layout_constraintBottom_toBottomOf="parent" | |||||
| android:layout_marginStart="@dimen/global_spacing"/> | |||||
| <ImageView | <ImageView | ||||
| android:id="@+id/iv_arrow_right" | android:id="@+id/iv_arrow_right" | ||||
| android:layout_width="16dp" | android:layout_width="16dp" | ||||
| android:layout_height="16dp" | android:layout_height="16dp" | ||||
| android:src="@drawable/ic_arrow_right" | android:src="@drawable/ic_arrow_right" | ||||
| android:layout_marginRight="@dimen/global_spacing" | |||||
| android:layout_gravity="center_vertical"/> | |||||
| android:layout_marginEnd="@dimen/global_spacing" | |||||
| android:layout_gravity="center_vertical" | |||||
| app:layout_constraintEnd_toEndOf="parent" | |||||
| app:layout_constraintTop_toTopOf="parent" | |||||
| app:layout_constraintBottom_toBottomOf="parent"/> | |||||
| <TextView | |||||
| android:id="@+id/tv_more_info" | |||||
| android:layout_width="wrap_content" | |||||
| android:layout_height="wrap_content" | |||||
| app:layout_constraintEnd_toStartOf="@+id/iv_arrow_right" | |||||
| app:layout_constraintTop_toTopOf="parent" | |||||
| app:layout_constraintBottom_toBottomOf="parent" | |||||
| tools:text = "一而三死伍流起吧就一而三死伍流起吧就" | |||||
| android:textColor="@color/red_1" | |||||
| android:maxLines="1" | |||||
| android:ellipsize="end" | |||||
| app:layout_constraintStart_toEndOf="@+id/tv_name" | |||||
| app:layout_constraintHorizontal_bias="1" | |||||
| app:layout_constrainedWidth="true" | |||||
| android:layout_marginStart="@dimen/global_spacing" | |||||
| android:layout_marginEnd="@dimen/global_spacing"/> | |||||
| <View | |||||
| android:layout_width="match_parent" | |||||
| android:layout_height="@dimen/line_height" | |||||
| app:layout_constraintStart_toStartOf="parent" | |||||
| app:layout_constraintEnd_toEndOf="parent" | |||||
| app:layout_constraintBottom_toBottomOf="parent" | |||||
| android:layout_marginStart="@dimen/global_spacing" | |||||
| android:layout_marginEnd="@dimen/global_spacing" | |||||
| android:background="@color/gray_1"/> | |||||
| </androidx.appcompat.widget.LinearLayoutCompat> | |||||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||||
| </layout> | </layout> |
| <string name="activate">激活</string> | <string name="activate">激活</string> | ||||
| <string name="activate_result_title">激活结果</string> | <string name="activate_result_title">激活结果</string> | ||||
| <string name="activate_result_success">激活成功</string> | <string name="activate_result_success">激活成功</string> | ||||
| <string name="activate_result_failed">激活失败</string> | |||||
| </resources> | </resources> |
| //Lottie | //Lottie | ||||
| Lottie : "com.airbnb.android:lottie:5.0.3", | Lottie : "com.airbnb.android:lottie:5.0.3", | ||||
| //Rxjava RxAndroid | //Rxjava RxAndroid | ||||
| RxJava : "io.reactivex.rxjava3:rxjava:3.1.4", | |||||
| RxAndroid: "io.reactivex.rxjava3:rxandroid:3.0.0", | |||||
| // RxJava : "io.reactivex.rxjava3:rxjava:3.1.4", | |||||
| RxAndroid: "io.reactivex.rxjava3:rxandroid:3.0.2", | |||||
| //gson https://github.com/google/gson | //gson https://github.com/google/gson | ||||
| Gson : "com.google.code.gson:gson:2.9.0", | Gson : "com.google.code.gson:gson:2.9.0", | ||||
| //MMKV https://github.com/Tencent/MMKV/wiki/android_tutorial_cn | //MMKV https://github.com/Tencent/MMKV/wiki/android_tutorial_cn | ||||
| RetrofitProtobuf : 'com.squareup.retrofit2:converter-protobuf:2.9.0', | RetrofitProtobuf : 'com.squareup.retrofit2:converter-protobuf:2.9.0', | ||||
| RetrofitScalars : 'com.squareup.retrofit2:converter-scalars:2.9.0', | RetrofitScalars : 'com.squareup.retrofit2:converter-scalars:2.9.0', | ||||
| RetrofitGson : 'com.squareup.retrofit2:converter-gson:2.9.0', | RetrofitGson : 'com.squareup.retrofit2:converter-gson:2.9.0', | ||||
| Rxjava : 'io.reactivex.rxjava3:rxjava:3.1.5' | |||||
| Rxjava : 'io.reactivex.rxjava3:rxjava:3.1.5', | |||||
| Retrofit2Rxjava3Adapter : 'com.squareup.retrofit2:adapter-rxjava3:2.9.0' | |||||
| ] | ] | ||||
| } | } | ||||
| dependencies { | dependencies { | ||||
| // implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs') | // implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs') | ||||
| // implementation 'androidx.legacy:legacy-support-v4:1.0.0' | // implementation 'androidx.legacy:legacy-support-v4:1.0.0' | ||||
| rootProject.ext.dependencies_required.each{ k, v -> implementation v} | rootProject.ext.dependencies_required.each{ k, v -> implementation v} | ||||
| //glide translation | //glide translation | ||||
| api customDependencies.GlideTranslation | api customDependencies.GlideTranslation | ||||
| //RxJava RxAndroid | //RxJava RxAndroid | ||||
| api customDependencies.RxJava | |||||
| api customDependencies.RxAndroid | |||||
| // api customDependencies.RxJava | |||||
| // api customDependencies.RxAndroid | |||||
| coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' | ||||
| //MMKV | //MMKV | ||||
| api customDependencies.MMKV | api customDependencies.MMKV | ||||
| api customDependencies.Okhttp | api customDependencies.Okhttp | ||||
| api customDependencies.OkhttpLoggingIntercepter | api customDependencies.OkhttpLoggingIntercepter | ||||
| api customDependencies.Retrofit | api customDependencies.Retrofit | ||||
| api customDependencies.RetrofitScalars | |||||
| api customDependencies.RetrofitGson | |||||
| api customDependencies.RetrofitProtobuf | |||||
| // api customDependencies.RetrofitScalars | |||||
| // api customDependencies.RetrofitGson | |||||
| // api customDependencies.RetrofitProtobuf | |||||
| api customDependencies.Rxjava | api customDependencies.Rxjava | ||||
| api customDependencies.RxAndroid | |||||
| api customDependencies.Retrofit2Rxjava3Adapter | |||||
| <!-- tools:ignore="ScopedStorage" />--> | <!-- tools:ignore="ScopedStorage" />--> | ||||
| <!--networkSecurityConfig,usesCleartextTraffic 二选一都可以9.0-P以上高版本的http连接,否则连接失败 | |||||
| <!-- networkSecurityConfig,usesCleartextTraffic 二选一都可以9.0-P以上高版本的http连接,否则连接失败 | |||||
| android:networkSecurityConfig="@xml/network_security_config" | android:networkSecurityConfig="@xml/network_security_config" | ||||
| android:usesCleartextTraffic="true" | |||||
| --> | |||||
| <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | |||||
| android:usesCleartextTraffic="true"--> | |||||
| <application | <application | ||||
| android:networkSecurityConfig="@xml/network_security_config"/> | android:networkSecurityConfig="@xml/network_security_config"/> | ||||
| import android.widget.Toast | import android.widget.Toast | ||||
| import com.suliang.common.util.AppGlobals | import com.suliang.common.util.AppGlobals | ||||
| import com.suliang.common.util.file.FileUtil | |||||
| import com.suliang.common.util.thread.AppExecutors | |||||
| import com.suliang.common.util.thread.MainThreadExecutor | import com.suliang.common.util.thread.MainThreadExecutor | ||||
| import okhttp3.* | import okhttp3.* | ||||
| import okhttp3.logging.HttpLoggingInterceptor | import okhttp3.logging.HttpLoggingInterceptor | ||||
| import retrofit2.Retrofit | import retrofit2.Retrofit | ||||
| import retrofit2.converter.gson.GsonConverterFactory | |||||
| import retrofit2.converter.protobuf.ProtoConverterFactory | |||||
| import retrofit2.converter.scalars.ScalarsConverterFactory | |||||
| import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory | |||||
| import java.io.* | import java.io.* | ||||
| import java.util.concurrent.TimeUnit | import java.util.concurrent.TimeUnit | ||||
| .build() | .build() | ||||
| } | } | ||||
| private val retrofit : Retrofit by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { | private val retrofit : Retrofit by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { | ||||
| Retrofit.Builder().baseUrl("") | |||||
| .addConverterFactory(GsonConverterFactory.create()) | |||||
| .addConverterFactory(ScalarsConverterFactory.create()) | |||||
| .addConverterFactory(ProtoConverterFactory.create()) | |||||
| Retrofit.Builder().baseUrl("http://api.offline.xuekaole.com") | |||||
| .client(okHttpClient) | |||||
| // .addConverterFactory(GsonConverterFactory.create()) | |||||
| // .addConverterFactory(ScalarsConverterFactory.create()) | |||||
| // .addConverterFactory(ProtoConverterFactory.create()) | |||||
| .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) | |||||
| .build() | .build() | ||||
| } | } | ||||
| } | } | ||||
| fun getOkhttpClient() : OkHttpClient { | fun getOkhttpClient() : OkHttpClient { | ||||
| return okHttpClient | return okHttpClient | ||||
| } | } | ||||
| /** | |||||
| * 获取 Retrofit Service | |||||
| * @param T IAPiService子类 | |||||
| * @param service | |||||
| * @return | |||||
| */ | |||||
| fun <T : IApiService> getRetrofitService (service : Class<T> ) : T { | fun <T : IApiService> getRetrofitService (service : Class<T> ) : T { | ||||
| return retrofit.create(service) | return retrofit.create(service) | ||||
| } | } | ||||
| /** | /** | ||||
| * 同步请求 | * 同步请求 | ||||
| * @param request 请求request | * @param request 请求request | ||||
| }) | }) | ||||
| } | } | ||||
| /** | |||||
| * 同步下载 | |||||
| * @param urlPath 下载路径 | |||||
| * @param savePathFile 保存地址,带文件地址 | |||||
| * @param downLoadFileListener 下载监听 | |||||
| */ | |||||
| fun downLoadSync(urlPath : String, savePathFile : File, downLoadFileListener : DownLoadFileListener) { | |||||
| if (!NetworkUtil.isConnected()) { | |||||
| try { | |||||
| AppExecutors.mainThread.execute { | |||||
| Toast.makeText(AppGlobals.application, "网络不可用", Toast.LENGTH_SHORT).show() | |||||
| } | |||||
| } catch (e : Exception) { | |||||
| e.printStackTrace() | |||||
| } | |||||
| downLoadFileListener.downFileResult(null) | |||||
| return | |||||
| } | |||||
| //下载 | |||||
| val newCall = okHttpClient.newCall(Request.Builder().url(urlPath).build()) | |||||
| newCall.execute().use { response -> | |||||
| //下载失败 | |||||
| if (!response.isSuccessful) { | |||||
| downLoadFileListener.downFileResult(null) | |||||
| return | |||||
| } | |||||
| //下载成功 | |||||
| var inputStream : InputStream? = null | |||||
| var outputStream : OutputStream? = null | |||||
| response.body?.let { | |||||
| try { | |||||
| val contentLength:Double = it.contentLength().toDouble() //总长度 | |||||
| downLoadFileListener.downFileSize(contentLength.toLong()) //回调总长度 | |||||
| inputStream = it.byteStream() | |||||
| outputStream = FileOutputStream(savePathFile) | |||||
| var readLength : Int = 0 | |||||
| var writeLength = 0f | |||||
| val bytes = ByteArray(1025 * 1025 * 5) | |||||
| var progress = 0 | |||||
| while ((inputStream!!.read(bytes)).also { | |||||
| readLength = it | |||||
| } != -1) { | |||||
| outputStream!!.write(bytes, 0, readLength) | |||||
| writeLength += readLength | |||||
| progress = (writeLength / contentLength * 100).toInt() | |||||
| // AppExecutors.mainThread.execute { | |||||
| // 进度回调给放到主线程中 | |||||
| downLoadFileListener.downFileProgress(progress) //下载百分比 | |||||
| // } | |||||
| } | |||||
| outputStream?.flush() | |||||
| outputStream?.close() | |||||
| downLoadFileListener.downFileResult(savePathFile) | |||||
| } catch (e : Exception) { | |||||
| e.printStackTrace() | |||||
| downLoadFileListener.downFileResult(null) | |||||
| }finally { | |||||
| try { | |||||
| outputStream?.close() | |||||
| inputStream?.close() | |||||
| }catch (e: Exception){ | |||||
| e.printStackTrace() | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * 取消全部请求队列 | * 取消全部请求队列 | ||||
| */ | */ |
| import okhttp3.MultipartBody | import okhttp3.MultipartBody | ||||
| import okhttp3.RequestBody | import okhttp3.RequestBody | ||||
| import okhttp3.ResponseBody | import okhttp3.ResponseBody | ||||
| import retrofit2.Call | |||||
| import retrofit2.Response | |||||
| import retrofit2.http.* | import retrofit2.http.* | ||||
| /** | /** | ||||
| * author: suliang | * author: suliang | ||||
| * 2022/12/7 16:36 | * 2022/12/7 16:36 | ||||
| interface IApiService { | interface IApiService { | ||||
| @POST | |||||
| @Headers("contentType : application/xkl_v2", | |||||
| "accept : application/xkl_v2") | |||||
| fun <T> postRequest(@Url url : String, @Body body: RequestBody) : Observable<T> | |||||
| @GET | |||||
| fun get(@Url url : String, @QueryMap fields : MutableMap<String,Any>) : Observable<Response<ResponseBody>> | |||||
| @FormUrlEncoded | |||||
| @POST | @POST | ||||
| @Headers("Content-Type:application/xkl_v2", "accept:application/xkl_v2") | |||||
| fun <T> postRequest(@Url url : String?, | |||||
| @Header("Authorization") token : String, | |||||
| @Body body : RequestBody) : Observable<T> | |||||
| fun post(@Url url : String,@FieldMap fields:MutableMap<String,Any>) : Observable<Response<ResponseBody>> | |||||
| @FormUrlEncoded | |||||
| @PUT | |||||
| fun put(@Url url : String, @FieldMap fields : Map<String, Any>) : Observable<Response<ResponseBody>> | |||||
| @POST | |||||
| @Headers("accept:application/xkl_v2") | |||||
| fun <T> postRequest(@Url url : String?) : Observable<T> | |||||
| @POST | |||||
| @Headers("accept:application/xkl_v2") | |||||
| fun <T> postRequest(@Url url : String?, @Header("Authorization") token : String) : Observable<T> | |||||
| @FormUrlEncoded | |||||
| @DELETE | |||||
| fun delete(@Url url : String, @FieldMap fields : Map<String, Any>) : Observable<Response<ResponseBody>> | |||||
| @FormUrlEncoded | |||||
| @PATCH | |||||
| fun patch(@Url url : String, @FieldMap fields : Map<String, Any>) : Observable<Response<ResponseBody>> | |||||
| @Streaming | |||||
| @GET | |||||
| fun download(@Url url : String) : Observable<Response<ResponseBody>> | |||||
| /** | /** | ||||
| * 上传头像 | * 上传头像 | ||||
| */ | */ | ||||
| @Multipart | @Multipart | ||||
| @POST("upload/upload") //base路径直接连接成为全路径 | |||||
| @Headers("accept:application/xkl_v2") | |||||
| fun <T> updateImage(@Header("Authorization") token : String, @Part body : MultipartBody.Part) : Observable<T> | |||||
| @POST //base路径直接连接成为全路径 | |||||
| @Headers("accept: application/json; charset=utf-8") | |||||
| fun <T> updateImage(@Url url : String, @Header("Authorization") token : String, @Part body : MultipartBody.Part) : Observable<Response<ResponseBody>> | |||||
| // | |||||
| // @POST | |||||
| // @Headers("Content-Type: application/x-www-form-urlencoded", | |||||
| // "accept: application/json; charset=utf-8") | |||||
| // fun <T> postRequest(@Url url : String, @Body body: RequestBody) : Observable<Any> | |||||
| // | |||||
| // | |||||
| // @POST | |||||
| // @Headers("Content-Type: application/x-www-form-urlencoded", "accept: application/json; charset=utf-8") | |||||
| // fun <T> postRequest(@Url url : String?, | |||||
| // @Header("Authorization") token : String, | |||||
| // @Body body : RequestBody) : Observable<Any> | |||||
| // | |||||
| // | |||||
| // @POST | |||||
| // @Headers("accept: application/json; charset=utf-8") | |||||
| // fun <T> postRequest(@Url url : String?) : Observable<Any> | |||||
| // | |||||
| // @POST | |||||
| // @Headers("accept: application/json; charset=utf-8") | |||||
| // fun <T> postRequest(@Url url : String?, @Header("Authorization") token : String) : Observable<Any> | |||||
| // | |||||
| package com.suliang.common.util.net | package com.suliang.common.util.net | ||||
| import io.reactivex.rxjava3.core.Observable | |||||
| import io.reactivex.rxjava3.core.Observer | import io.reactivex.rxjava3.core.Observer | ||||
| import io.reactivex.rxjava3.disposables.Disposable | import io.reactivex.rxjava3.disposables.Disposable | ||||
| import okhttp3.ResponseBody | import okhttp3.ResponseBody | ||||
| import retrofit2.Response | |||||
| import java.io.IOException | import java.io.IOException | ||||
| import java.nio.ByteBuffer | import java.nio.ByteBuffer | ||||
| * 2022/12/8 10:00 | * 2022/12/8 10:00 | ||||
| * describe : Retrofit + Rxjava Observer | * describe : Retrofit + Rxjava Observer | ||||
| */ | */ | ||||
| abstract class NetObserver<T> : Observer<T>{ | |||||
| abstract class NetObserver<T> : Observer<Response<ResponseBody>> { | |||||
| override fun onSubscribe(d : Disposable) { | override fun onSubscribe(d : Disposable) { | ||||
| } | } | ||||
| override fun onNext(t : T) { | |||||
| override fun onNext(response : Response<ResponseBody>) { | |||||
| if(response.isSuccessful){ | |||||
| val responseBody = response.toString() | |||||
| success(responseBody) | |||||
| }else{ | |||||
| failure("请求失败") | |||||
| } | |||||
| // try { | // try { | ||||
| // val data : ByteArray = responseBody.bytes() | // val data : ByteArray = responseBody.bytes() | ||||
| // val buffer : ByteBuffer = PbZstdUtil.parseHeaders(data) | // val buffer : ByteBuffer = PbZstdUtil.parseHeaders(data) | ||||
| // } catch (e : IOException) { | // } catch (e : IOException) { | ||||
| // e.printStackTrace() | // e.printStackTrace() | ||||
| // } | // } | ||||
| success(t) | |||||
| // success(t) | |||||
| } | } | ||||
| override fun onError(e : Throwable) { | override fun onError(e : Throwable) { | ||||
| failure(e.message) | |||||
| failure(e.message) | |||||
| } | } | ||||
| override fun onComplete() { | override fun onComplete() { | ||||
| * 成功 | * 成功 | ||||
| * @param t 返回的类型 | * @param t 返回的类型 | ||||
| */ | */ | ||||
| abstract fun success(t : T) | |||||
| abstract fun success(t : String) | |||||
| /** | /** | ||||
| * 失败 | * 失败 |