Browse Source

激活、实现课程获取方式、跳过学前测试

master
suliang 2 years ago
parent
commit
bc6daba828
37 changed files with 1013 additions and 476 deletions
  1. 4
    1
      .idea/misc.xml
  2. 9
    1
      README.md
  3. 3
    1
      app/src/main/AndroidManifest.xml
  4. BIN
      app/src/main/assets/20479f95-32bc-44e6-8398-75e1646c84a0.db
  5. BIN
      app/src/main/assets/52d3b7f2-fff2-11eb-9677-7cd30aeb7e40.db
  6. BIN
      app/src/main/assets/6fa25e64-e68d-4fd0-b57d-f20ee0f3b31d.db
  7. BIN
      app/src/main/assets/885b4531-7e69-4cea-b427-2b059586cb81.db
  8. BIN
      app/src/main/assets/b4e29043-960f-4a5d-b90c-8c9e07f80fb6.db
  9. BIN
      app/src/main/assets/be6e9d63-d9aa-4684-b1a6-c6f304b06701.db
  10. BIN
      app/src/main/assets/course-pack.db
  11. BIN
      app/src/main/assets/course_spoken.db
  12. BIN
      app/src/main/assets/d166b57b-21c7-4c58-a78d-748a57941052.zip
  13. 7
    2
      app/src/main/java/com/xkl/cdl/data/AppConstants.kt
  14. 3
    0
      app/src/main/java/com/xkl/cdl/data/bean/course/Lesson.kt
  15. 49
    4
      app/src/main/java/com/xkl/cdl/data/manager/UserInfoManager.kt
  16. 1
    1
      app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt
  17. 8
    0
      app/src/main/java/com/xkl/cdl/dialog/LearnDialog.kt
  18. 3
    0
      app/src/main/java/com/xkl/cdl/module/learn/LearnWordViewModel.kt
  19. 0
    1
      app/src/main/java/com/xkl/cdl/module/m_center_learn/CoursePackMainActivity.kt
  20. 7
    2
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseLessonFragment.kt
  21. 26
    23
      app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragmentViewModel.kt
  22. 15
    0
      app/src/main/java/com/xkl/cdl/module/m_memo/MemoFragmentViewModel.kt
  23. 47
    7
      app/src/main/java/com/xkl/cdl/module/m_my/MyFragment.kt
  24. 29
    12
      app/src/main/java/com/xkl/cdl/module/splash/ActivateActivity.kt
  25. 47
    22
      app/src/main/java/com/xkl/cdl/module/splash/ActivateViewModel.kt
  26. 182
    107
      app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt
  27. 360
    145
      app/src/main/java/com/xkl/cdl/module/splash/SplashViewModel.kt
  28. 1
    79
      app/src/main/java/com/xkl/cdl/net/RequestUtil.kt
  29. 2
    2
      app/src/main/res/layout/activity_activate.xml
  30. 47
    15
      app/src/main/res/layout/item_my_setting.xml
  31. 1
    0
      app/src/main/res/values/strings.xml
  32. 4
    3
      build.gradle
  33. 8
    6
      lib/common/build.gradle
  34. 2
    4
      lib/common/src/main/AndroidManifest.xml
  35. 84
    13
      lib/common/src/main/java/com/suliang/common/util/net/HttpUtil.kt
  36. 49
    20
      lib/common/src/main/java/com/suliang/common/util/net/IApiService.kt
  37. 15
    5
      lib/common/src/main/java/com/suliang/common/util/net/NetObserver.kt

+ 4
- 1
.idea/misc.xml View File

@@ -86,7 +86,7 @@
<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_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_test_detail.xml" value="0.3348694316436252" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_test_score.xml" value="0.31521739130434784" />
@@ -178,6 +178,7 @@
<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_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_discern.xml" value="0.30092592592592593" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_course_literacy.xml" value="0.2972222222222222" />
@@ -204,6 +205,8 @@
<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_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_spoken.xml" value="0.5061538461538462" />
<entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_star.xml" value="0.49538461538461537" />

+ 9
- 1
README.md View File

