@@ -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" /> |
@@ -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 跳转进行设备账号激活 | |||
- 激活成功 拉取绑定的课程列表 | |||
- 拉取成功后保存本地,拉取失败,使用本地数据,本地没有数据,提示重新拉取,否则无法进入项目 | |||
- 下载课程、复制本地数据(词典、测词汇量) | |||
- 进入课程 |
@@ -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" /> |
@@ -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 | |||
} |
@@ -37,6 +37,9 @@ data class Lesson( | |||
var learnIsOver = false | |||
/**是否是最后一个课时 */ | |||
var lastLesson : Boolean = false | |||
//是否跳过学前测试 | |||
var isSkipLessonBeforeTest = false | |||
//总数量 | |||
@get:Bindable |
@@ -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") | |||
} | |||
} |
@@ -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" |
@@ -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() |
@@ -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 | |||
} | |||
//学习发送结果 |
@@ -189,7 +189,6 @@ class CoursePackMainActivity : BaseActivityVM<ActivityCourseMainBinding, CourseP | |||
tabSpell.click { binding.viewPager2.currentItem = 1 } | |||
tabVoice.click { binding.viewPager2.currentItem = 2 } | |||
} | |||
} | |||
@@ -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) |
@@ -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 | |||
} | |||
/** 课程包主页上的更多按钮点击是否有效 */ |
@@ -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 |
@@ -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) | |||
} |
@@ -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() //关闭界面 | |||
} | |||
} |
@@ -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) | |||
} | |||
} | |||
}) | |||
} | |||
} |
@@ -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() | |||
} | |||
@@ -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() | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -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) | |||
} | |||
*/ | |||
} | |||
} |
@@ -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="请填入激活码"/> | |||
@@ -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> |
@@ -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> |
@@ -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' | |||
] | |||
@@ -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 | |||
@@ -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"/> | |||
@@ -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() | |||
} | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* 取消全部请求队列 | |||
*/ |
@@ -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> | |||
// | |||
@@ -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) | |||
/** | |||
* 失败 |