@@ -121,4 +121,12 @@ CourseMainFragmentViewModel.loadmain()
public native void gameFinished(long var1, long var3, long var5, long var7, long var9) throws Exception;
public native void changeLocker(long var, long var3, long var5, long var7, long var9, boolean var11) throws Exception;
public native void delLearnedCourseID(long var1, long var3, long var5) throws Exception;
public native void destroy();
public native void destroy();

启动:
- 检查uuid 生成uuid
- 检查licence,没有licence 跳转进行设备账号激活
- 激活成功 拉取绑定的课程列表
- 拉取成功后保存本地,拉取失败,使用本地数据,本地没有数据,提示重新拉取,否则无法进入项目
- 下载课程、复制本地数据(词典、测词汇量)
- 进入课程

+ 3
- 1
app/src/main/AndroidManifest.xml View File

@@ -14,7 +14,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.XklLocal"
tools:ignore="LockedOrientationActivity">
android:usesCleartextTraffic="true"
tools:ignore="LockedOrientationActivity"
android:manageSpaceActivity=".module.m_my.CacheClearActivity">
<activity
android:name=".module.splash.ActivateActivity"
android:exported="true" />

BIN
app/src/main/assets/20479f95-32bc-44e6-8398-75e1646c84a0.db View File


BIN
app/src/main/assets/52d3b7f2-fff2-11eb-9677-7cd30aeb7e40.db View File


BIN
app/src/main/assets/6fa25e64-e68d-4fd0-b57d-f20ee0f3b31d.db View File


BIN
app/src/main/assets/885b4531-7e69-4cea-b427-2b059586cb81.db View File


BIN
app/src/main/assets/b4e29043-960f-4a5d-b90c-8c9e07f80fb6.db View File


BIN
app/src/main/assets/be6e9d63-d9aa-4684-b1a6-c6f304b06701.db View File


BIN
app/src/main/assets/course-pack.db View File


BIN
app/src/main/assets/course_spoken.db View File


BIN
app/src/main/assets/d166b57b-21c7-4c58-a78d-748a57941052.zip View File


+ 7
- 2
app/src/main/java/com/xkl/cdl/data/AppConstants.kt View File

@@ -7,8 +7,10 @@ package com.xkl.cdl.data
*/
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
@@ -227,4 +229,7 @@ object AppConstants {
const val DIALOG_AFTER_TOTAL_TEST_AGAIN = 7
/**弹窗动作: 备忘本测试结束查看详情*/
const val DIALOG_MEMO_TEST_DETAIL = 8
/** 每门课程每天复习的最大数 */
const val TODAY_MAX_REVIEW_COUNT = 200
}

+ 3
- 0
app/src/main/java/com/xkl/cdl/data/bean/course/Lesson.kt View File

@@ -37,6 +37,9 @@ data class Lesson(
var learnIsOver = false
/**是否是最后一个课时 */
var lastLesson : Boolean = false
//是否跳过学前测试
var isSkipLessonBeforeTest = false

//总数量
@get:Bindable

+ 49
- 4
app/src/main/java/com/xkl/cdl/data/manager/UserInfoManager.kt View File

@@ -1,5 +1,6 @@
package com.xkl.cdl.data.manager

import com.suliang.common.util.DateUtil
import com.suliang.common.util.SpUtils
import com.suliang.common.util.file.FileUtil
import com.suliang.common.util.thread.AppExecutors
@@ -78,16 +79,60 @@ class UserInfoManager private constructor(){
/**
* 检查绑定的课程信息
*/
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")
}
}

+ 1
- 1
app/src/main/java/com/xkl/cdl/data/manager/db/DBCourseManager.kt View File

@@ -30,7 +30,7 @@ import kotlin.math.abs
*/
object DBCourseManager {
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 COMPOSITION = "XKL_LOCAL_COMPOSITION_DATA_KEY"
private const val LITERACY_PINYING = "XKL_LOCAL_CHINESE_COURSE_KEY"

+ 8
- 0
app/src/main/java/com/xkl/cdl/dialog/LearnDialog.kt View File

@@ -151,11 +151,17 @@ class LearnDialog private constructor() : BaseDialogFragment<DialogLessonLearnBi
tvLessonName.text = learnDialogBean.chapter_lesson_name
tvCountTime.text = learnDialogBean.showTimeCount
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)
//开始测试,进入课时学前测试界面
binding.tvRight.click {
onDialogListener(AppConstants.DIALOG_START_TEST, this@LearnDialog)
}
binding.tvLeft.click {
onDialogListener(AppConstants.DIALOG_START_LEARN,this@LearnDialog)
}
binding.ivClose.click { dismissAllowingStateLoss() }
}
}
@@ -385,6 +391,7 @@ class LearnDialog private constructor() : BaseDialogFragment<DialogLessonLearnBi
tvTitle.text = "恭喜你,完成了知识点测试!"
tvLeft.visibility = View.VISIBLE
tvLeft.text = "再测一次"
vSplit.visibility = View.VISIBLE
tvRight.text = "完成"
}
binding.tvLeft.click { onDialogListener(AppConstants.DIALOG_START_TEST,this) }
@@ -429,6 +436,7 @@ class LearnDialog private constructor() : BaseDialogFragment<DialogLessonLearnBi
tvRight.text = "完成"
tvLeft.visibility = View.VISIBLE
tvLeft.text = "再测一次"
vSplit.visibility = View.VISIBLE
}
initScore()
initNumber()

+ 3
- 0
app/src/main/java/com/xkl/cdl/module/learn/LearnWordViewModel.kt View File

@@ -14,6 +14,7 @@ import com.xkl.cdl.data.bean.SpellItemBean
import com.xkl.cdl.data.bean.intentdata.LearnData
import com.xkl.cdl.data.event.LearnEventData
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.DbControlBase
import com.xkl.cdl.data.repository.DataRepository
@@ -388,6 +389,8 @@ class LearnWordViewModel : LearnBaseViewModel() {
.post(LearnEventData(learnData.lesson.subjectId, learnData.lesson.courseId, AppConstants.DATA_REVIEW).apply {
this.leesonPositionIndex = currentLessonLearnedPosition
})
//保存复习数据
UserInfoManager.instance.putTodayReviewCount(learnData.lesson.subjectId,learnData.lesson.coursePackId,learnData.lesson.courseId,currentLessonLearnedPosition+1)
return
}
//学习发送结果

+ 0
- 1
app/src/main/java/com/xkl/cdl/module/m_center_learn/CoursePackMainActivity.kt View File

@@ -189,7 +189,6 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP
tabSpell.click { binding.viewPager2.currentItem = 1 }
tabVoice.click { binding.viewPager2.currentItem = 2 }
}
}

+ 7
- 2
app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseLessonFragment.kt View File

@@ -318,14 +318,17 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM
private val onLessonClick : (v : View, position : Int, lesson : Lesson) -> Unit = { view, position, entity ->
when (entity.lessonType) {
AppConstants.LESSON_TYPE_WORD -> {
if (entity.beforeTestScore == AppConstants.NOT_DOING) { //课时学前测试,没有做
if (entity.beforeTestScore == AppConstants.NOT_DOING && !entity.isSkipLessonBeforeTest) { //课时学前测试,没有做
//弹窗显示学前测试提示
showLessonBeforeTestStartDialog(entity)
} else if (!entity.learnIsOver) { //当前课时未学完,直接开始学习
entity.isSkipLessonBeforeTest = false
startLearn(entity)
} else if (entity.afterTestScore == AppConstants.NOT_DOING) {
entity.isSkipLessonBeforeTest = false
loadLessonAfterTest(entity,true)
} else { //当前课时学习完成的弹窗
entity.isSkipLessonBeforeTest = false
showLessonAllOverDialog(entity)
}
}
@@ -401,7 +404,9 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM
dialog.dismissAllowingStateLoss()
when (action) {
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)

+ 26
- 23
app/src/main/java/com/xkl/cdl/module/m_center_learn/coursechildren/CourseMainFragmentViewModel.kt View File

@@ -17,6 +17,7 @@ import com.xkl.cdl.data.bean.course.ExamBean
import com.xkl.cdl.data.bean.course.Lesson
import com.xkl.cdl.data.bean.intentdata.LearnData
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.DbControlBase
import com.xkl.cdl.data.repository.DataRepository
@@ -97,36 +98,38 @@ class CourseMainFragmentViewModel(val courseIndex : Int) : BaseViewModel() {
* @param rev Array<String> 需要复习的数据 value 格式 {project_id}_{pack_id}_{course_id}_{chapter_id}_{lesson_id}_{entity_id}_{review_num}_{Y-m-d H:i:s}_{Y-m-d H:i:s}
*/
private fun initReviewData(rev : ProtocolStringList) {
if (!this::reviewDataList.isInitialized) {
reviewDataList = mutableListOf()
}
reviewDataList.clear()
//更新备忘本数据
if (coursePackMainActivityVM.isMemoSource){
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
}
/** 课程包主页上的更多按钮点击是否有效 */

+ 15
- 0
app/src/main/java/com/xkl/cdl/module/m_memo/MemoFragmentViewModel.kt View File

@@ -9,6 +9,7 @@ import com.xkl.cdl.adapter.AdapterCoursePackWithMemo
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.bean.MemoCoursePack
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.main.MainActivityViewModel
import io.reactivex.rxjava3.core.Observable
@@ -87,6 +88,20 @@ class MemoFragmentViewModel : BaseViewModel() {
resultMemoCoursePack.sortBy {
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)
}
return@fromCallable true

+ 47
- 7
app/src/main/java/com/xkl/cdl/module/m_my/MyFragment.kt View File

@@ -1,5 +1,8 @@
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.ViewGroup
import android.widget.TextView
@@ -9,6 +12,7 @@ import com.huantansheng.easyphotos.EasyPhotos
import com.huantansheng.easyphotos.callback.SelectCallback
import com.huantansheng.easyphotos.models.album.entity.Photo
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.BaseRVAdapter
import com.suliang.common.base.fragment.BaseFragmentVM
@@ -22,6 +26,8 @@ import com.xkl.cdl.data.bean.TimeStatisticItem
import com.xkl.cdl.data.manager.UserInfoManager
import com.xkl.cdl.databinding.FragmentMyBinding
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.util.EasyPhotoEngine
import java.io.File
@@ -37,8 +43,13 @@ class MyFragment : BaseFragmentVM<FragmentMyBinding, MyViewModel>() {
}
private val rvData = mutableListOf<MyFragment.Item>(
// 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 {
@@ -116,14 +127,42 @@ class MyFragment : BaseFragmentVM<FragmentMyBinding, MyViewModel>() {
(holder.binding as ItemMySettingBinding).run {
getItem(position).let {
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
//到期时间
when(position){
0 -> {
tvMoreInfo.text = UserInfoManager.instance.getExpirationTime()
tvMoreInfo.visibility = View.VISIBLE
}
else -> tvMoreInfo.visibility = View.GONE
}
}
root.click {
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) //关于我们
}
}
}
@@ -174,5 +213,6 @@ class MyFragment : BaseFragmentVM<FragmentMyBinding, MyViewModel>() {
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)
}

+ 29
- 12
app/src/main/java/com/xkl/cdl/module/splash/ActivateActivity.kt View File

@@ -2,24 +2,30 @@ package com.xkl.cdl.module.splash

import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.ViewModelProvider
import com.suliang.common.base.activity.BaseActivityVM
import com.suliang.common.base.activity.ToastEvent
import com.suliang.common.extension.click
import com.suliang.common.util.StringUtil
import com.xkl.cdl.R
import com.xkl.cdl.databinding.ActivityActivateBinding
import com.xkl.cdl.dialog.CommonDialog
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?) {
binding.titleBar.onBackClick = {
setResult(RESULT_CANCELED)
finish()
}
binding.etLicence.addTextChangedListener {
binding.btActive.isEnabled = !it.isNullOrEmpty() && !it.isNullOrBlank() //不为空、空格 则激活按钮可用
}
@@ -29,18 +35,29 @@ class ActivateActivity : BaseActivityVM<ActivityActivateBinding,ActivateViewMode
}
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 {
onCommonDialogButtonClickListener = { dialog : CommonDialog, _ ->
onCommonDialogButtonClickListener = { dialog : CommonDialog, _ ->
dialog.dismissAllowingStateLoss()
if (requestResult) { //激活成功、关闭界面
setResult(RESULT_OK)
finish() //关闭界面
}
}

+ 47
- 22
app/src/main/java/com/xkl/cdl/module/splash/ActivateViewModel.kt View File

@@ -1,7 +1,13 @@
package com.xkl.cdl.module.splash

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.util.DateUtil
import com.suliang.common.util.net.HttpUtil
import com.suliang.common.util.net.NetObserver
import com.suliang.common.util.thread.AppExecutors
import com.xkl.cdl.data.AppConstants
@@ -9,8 +15,11 @@ import com.xkl.cdl.data.manager.UserInfoManager
import com.xkl.cdl.net.RequestUtil
import io.reactivex.rxjava3.disposables.Disposable
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
@@ -23,6 +32,7 @@ class ActivateViewModel : BaseViewModel() {
val activeMutable = MutableLiveData<Boolean>()
var errorRequestMsg : String? = null
var expirationTime : String = ""
/**
* 激活
@@ -32,26 +42,41 @@ class ActivateViewModel : BaseViewModel() {
showHideLoading(true)
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)
}
}
})
}
}

+ 182
- 107
app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt View File

@@ -1,29 +1,51 @@
package com.xkl.cdl.module.splash

import android.annotation.SuppressLint
import android.content.Intent
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 appApi.AppApi
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.file.FileUtil
import com.suliang.common.util.thread.AppExecutors
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.data.manager.FilePathManager
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.dialog.CommonDialog
import com.xkl.cdl.dialog.CommonDialogBean
import com.xkl.cdl.module.XKLApplication
import com.xkl.cdl.module.main.MainActivity
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.system.exitProcess

@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?) {
if (!isTaskRoot) {
@@ -35,132 +57,185 @@ class SplashActivity : BaseActivityVM<ActivitySplashBinding,SplashViewModel>() {
}
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() {
//检测唯一码、检测激活码
val uuid = UserInfoManager.instance.getUuid()
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)
//跳转激活,绑定激活码
startActivity(ActivateActivity::class.java)
Handler(Looper.getMainLooper()).postDelayed({
requestDataLauncher.launch(Intent(this, ActivateActivity::class.java))
}, 1000)
return
}
//检测激活码是否存在
if (UserInfoManager.instance.getLicence().isEmpty()) {
//激活码不存在,跳转到激活界面
startActivity(ActivateActivity::class.java)
//跳转激活,绑定激活码
Handler(Looper.getMainLooper()).postDelayed({
requestDataLauncher.launch(Intent(this, ActivateActivity::class.java))
}, 1000)
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()
}

+ 360
- 145
app/src/main/java/com/xkl/cdl/module/splash/SplashViewModel.kt View File

@@ -1,26 +1,31 @@
package com.xkl.cdl.module.splash

import android.os.Handler
import androidx.lifecycle.MutableLiveData
import com.suliang.common.base.viewmodel.BaseViewModel
import com.suliang.common.util.LogUtil
import com.suliang.common.util.file.FileUtil
import com.suliang.common.util.net.DownLoadFileListener
import com.suliang.common.util.net.HttpUtil
import com.suliang.common.util.net.NetObserver
import com.suliang.common.util.thread.AppExecutors
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.manager.CourseManager
import com.xkl.cdl.data.manager.FilePathManager
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.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.zip.ZipInputStream
import java.io.IOException
import java.util.zip.ZipFile

/**
* author: suliang
@@ -29,14 +34,20 @@ import java.util.zip.ZipInputStream
*/
class SplashViewModel : BaseViewModel() {
lateinit var handler : Handler
//账号是否过期
var isExpirationMutableLiveData = MutableLiveData<Boolean>()
//绑定课程的结果
var bindCoursePackList : List<CoursePack>? = null
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()
@@ -49,6 +60,14 @@ class SplashViewModel : BaseViewModel() {
/**
* 拉取绑定的课程
* const (
* NotActive = 20001 //未激活
* OutOfExpiry = 20002 //已过期
* InvalidData = 20003 //非法数据
* RepeatActivation = 20004 //重复激活
* )
* 400 参数错误
* 500 服务器错误
* @return
*/
fun loadBindCourse() {
@@ -56,44 +75,126 @@ class SplashViewModel : BaseViewModel() {
val deviceId = UserInfoManager.instance.getUuid()
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() {
//需要下载的数量
var needCount = 0
AppExecutors.io.execute {
//需要下载的数量
var needCount = 0
//判断该课程的数据的数据包是否存在
bindCoursePackList?.forEach { coursePackInfo : CoursePack ->
var isAdd = false
//课程id 判断数据库是否存在
coursePackInfo.childrenCourses.forEach { courseId ->
val courseID = coursePackInfo.childrenCourses[0]
coursePackInfo.childrenCourses.forEach { course_ ->
//数据库不存在
val dbFile = File(FilePathManager.getDbRootPath(),
"${coursePackInfo.subjectId}/${coursePackInfo.coursePackId}/$courseID/course.db")
"${coursePackInfo.subjectId}/${coursePackInfo.coursePackId}/${course_.courseId}/course.db")
if (!dbFile.exists() && !isAdd) {
coursePackInfo.isDown = false
needCount++
@@ -109,23 +210,23 @@ class SplashViewModel : BaseViewModel() {
val lock : String = ""
//默认下载成功,当有false时,说明下载有失败
var downLoadSuccess = true
println("开始下载")
//下载课程
bindCoursePackList?.forEach { coursePackBaseInfo ->
if (!coursePackBaseInfo.isDown) {
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 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?) {
@@ -135,131 +236,71 @@ class SplashViewModel : BaseViewModel() {
downLoadSuccess = false
}
}
println("${coursePackBaseInfo.coursePackName} : 下载成功")
//下载成功,解压到对应的文件
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() {
AppExecutors.diskIO.run {
//复制词典 和 词汇量测试
@@ -273,6 +314,180 @@ class SplashViewModel : BaseViewModel() {
LogUtil.e("复制vocabulary.db")
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()
}
}
}
}
}

+ 1
- 79
app/src/main/java/com/xkl/cdl/net/RequestUtil.kt View File

@@ -16,87 +16,9 @@ class RequestUtil {
companion object{
private val apiService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
val apiService by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
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)
}
*/
}
}

+ 2
- 2
app/src/main/res/layout/activity_activate.xml View File

@@ -32,7 +32,7 @@
<EditText
android:id="@+id/et_licence"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="80dp"
app:layout_constraintStart_toStartOf="@+id/tv_flag_1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_flag_1"
@@ -40,7 +40,7 @@
android:textColor="@color/main_text_color"
android:layout_marginTop="@dimen/global_spacing"
android:layout_marginEnd="@dimen/global_spacing"
android:maxLines="1"
android:gravity="bottom"
android:hint="请填入激活码"/>



+ 47
- 15
app/src/main/res/layout/item_my_setting.xml View File

@@ -1,37 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>

</data>

<androidx.appcompat.widget.LinearLayoutCompat

<androidx.constraintlayout.widget.ConstraintLayout
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
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/main_text_color"
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
android:id="@+id/iv_arrow_right"
android:layout_width="16dp"
android:layout_height="16dp"
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>

+ 1
- 0
app/src/main/res/values/strings.xml View File

@@ -115,5 +115,6 @@
<string name="activate">激活</string>
<string name="activate_result_title">激活结果</string>
<string name="activate_result_success">激活成功</string>
<string name="activate_result_failed">激活失败</string>

</resources>

+ 4
- 3
build.gradle View File

@@ -85,8 +85,8 @@ ext {
//Lottie
Lottie : "com.airbnb.android:lottie:5.0.3",
//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 : "com.google.code.gson:gson:2.9.0",
//MMKV https://github.com/Tencent/MMKV/wiki/android_tutorial_cn
@@ -129,7 +129,8 @@ ext {
RetrofitProtobuf : 'com.squareup.retrofit2:converter-protobuf:2.9.0',
RetrofitScalars : 'com.squareup.retrofit2:converter-scalars: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'
]



+ 8
- 6
lib/common/build.gradle View File

@@ -46,6 +46,7 @@ android {
}

dependencies {

// implementation fileTree(include: ['*.jar',"*.aar"], dir: 'libs')
// implementation 'androidx.legacy:legacy-support-v4:1.0.0'
rootProject.ext.dependencies_required.each{ k, v -> implementation v}
@@ -64,8 +65,8 @@ dependencies {
//glide translation
api customDependencies.GlideTranslation
//RxJava RxAndroid
api customDependencies.RxJava
api customDependencies.RxAndroid
// api customDependencies.RxJava
// api customDependencies.RxAndroid
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
//MMKV
api customDependencies.MMKV
@@ -75,11 +76,12 @@ dependencies {
api customDependencies.Okhttp
api customDependencies.OkhttpLoggingIntercepter
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.RxAndroid
api customDependencies.Retrofit2Rxjava3Adapter




+ 2
- 4
lib/common/src/main/AndroidManifest.xml View File

@@ -12,11 +12,9 @@
<!-- tools:ignore="ScopedStorage" />-->


<!--networkSecurityConfig,usesCleartextTraffic 二选一都可以9.0-P以上高版本的http连接,否则连接失败
<!-- networkSecurityConfig,usesCleartextTraffic 二选一都可以9.0-P以上高版本的http连接,否则连接失败
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
android:usesCleartextTraffic="true"-->
<application
android:networkSecurityConfig="@xml/network_security_config"/>


+ 84
- 13
lib/common/src/main/java/com/suliang/common/util/net/HttpUtil.kt View File

@@ -2,14 +2,12 @@ package com.suliang.common.util.net

import android.widget.Toast
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 okhttp3.*
import okhttp3.logging.HttpLoggingInterceptor
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.util.concurrent.TimeUnit

@@ -33,29 +31,32 @@ class HttpUtil private constructor() {
.build()
}
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()
}
}
fun getOkhttpClient() : OkHttpClient {
return okHttpClient
}
/**
* 获取 Retrofit Service
* @param T IAPiService子类
* @param service
* @return
*/
fun <T : IApiService> getRetrofitService (service : Class<T> ) : T {
return retrofit.create(service)
}
/**
* 同步请求
* @param request 请求request
@@ -163,6 +164,76 @@ class HttpUtil private constructor() {
})
}
/**
* 同步下载
* @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()
}
}
}
}
}
/**
* 取消全部请求队列
*/

+ 49
- 20
lib/common/src/main/java/com/suliang/common/util/net/IApiService.kt View File

@@ -4,9 +4,10 @@ import io.reactivex.rxjava3.core.Observable
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.*


/**
* author: suliang
* 2022/12/7 16:36
@@ -15,37 +16,65 @@ import retrofit2.http.*
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
@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
@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>
//


+ 15
- 5
lib/common/src/main/java/com/suliang/common/util/net/NetObserver.kt View File

@@ -1,8 +1,10 @@
package com.suliang.common.util.net

import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Observer
import io.reactivex.rxjava3.disposables.Disposable
import okhttp3.ResponseBody
import retrofit2.Response
import java.io.IOException
import java.nio.ByteBuffer

@@ -11,13 +13,21 @@ import java.nio.ByteBuffer
* 2022/12/8 10:00
* describe : Retrofit + Rxjava Observer
*/
abstract class NetObserver<T> : Observer<T>{
abstract class NetObserver<T> : Observer<Response<ResponseBody>> {
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 {
// val data : ByteArray = responseBody.bytes()
// val buffer : ByteBuffer = PbZstdUtil.parseHeaders(data)
@@ -42,12 +52,12 @@ import java.nio.ByteBuffer
// } catch (e : IOException) {
// e.printStackTrace()
// }
success(t)
// success(t)
}
override fun onError(e : Throwable) {
failure(e.message)
failure(e.message)
}
override fun onComplete() {
@@ -59,7 +69,7 @@ import java.nio.ByteBuffer
* 成功
* @param t 返回的类型
*/
abstract fun success(t : T)
abstract fun success(t : String)
/**
* 失败

Loading…
Cancel
Save