| @@ -14,10 +14,12 @@ | |||
| <option value="$PROJECT_DIR$/app" /> | |||
| <option value="$PROJECT_DIR$/lib" /> | |||
| <option value="$PROJECT_DIR$/lib/common" /> | |||
| <option value="$PROJECT_DIR$/videoplayer" /> | |||
| </set> | |||
| </option> | |||
| <option name="resolveModulePerSourceSet" value="false" /> | |||
| </GradleProjectSettings> | |||
| </option> | |||
| <option name="offlineMode" value="true" /> | |||
| </component> | |||
| </project> | |||
| @@ -35,17 +35,20 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_toplr_24_white.xml" value="0.5061538461538462" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_rounder_toplr_8_white.xml" value="0.5010416666666667" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_switch_thumb.xml" value="0.14074074074074075" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/shape_switch_track.xml" value="0.26851851851851855" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/spoken_autoplay_btn_text_color_.xml" value="0.5140625" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/switch_thumb_selector.xml" value="0.3768518518518518" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/switch_track_selector.xml" value="0.3768518518518518" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/test_error_state_button_bg.xml" value="0.45740740740740743" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.30520833333333336" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/drawable/video_tab_bg.xml" value="0.26851851851851855" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout-v23/include_main_learn_center_course_type_title.xml" value="0.4963768115942029" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_course_main.xml" value="0.33" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_exam_learn_spell.xml" value="0.47690217391304346" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_base.xml" value="0.4979166666666667" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_creading.xml" value="0.5" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_ctask.xml" value="0.23353596757852077" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_cvideo.xml" value="0.22407407407407406" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_exam.xml" value="0.25" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_exam_word.xml" value="0.33" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_learn_spell.xml" value="0.47690217391304346" /> | |||
| @@ -91,6 +94,7 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_historical_route.xml" value="0.4859375" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_spell_single_word.xml" value="0.23632218844984804" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_task_image.xml" value="0.23353596757852077" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_video_adapter.xml" value="0.67" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_course_progress.xml" value="0.25" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/main_item_coursepack.xml" value="0.43500866551126516" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/merge_recyclerview_smart_refresh_layout.xml" value="0.34427083333333336" /> | |||
| @@ -107,6 +111,8 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_my.xml" value="0.44166666666666665" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_service.xml" value="0.21574074074074073" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_nav_statistics.xml" value="0.44166666666666665" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_note.xml" value="0.3567708333333333" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_note_del.xml" value="0.3567708333333333" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play.xml" value="0.503125" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_pause.xml" value="0.503125" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/svg/drawable/ic_play_stop.xml" value="0.4973958333333333" /> | |||
| @@ -124,6 +130,11 @@ | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/loadding_fragment.xml" value="0.4817708333333333" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/loading_fragment.xml" value="0.4979166666666667" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/lib/common/src/main/res/layout/public_title_bar.xml" value="0.33" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/pine_player_definition_recycler_view.xml" value="0.10277777777777777" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/pine_player_item_definition_select_in_player.xml" value="0.10277777777777777" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/pine_player_media_controller.xml" value="0.23454913880445796" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/pine_player_media_controller_full.xml" value="0.5" /> | |||
| <entry key="..\:/Work/XKL/XKL/XklLocal/videoplayer/src/main/res/layout/video_popup.xml" value="0.67" /> | |||
| <entry key="..\:/Work/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.22" /> | |||
| <entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_fullscreen.xml" value="0.10144927536231885" /> | |||
| <entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.1" /> | |||
| @@ -53,4 +53,6 @@ DialogFragment原理 | |||
| BottomSheetDialog 固定高度和原理 | |||
| BottomSheetDialog中使用TextView滑动的冲突? | |||
| Behavior | |||
| MMKV实现和保存原理 | |||
| MMKV实现和保存原理 | |||
| canvas.translate | |||
| 视频播放器 | |||
| @@ -48,6 +48,13 @@ image包: 实现了图片加载的封装 | |||
| 学前总测试 :不让跳过,必须测,用于进行界面判断 和 统计的判断 | |||
| 课时学前测试: 不让跳过,必须测,不然对统计和判断有影响 | |||
| 作文如何记录每个课时是否学习完成: | |||
| 1 视频 : 1)保存播放的时间点 2)保存有效和总时间 3)保存进度点( 学习完成entityId设置为负值,未学习完成为正值) | |||
| 2 知识点速记 : 和英语课时一样处理 | |||
| 3 知识点测试:添加一个单词作为进度点, 学习完成有进度,否则没有进度 | |||
| 4 课堂练习: 设置了进度点(为当前显示的entityId) | |||
| lesson的learnedIndex 为当前位置,但在学习的时候,需要对当前位置进行-1,第一次学习,初始时,必须为0 | |||
| 学习完成entityId设置为负值,未学习完成为正值 | |||
| @@ -72,6 +72,7 @@ dependencies { | |||
| implementation fileTree(include: ['*.jar', "*.aar"], dir: 'libs') | |||
| // implementation 'androidx.legacy:legacy-support-v4:1.0.0' | |||
| implementation project(path: ':lib:common') | |||
| implementation project(path: ':videoplayer') | |||
| // implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' | |||
| // implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' | |||
| // implementation 'androidx.appcompat:appcompat:1.2.0' | |||
| @@ -20,7 +20,11 @@ | |||
| android:exported="true" /> | |||
| <activity | |||
| android:name=".module.learn.LearnCVideoActivity" | |||
| android:exported="true" /> | |||
| android:exported="true" | |||
| android:theme="@style/Theme.videoTheme" | |||
| android:configChanges="orientation|keyboardHidden|screenSize|locale" | |||
| android:screenOrientation="portrait" | |||
| android:windowSoftInputMode="adjustPan|stateHidden"/> | |||
| <activity | |||
| android:name=".module.learn.LearnExamActivity" | |||
| android:exported="true" /> | |||
| @@ -6,7 +6,9 @@ import com.suliang.common.base.adapter.BaseAdapterViewHolder | |||
| import com.suliang.common.base.adapter.BaseRVAdapterVM | |||
| import com.suliang.common.extension.click | |||
| import com.suliang.common.util.image.ImageLoader | |||
| import com.suliang.common.util.os.ScreenUtil | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.databinding.ItemTaskImageBinding | |||
| import com.xkl.cdl.module.learn.LearnCTaskViewModel | |||
| import java.io.File | |||
| @@ -22,7 +24,12 @@ class AdapterImageTask(viewModel : LearnCTaskViewModel) : BaseRVAdapterVM<File, | |||
| } | |||
| override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) { | |||
| ImageLoader.loadImage(holder.binding.root as ImageView,getItem(position)) | |||
| val binding = holder.binding as ItemTaskImageBinding | |||
| binding.ivPhoto.layoutParams.height = ScreenUtil.getScreenWidth() / 3 - ScreenUtil.dp2px(36f).toInt() | |||
| ImageLoader.loadImage(binding.ivPhoto,getItem(position)) | |||
| holder.binding.root.click { | |||
| if (onItemClickIsInitialized()){ | |||
| onItemClick.invoke(it,position,getItem(position)) | |||
| @@ -0,0 +1,66 @@ | |||
| package com.xkl.cdl.adapter | |||
| import android.view.ViewGroup | |||
| import com.suliang.common.base.adapter.BaseAdapterViewHolder | |||
| import com.suliang.common.base.adapter.BaseRVAdapterVM | |||
| import com.suliang.common.extension.click | |||
| import com.suliang.common.util.DrawableUti | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.data.bean.VideoBean | |||
| import com.xkl.cdl.databinding.ItemVideoAdapterBinding | |||
| import com.xkl.cdl.module.learn.LearnCVideoViewModel | |||
| /** | |||
| * author suliang | |||
| * create 2021/4/9 16:44 | |||
| * Describe: 视频适配器 | |||
| */ | |||
| class AdapterVideo(viewmodel : LearnCVideoViewModel) : | |||
| BaseRVAdapterVM<VideoBean.VideoBlackBoard, LearnCVideoViewModel>(viewmodel) { | |||
| override fun getItemCount() : Int { | |||
| return when (vm.showTab) { | |||
| 1 -> vm.mCollectVideoBlackBoradList.size //笔记 | |||
| else -> vm.mVideoBean?.getmVideoBlackBoard()?.size ?: super.getItemCount() //板书 | |||
| } | |||
| } | |||
| override fun getItem(position : Int) : VideoBean.VideoBlackBoard { | |||
| return when (vm.showTab) { | |||
| 1 -> vm.mCollectVideoBlackBoradList.get(position) //笔记 | |||
| else -> vm.mVideoBean?.getmVideoBlackBoard()?.get(position) ?: super.getItem(position) //板书 | |||
| } | |||
| } | |||
| override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder { | |||
| return BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_video_adapter)) | |||
| } | |||
| override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) { | |||
| val value = "${position + 1}.${getItem(position).content}" | |||
| (holder.binding as ItemVideoAdapterBinding).run { | |||
| tvValue.text = value | |||
| when (vm.showTab) { | |||
| 0 -> { | |||
| tvValue.setCompoundDrawablesWithIntrinsicBounds(when (getItem(position).collectId) { | |||
| 0L -> DrawableUti.changeSvgColor(context.resources, | |||
| R.drawable.ic_note, | |||
| R.color.gray_2) //未收藏 | |||
| else -> DrawableUti.changeSvgColor(context.resources, | |||
| R.drawable.ic_note, | |||
| R.color.red_2) //已收藏 | |||
| }, null, null, null) | |||
| } | |||
| else -> tvValue.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_note_del, 0, 0, 0) //笔记 | |||
| } | |||
| } | |||
| holder.binding.root.click { | |||
| if (onItemClickIsInitialized()) { | |||
| onItemClick.invoke(it, position, getItem(position)) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,109 @@ | |||
| package com.xkl.cdl.data.bean; | |||
| import com.xkl.videoplayer.bean.VideoAnchor; | |||
| import java.io.Serializable; | |||
| import java.util.List; | |||
| /** | |||
| * author suliang | |||
| * create 2021/3/26 15:43 | |||
| * Describe: 视频数据表模型 | |||
| */ | |||
| public class VideoBean implements Serializable { | |||
| private long videoId; | |||
| private String title ; | |||
| private String url ; // url为全链接 | |||
| private long duration ; //总秒数 | |||
| private List<VideoAnchor> mVideoAnchorList ; //视频锚点 | |||
| private List<VideoBean.VideoBlackBoard> mVideoBlackBoard ; //视频板书 | |||
| public long getVideoId() { | |||
| return videoId; | |||
| } | |||
| public void setVideoId(long videoId) { | |||
| this.videoId = videoId; | |||
| } | |||
| public String getTitle() { | |||
| return title; | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title = title; | |||
| } | |||
| public String getUrl() { | |||
| return url; | |||
| } | |||
| public void setUrl(String url) { | |||
| this.url = url; | |||
| } | |||
| public long getDuration() { | |||
| return duration; | |||
| } | |||
| public void setDuration(long duration) { | |||
| this.duration = duration; | |||
| } | |||
| public List<VideoAnchor> getmVideoAnchorList() { | |||
| return mVideoAnchorList; | |||
| } | |||
| public void setmVideoAnchorList(List<VideoAnchor> mVideoAnchorList) { | |||
| this.mVideoAnchorList = mVideoAnchorList; | |||
| } | |||
| public List<VideoBlackBoard> getmVideoBlackBoard() { | |||
| return mVideoBlackBoard; | |||
| } | |||
| public void setmVideoBlackBoard(List<VideoBlackBoard> mVideoBlackBoard) { | |||
| this.mVideoBlackBoard = mVideoBlackBoard; | |||
| } | |||
| /** 视频板书 */ | |||
| public static class VideoBlackBoard implements Serializable{ | |||
| private long videoBlackboardId; | |||
| private String content ; | |||
| private int sort ; | |||
| private long collectId ; //收藏id,是否被收藏 | |||
| public long getVideoBlackboardId() { | |||
| return videoBlackboardId; | |||
| } | |||
| public void setVideoBlackboardId(long videoBlackboardId) { | |||
| this.videoBlackboardId = videoBlackboardId; | |||
| } | |||
| public String getContent() { | |||
| return content; | |||
| } | |||
| public void setContent(String content) { | |||
| this.content = content; | |||
| } | |||
| public int getSort() { | |||
| return sort; | |||
| } | |||
| public void setSort(int sort) { | |||
| this.sort = sort; | |||
| } | |||
| public long getCollectId() { | |||
| return collectId; | |||
| } | |||
| public void setCollectId(long collectId) { | |||
| this.collectId = collectId; | |||
| } | |||
| } | |||
| } | |||
| @@ -17,7 +17,7 @@ class CourseDetail:Serializable{ | |||
| var after: HashMap<String, Double> = hashMapOf() //章节学后测试成绩,key=>{chapter_id}_{lesson_id} value=>成绩 | |||
| var right = hashMapOf<String, Int>() //正确条目数,key=>{chapter_id}_{lesson_id} value=>条目数量 | |||
| var wrong: HashMap<String, Int> = hashMapOf() //错误条目数,key=>{chapter_id}_{lesson_id} value=>条目数量 | |||
| var lesson_learn_point: HashMap<String, Long> = hashMapOf() //章节学习点 key=>{chapter_id}_{lesson_id} value=>{entity_id) | |||
| var lesson_learn_point: HashMap<String, Long> = hashMapOf() //课时学习点 key=>{chapter_id}_{lesson_id} value=>{entity_id) | |||
| var exam_w_r_list: HashMap<String, Boolean> = hashMapOf() //课程/课时学前测试正确错误列表 key=> {chapter_id}_{lesson_id}_{entity_id} value=>正确 true;错误 false | |||
| var course_learn_point: String = "" //课程学习进度点 {chapter_id}_{lesson_id}_{entity_id} | |||
| @@ -27,7 +27,7 @@ data class Lesson( | |||
| var lessonPositionInList : Int = 0 | |||
| /** 该课时的总数据 或者作文关联的数据,如视频关联的内容*/ | |||
| var wordIds = mutableListOf<Long>() | |||
| /** 学习进度位置,为已学下标的位置,在作文课堂练习时,学习取当前条,其他取下一条*/ | |||
| /** 学习进度位置,为已学下标的位置,在作文课堂练习时,初始值为0开始的下标,学习时已自动-1,取当前下标的值*/ | |||
| var learnedIndex: Int = -1 | |||
| /**课时学前测试成绩 */ | |||
| var beforeTestScore = AppConstants.NOT_DOING | |||
| @@ -63,7 +63,7 @@ data class Lesson( | |||
| //作文视频使用 | |||
| /** 视频播放时长 */ | |||
| var videoPlayTime : Int = 0 | |||
| var videoPlayTime : Long = 0 | |||
| /** 视频总时长 */ | |||
| var videoTotalTime : Int = 0 | |||
| @@ -51,11 +51,13 @@ object BindingAdapter { | |||
| @JvmStatic | |||
| fun loadVectorDrawable(view:TextView,svgColor:Int){ | |||
| view.compoundDrawables.forEachIndexed{ index: Int, drawable: Drawable? -> | |||
| when(index){ | |||
| 0 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(DrawableUti.tintDrawable(it,svgColor),null,null,null) } | |||
| 1 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(null,DrawableUti.tintDrawable(it,svgColor),null,null) } | |||
| 2 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(null,null,DrawableUti.tintDrawable(it,svgColor),null) } | |||
| 3 -> drawable?.let { view.setCompoundDrawablesWithIntrinsicBounds(null,null,null,DrawableUti.tintDrawable(it,svgColor),) } | |||
| drawable?.let { | |||
| when(index){ | |||
| 0 -> view.setCompoundDrawablesWithIntrinsicBounds(DrawableUti.tintDrawable(it,svgColor),null,null,null) | |||
| 1 -> view.setCompoundDrawablesWithIntrinsicBounds(null,DrawableUti.tintDrawable(it,svgColor),null,null) | |||
| 2 -> view.setCompoundDrawablesWithIntrinsicBounds(null,null,DrawableUti.tintDrawable(it,svgColor),null) | |||
| 3 -> view.setCompoundDrawablesWithIntrinsicBounds(null,null,null,DrawableUti.tintDrawable(it,svgColor)) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -20,8 +20,8 @@ class LearnEventData(val subjectId : Int, var courseId : Long, val actionFlag : | |||
| //学习结束 newErrorMap 保存为所有的错误,包含学前和课程前的测试,主要用与后面进行小游戏的数据加载 | |||
| //视频播放结束,数据传递的播放时间点 | |||
| var videoPlayTime : String = "" | |||
| //视频播放结束,数据传递的播放时间点, 不需要,直接更新课时后,可从课时中获取 | |||
| // var videoPlayTime : String = "" | |||
| //作文使用,发送消息时,是否只更新进度点,而不需要重算进度,进行上传进度 | |||
| var isOnlyUpdatePoint = false | |||
| @@ -39,6 +39,12 @@ class FilePathManager { | |||
| fun getMp4PngRootPath():String{ | |||
| return FileUtil.getSaveDirPath("mp4png") | |||
| } | |||
| /**作文视频瞄点png保存的文件*/ | |||
| fun getMp4PngFile(subjectId : Int,coursePackId : Long,courseId : Long, pngName:String):File{ | |||
| return File(getMp4PngRootPath(),"$subjectId/$coursePackId/$courseId/$pngName") | |||
| } | |||
| /**作文课外练习png保存的文件夹*/ | |||
| fun getPngRootPath():String{ | |||
| return FileUtil.getSaveDirPath("png") | |||
| @@ -52,6 +58,10 @@ class FilePathManager { | |||
| fun getMp4RootPath():String{ | |||
| return FileUtil.getSaveDirPath("mp4") | |||
| } | |||
| /**作文课外练习png保存的文件*/ | |||
| fun getMp4File(subjectId : Int,coursePackId : Long,courseId : Long, fileName:String):File{ | |||
| return File(getMp4RootPath(), "$subjectId/$coursePackId/$courseId/$fileName") | |||
| } | |||
| } | |||
| @@ -6,16 +6,19 @@ import androidx.core.database.getBlobOrNull | |||
| import com.google.common.base.Joiner | |||
| import com.suliang.common.AppConfig | |||
| import com.suliang.common.util.AppGlobals | |||
| import com.suliang.common.util.DateUtil | |||
| 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.bean.CompositionReadingBean | |||
| import com.xkl.cdl.data.bean.CompositionTaskBean | |||
| import com.xkl.cdl.data.bean.LearnWord | |||
| import com.xkl.cdl.data.bean.VideoBean | |||
| import com.xkl.cdl.data.bean.course.CourseDetail | |||
| import com.xkl.cdl.data.bean.course.ExamBean | |||
| import com.xkl.cdl.data.bean.course.Lesson | |||
| import com.xkl.cdl.data.manager.FilePathManager | |||
| import com.xkl.videoplayer.bean.VideoAnchor | |||
| import io.reactivex.rxjava3.annotations.NonNull | |||
| import net.sqlcipher.database.SQLiteDatabase | |||
| import java.io.File | |||
| @@ -104,9 +107,6 @@ object DBCourseManager { | |||
| val learnIndex = wordIds.indexOf(detail.lesson_learn_point.getOrElse(key, { -1 })) //学习位置,当前位置为已学 | |||
| // 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 todo 分课程类型设置进度点 | |||
| val learnIsOver = wordIds.size - 1 == learnIndex | |||
| val lesson = Lesson(base.subjectId, base.coursePackId, base.coursePackType, base.courseId, base.courseType, | |||
| chapterId, chapterName, lessonId, lessonName).apply { | |||
| lessonPositionInList = positionIndex | |||
| @@ -117,16 +117,31 @@ object DBCourseManager { | |||
| beforeTestScore = detail.before.getOrElse(key, { AppConstants.NOT_DOING }) //课时学前测试成绩 | |||
| afterTestScore = detail.after.getOrElse(key, { AppConstants.NOT_DOING }) //课时学后测试成绩 | |||
| this.learnedIndex = learnIndex //学习位置,当前位置为已学 | |||
| this.learnIsOver = learnIsOver | |||
| this.lessonType = lessonType | |||
| } | |||
| // 注:针对口语对话课时,如果没有学习完,是不会设置进度点的。 todo 分课程类型设置进度点 口语未做判断处理 | |||
| lesson.learnIsOver = when(base.coursePackType){ | |||
| //作文课时,需要根据课时属性,判断本课时是否学习完成 | |||
| AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION -> when(lesson.lessonType){ | |||
| AppConstants.LESSON_TYPE_COMPOSITION_VIDEO -> if (learnIndex == -1) false else learnIndex < 0 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_KNOWLEDGE -> wordIds.size - 1 == learnIndex | |||
| AppConstants.LESSON_TYPE_COMPOSITION_EXAM -> learnIndex != -1 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_READING -> if (learnIndex == -1) { | |||
| lesson.learnedIndex = 0 //设其初始值为0 | |||
| false | |||
| } else learnIndex < 0 | |||
| else -> false | |||
| } | |||
| else -> wordIds.size - 1 == learnIndex | |||
| } | |||
| mutableList.add(lesson) | |||
| positionIndex += 1 | |||
| // TODO: 2022/5/9 对课时数量进行限制,课时太多,开发人员不好进行测试 | |||
| val needBreak = when (base.coursePackType) { | |||
| AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION -> positionIndex == 11 //保留十个课时 | |||
| AppConstants.COURSEPACK_TYPE_ENGLISH_SPOKEN -> positionIndex == 7 //保留六个课时 | |||
| else -> positionIndex == 3 //保留三个课时 | |||
| AppConstants.COURSEPACK_TYPE_CHINESE_COMPOSITION -> positionIndex == 6 //保留十个课时 | |||
| AppConstants.COURSEPACK_TYPE_ENGLISH_SPOKEN -> positionIndex == 4 //保留六个课时 | |||
| else -> positionIndex == 1 //保留三个课时 | |||
| } | |||
| if (needBreak) { | |||
| break | |||
| @@ -638,18 +653,62 @@ object DBCourseManager { | |||
| getString(8), | |||
| position)) | |||
| position++ | |||
| result.add(CompositionTaskBean(getLong(2), | |||
| getLong(4), | |||
| getLong(3), | |||
| getString(6), | |||
| getString(7), | |||
| getString(8), | |||
| position)) | |||
| position++ | |||
| } | |||
| close() | |||
| } | |||
| return result | |||
| } | |||
| /*** | |||
| * 查询视频数据 | |||
| * @param dbBaseControl DbControlBase | |||
| * @param lesson Lesson | |||
| */ | |||
| fun queryVideoData(dbBaseControl : DbControlBase, lesson : Lesson) : VideoBean { | |||
| val result = VideoBean() | |||
| open(dbBaseControl) | |||
| var sql = "SELECT * FROM video WHERE video_id = ${lesson.wordIds[0]}" | |||
| mDataBase?.rawQuery(sql,null)?.run { | |||
| while (moveToNext()){ | |||
| result.apply { | |||
| videoId = getLong(2) | |||
| title = getString(3) | |||
| url = getString(4).substringAfterLast("/") | |||
| duration = getLong(5) | |||
| } | |||
| } | |||
| close() | |||
| } | |||
| //关键点数据 | |||
| val videoAnchorList = mutableListOf<VideoAnchor>() | |||
| sql = "SELECT * FROM video_anchor WHERE video_id = ${lesson.wordIds[0]}" | |||
| mDataBase?.rawQuery(sql,null)?.run { | |||
| while (moveToNext()){ | |||
| val videoAnchor = VideoAnchor().apply { | |||
| anchor_id = getLong(3) | |||
| title = getString(5) | |||
| time = DateUtil.getTimeSecond(getString(2)) | |||
| } | |||
| videoAnchorList.add(videoAnchor) | |||
| } | |||
| close() | |||
| } | |||
| result.setmVideoAnchorList(videoAnchorList) | |||
| //查询板书信息 | |||
| val videoBlackBoardList = mutableListOf<VideoBean.VideoBlackBoard>() | |||
| sql = "SELECT * FROM video_blackboard WHERE video_id = ${lesson.wordIds[0]} ORDER by sort" | |||
| mDataBase?.rawQuery(sql,null)?.run { | |||
| while (moveToNext()){ | |||
| videoBlackBoardList.add( VideoBean.VideoBlackBoard().apply { | |||
| videoBlackboardId = getLong(2) | |||
| content = getString(4) | |||
| sort = getInt(5) | |||
| }) | |||
| } | |||
| close() | |||
| } | |||
| result.setmVideoBlackBoard(videoBlackBoardList) | |||
| return result | |||
| } | |||
| } | |||
| @@ -1,6 +1,8 @@ | |||
| package com.xkl.cdl.data.repository | |||
| import com.googlecode.protobuf.format.JsonFormat | |||
| import com.suliang.common.util.file.FileUtil | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.bean.course.CourseDetail | |||
| import com.xkl.cdl.data.bean.course.Lesson | |||
| import com.xkl.cdl.data.manager.db.DBCourseManager | |||
| @@ -8,6 +10,7 @@ import com.xkl.cdl.data.manager.db.DbControlBase | |||
| import io.reactivex.rxjava3.core.Observable | |||
| import mqComsumerV1.Struct | |||
| import java.io.File | |||
| import java.io.FileOutputStream | |||
| /** | |||
| * author suliang | |||
| @@ -94,6 +97,41 @@ object DataRepository { | |||
| calcCourseVocabularyAndEfficiency(subjectId,coursePackId,courseId) | |||
| } | |||
| // TODO: 2022/4/29 调用保存方法 | |||
| var fileName = "" | |||
| //写入文件 | |||
| if (record.examCount > 0 ){ //当前为测试 | |||
| val s = when (record.getExam(0).type.toInt()) { //测试类型 | |||
| AppConstants.TEST_TYPE_BEFORE_TOTAL -> "学前总测试" | |||
| AppConstants.TEST_TYPE_BEFORE -> "章节学前测试" | |||
| AppConstants.TEST_TYPE_AFTER -> "章节学后测试" | |||
| AppConstants.TEST_TYPE_AFTER_TOTAL -> "学前总测试" | |||
| AppConstants.TEST_TYPE_COMPOSITION -> "作文知识点测试" | |||
| else -> "" | |||
| } | |||
| fileName = "${record.getExam(0).projectId}_${record.getExam(0).packId}_${record.getExam(0).courseId}_${record.getExam(0).chapterId}_${record.getExam(0).lessonId}_$s" | |||
| }else if (record.entityCount > 0 ){ //没有测试,则为学习 | |||
| fileName = "${record.getEntity(0).projectId}_${record.getEntity(0).packId}_${record.getEntity(0).courseId}_${record.getEntity(0).chapterId}_${record.getEntity(0).lessonId}_课时学习" | |||
| }else if (record.scheduleCount > 0 ) { //进度更新 | |||
| fileName = "${record.getSchedule(0).projectId}_${record.getSchedule(0).packId}_${record.getSchedule(0).courseId}_进度更新" | |||
| }else { // 时长更新 | |||
| fileName = "${record.getDuration(0).projectId}_${record.getDuration(0).packId}_${record.getDuration(0).courseId}_时长更新" | |||
| } | |||
| FileUtil.getSaveDirFile("saveData").let { | |||
| if (!it.exists()){ | |||
| it.mkdirs() | |||
| } | |||
| //序列化输出 | |||
| val string = JsonFormat.printToString(record.build()) | |||
| val saveArray = record.build().toByteArray() | |||
| val saveStringName = "${fileName}_jsonString.txt" | |||
| FileUtil.writeBytesToFile(it,saveStringName,string.toByteArray()) | |||
| val saveByteArrayName = "${fileName}_byteArray.txt" | |||
| FileUtil.writeBytesToFile(it,saveByteArrayName,saveArray) | |||
| } | |||
| return true | |||
| } | |||
| /** | |||
| @@ -63,7 +63,7 @@ abstract class LearnBaseViewModel : BaseViewModel() { | |||
| /** 停止计时 */ | |||
| fun stopTotalCountTing() { | |||
| // LogUtil.e("停止总计时") | |||
| LogUtil.e("停止总计时") | |||
| timeTask?.cancel() | |||
| timeTask = null | |||
| } | |||
| @@ -229,7 +229,13 @@ class LearnCReadingActivity : BaseActivityVM<ActivityLearnCreadingBinding, Learn | |||
| dialog.dismissAllowingStateLoss() | |||
| when { | |||
| isRightClick -> vm.showOrDismissBackDialogForTime(false) | |||
| else -> vm.saveData() | |||
| else ->{ | |||
| if (!vm.isLearned){ //没有学习直接退出 | |||
| finish() | |||
| }else { | |||
| vm.saveData() | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }.show(supportFragmentManager, "learn_back_dialog") | |||
| @@ -20,10 +20,13 @@ class LearnCReadingViewModel : LearnBaseViewModel() { | |||
| /* 操作数据 */ | |||
| val learnData = DataTransferHolder.instance.getData<LearnData>() | |||
| //当前学习下标 | |||
| var currentLearnIndex = learnData.lesson.learnedIndex | |||
| //当前学习下标 初始为0 ,非-1 | |||
| var currentLearnIndex = learnData.lesson.learnedIndex - 1 | |||
| //记录最后一条的item是否进行了学习, 初始标记直接为课时学习是否完成的标记就好了 | |||
| var lastIsLearned = learnData.lesson.learnIsOver | |||
| //当前是否学习 | |||
| var isLearned = false | |||
| //当前学习Bean | |||
| val currentValue = MutableLiveData<CompositionReadingBean>() | |||
| @@ -145,7 +148,7 @@ class LearnCReadingViewModel : LearnBaseViewModel() { | |||
| courseId = it.courseId | |||
| chapterId = it.chapterId | |||
| lessonId = it.lessonId | |||
| entityId = currentValue.value!!.id | |||
| entityId = if (learnData.lesson.learnIsOver || lastIsLearned) -currentValue.value!!.id else currentValue.value!!.id | |||
| } | |||
| isCompExercise = true | |||
| tag = "android" | |||
| @@ -169,7 +172,7 @@ class LearnCReadingViewModel : LearnBaseViewModel() { | |||
| emitter.onNext(DataRepository.saveRecord(record)) | |||
| emitter.onComplete() | |||
| }.compose(diskIo2Main()).subscribe { | |||
| learnData.lesson.learnedIndex = currentLearnIndex - 1 | |||
| learnData.lesson.learnedIndex = currentLearnIndex | |||
| val temp = learnData.lesson.learnIsOver == lastIsLearned | |||
| //发送信息,更改进度点 | |||
| if (!temp){ | |||
| @@ -1,6 +1,5 @@ | |||
| package com.xkl.cdl.module.learn | |||
| import android.media.Image | |||
| import android.os.Bundle | |||
| import android.text.TextUtils | |||
| import android.widget.ImageView | |||
| @@ -18,7 +17,6 @@ import com.suliang.common.extension.click | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.adapter.AdapterBottomDialogSwitch | |||
| import com.xkl.cdl.adapter.AdapterImageTask | |||
| import com.xkl.cdl.data.bean.CompositionReadingBean | |||
| import com.xkl.cdl.data.bean.CompositionTaskBean | |||
| import com.xkl.cdl.data.manager.FilePathManager | |||
| import com.xkl.cdl.databinding.ActivityLearnCtaskBinding | |||
| @@ -58,6 +56,7 @@ class LearnCTaskActivity : BaseActivityVM<ActivityLearnCtaskBinding,LearnCTaskVi | |||
| popupView.updateSrcView(binding.recyclerView.getChildAt(position) as ImageView) | |||
| } | |||
| }, SmartGlideImageLoader()) | |||
| .isShowSaveButton(false) | |||
| .show() | |||
| } | |||
| } | |||
| @@ -78,6 +77,7 @@ class LearnCTaskActivity : BaseActivityVM<ActivityLearnCtaskBinding,LearnCTaskVi | |||
| val fileList = split(",").map { | |||
| FilePathManager.getPngFile(vm.lesson.subjectId,vm.lesson.coursePackId,vm.lesson.courseId,it.substringAfterLast ("/")) | |||
| }.toMutableList() | |||
| (binding.recyclerView.adapter as AdapterImageTask).setData(fileList) | |||
| } | |||
| } | |||
| @@ -1,12 +1,389 @@ | |||
| package com.xkl.cdl.module.learn | |||
| import androidx.appcompat.app.AppCompatActivity | |||
| import android.content.pm.ActivityInfo | |||
| import android.graphics.Color | |||
| import android.net.Uri | |||
| import android.os.Bundle | |||
| import android.os.Handler | |||
| import android.view.View | |||
| import android.view.WindowInsets | |||
| import android.view.WindowManager | |||
| import android.widget.RadioGroup | |||
| import androidx.core.view.ViewCompat | |||
| import androidx.lifecycle.Observer | |||
| import androidx.lifecycle.ViewModelProvider | |||
| import androidx.recyclerview.widget.LinearLayoutManager | |||
| import com.bumptech.glide.load.DataSource | |||
| import com.bumptech.glide.load.engine.GlideException | |||
| import com.bumptech.glide.request.RequestListener | |||
| import com.bumptech.glide.request.target.Target | |||
| import com.suliang.common.base.activity.BaseActivityVM | |||
| import com.suliang.common.extension.click | |||
| import com.suliang.common.util.LogUtil | |||
| import com.suliang.common.util.image.GlideApp | |||
| import com.suliang.common.util.thread.AppExecutors | |||
| import com.xkl.cdl.R | |||
| import com.xkl.cdl.adapter.AdapterVideo | |||
| import com.xkl.cdl.data.bean.VideoBean | |||
| import com.xkl.cdl.data.manager.FilePathManager | |||
| import com.xkl.cdl.databinding.ActivityLearnCvideoBinding | |||
| import com.xkl.cdl.dialog.CommonDialog | |||
| import com.xkl.cdl.dialog.CommonDialogBean | |||
| import com.xkl.videoplayer.bean.PineMediaPlayerBean | |||
| import com.xkl.videoplayer.bean.PineMediaUriSource | |||
| import com.xkl.videoplayer.component.PineMediaPlayerComponent | |||
| import com.xkl.videoplayer.component.PineMediaWidget | |||
| import com.xkl.videoplayer.widget.PineMediaController | |||
| import com.xkl.videoplayer.widget.adapter.DefaultVideoControllerAdapter | |||
| import com.zackratos.ultimatebarx.ultimatebarx.statusBar | |||
| import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly | |||
| import java.io.File | |||
| import java.util.* | |||
| class LearnCVideoActivity : AppCompatActivity() { | |||
| override fun onCreate(savedInstanceState: Bundle?) { | |||
| super.onCreate(savedInstanceState) | |||
| setContentView(R.layout.activity_learn_cvideo) | |||
| class LearnCVideoActivity : BaseActivityVM<ActivityLearnCvideoBinding, LearnCVideoViewModel>() { | |||
| //播放器控件绑定的播放器接口 | |||
| private var mPlayer : PineMediaWidget.IPineMediaPlayer? = null | |||
| private val defaultVideoControllerAdapter : DefaultVideoControllerAdapter by lazy { | |||
| DefaultVideoControllerAdapter(this) | |||
| } | |||
| //播放器控制器控件 | |||
| private val mController : PineMediaController by lazy { | |||
| PineMediaController(this) | |||
| } | |||
| override fun initViewModel() : LearnCVideoViewModel { | |||
| return ViewModelProvider(this)[LearnCVideoViewModel::class.java] | |||
| } | |||
| override fun initActivity(savedInstanceState : Bundle?) { | |||
| //初始化播放器控件 | |||
| initVideoControl() | |||
| //设置板书数据并跟新 | |||
| initTabRecyclerView() | |||
| //初始化事件 | |||
| initEvent() | |||
| } | |||
| override fun initStatusBar() { | |||
| statusBarOnly { | |||
| fitWindow = true | |||
| color = Color.WHITE | |||
| light = true | |||
| } | |||
| } | |||
| override fun loadData() { | |||
| vm.queryVideoData().observe(this) { | |||
| //设置瞄点图片所在文件 | |||
| val anchorPngFile = FilePathManager.getMp4PngFile(vm.lesson.subjectId, vm.lesson.coursePackId, vm.lesson.courseId, | |||
| vm.mVideoBean!!.url + ".png") | |||
| if (anchorPngFile.exists()) { | |||
| mController.setSpritFile(anchorPngFile.path) | |||
| } | |||
| //初始化视频 | |||
| initVideoMovie() | |||
| //有数据了,更新recyclerView显示,数据为适配器直接从vm中获取的 | |||
| binding.recyclerview.adapter?.notifyDataSetChanged() | |||
| } | |||
| vm.uploadDate.observe(this){ | |||
| finish() | |||
| } | |||
| } | |||
| /** 初始化播放器 */ | |||
| private fun initVideoControl() { | |||
| defaultVideoControllerAdapter.setActionListener(controllerListener) | |||
| mController.setMediaControllerAdapter(defaultVideoControllerAdapter) | |||
| mController.setFirstPlayOver(vm.lesson.learnIsOver) //设置第一次是否播放完成 | |||
| mController.setMaxPlayTime(vm.currentPlayTime.toInt()) //设置播放的最大时间 | |||
| LogUtil.e("lesson current play time = " + vm.currentPlayTime) | |||
| //为videoView设置获取时间监听器 | |||
| binding.videoView.setServiceDateListener { System.currentTimeMillis() } | |||
| binding.videoView.init("学考乐", mController, true, true) | |||
| binding.videoView.setKeepScreenOn(true) | |||
| binding.videoView.disableBackPressTip() | |||
| mPlayer = binding.videoView.getMediaPlayer() | |||
| mPlayer?.setAutocephalyPlayMode(false, true) //设置是否为独立播放模式(是否与播放界面共生命周期) | |||
| } | |||
| private fun initTabRecyclerView() { | |||
| binding.recyclerview.apply { | |||
| layoutManager = LinearLayoutManager(this@LearnCVideoActivity, LinearLayoutManager.VERTICAL, false) | |||
| adapter = AdapterVideo(vm).apply { | |||
| onItemClick = { v, position, item -> | |||
| // TODO: 2022/5/25 点击事件,处理收藏和取消收藏 | |||
| // when(item.collectId){ | |||
| // 0L -> vm.addCollect(item).observe(this@LearnCVideoActivity){ | |||
| // notifyDataSetChanged() | |||
| // } | |||
| // else -> vm.removeCollect(item).observe(this@LearnCVideoActivity){ | |||
| // notifyDataSetChanged() | |||
| // } | |||
| // } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| private fun initEvent() { | |||
| //tab切换监听 | |||
| binding.radioGroup.setOnCheckedChangeListener(RadioGroup.OnCheckedChangeListener { group, checkedId -> | |||
| if (checkedId == R.id.rb_video_blackboard) { | |||
| vm.showTab = 0 | |||
| } else { | |||
| vm.showTab = 1 | |||
| } | |||
| binding.recyclerview.adapter?.notifyDataSetChanged() | |||
| }) | |||
| //竖屏重新加载视频 | |||
| binding.reloadBtn.click { v -> | |||
| binding.stopView.setVisibility(View.GONE) | |||
| vm.isStop.setValue(false) | |||
| mPlayer?.reLoad() | |||
| } | |||
| //横屏重新加载视频 | |||
| binding.reloadBtnFull.click { v -> | |||
| binding.stopViewFull.setVisibility(View.GONE) | |||
| vm.isStop.setValue(false) | |||
| mPlayer?.reLoad() | |||
| } | |||
| //加载失败:竖屏返回退出 | |||
| binding.stopBack.click { v -> | |||
| onBackPressed() | |||
| } | |||
| //加载失败:横屏返回退出 | |||
| binding.stopBackFull.setOnClickListener { v -> | |||
| fullScreenSwitch(false) | |||
| initStatusBar() | |||
| binding.videoView.toggleFullScreenMode(true) | |||
| } | |||
| } | |||
| /** 初始化播放视频 */ | |||
| private fun initVideoMovie() { | |||
| vm.mVideoBean?.let { vb -> | |||
| getMediaUriSourceList(vb.url)?.let { videoPath : PineMediaUriSource -> | |||
| vm.pineMediaPlayerBean = PineMediaPlayerBean("0", vb.title, videoPath, PineMediaPlayerBean.MEDIA_TYPE_VIDEO, null, | |||
| null, null).apply { | |||
| currentDefinition = PineMediaUriSource.MEDIA_DEFINITION_VHD //设置清晰度 | |||
| videoAnchorList = vb.getmVideoAnchorList() //设置视频瞄点内容 | |||
| duration = vb.duration //总时间 | |||
| } | |||
| mPlayer?.let { mp -> | |||
| mp.setPlayingMedia(vm.pineMediaPlayerBean) //设置播放参数 | |||
| //设置播放监听 | |||
| mp.setCompletionListener { | |||
| vm.currentPlayTime = 0 | |||
| vm.isCompletion = true | |||
| //播放完成 :设置第一次播放完成 | |||
| binding.videoView.mediaController.setFirstPlayOver(true) | |||
| } | |||
| mp.setOnStopListener { | |||
| when { | |||
| !vm.isCompletion -> { //非播完后暂停状态 | |||
| vm.isStop.value = true | |||
| when (requestedOrientation) { | |||
| //横屏 | |||
| ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE -> binding.stopViewFull.visibility = View.VISIBLE | |||
| else -> binding.stopView.visibility = View.VISIBLE | |||
| } | |||
| } | |||
| else -> { | |||
| vm.isStop.value = false | |||
| mp.reLoad() | |||
| } | |||
| } | |||
| } | |||
| //播放中监听 | |||
| mp.setPlayingListener { millSeconds : Int, duration : Int -> | |||
| vm.isLearn = true | |||
| vm.currentPlayTime = millSeconds.toLong() | |||
| binding.videoView.mediaController.setMaxPlayTime(millSeconds) //设置播放的最大值 | |||
| //98%则算本课时学习完成 | |||
| if (!vm.isCompletion && millSeconds.toDouble() / duration >= 0.98) { | |||
| vm.isCompletion = true | |||
| binding.videoView.mediaController.setFirstPlayOver(true) | |||
| } | |||
| } | |||
| //设置缓存位置、播放位置 | |||
| LogUtil.e("开始播放设置播放位置时间:${vm.currentPlayTime}") | |||
| mp.setCachPosition(vm.currentPlayTime.toInt()) | |||
| } | |||
| } | |||
| } | |||
| // //播放不流畅监听 | |||
| // mPlayer.setPlayNotFluencyListener(object : PineMediaPlayerComponent.OnPlayNotFluencyListener() { | |||
| // fun onPlayNotFluency() { | |||
| // AppExecutors.getInstance().mainThread().execute { | |||
| // var currentDefinition : Int = vm.pineMediaPlayerBean.getCurrentDefinition() | |||
| // if (currentDefinition != PineMediaUriSource.MEDIA_DEFINITION_SD) { | |||
| // val oldPosition : Int = vm.pineMediaPlayerBean.getCurrentDefinitionPosition() | |||
| // currentDefinition = if (currentDefinition == PineMediaUriSource.MEDIA_DEFINITION_VHD) { | |||
| // PineMediaUriSource.MEDIA_DEFINITION_HD | |||
| // } else { | |||
| // PineMediaUriSource.MEDIA_DEFINITION_SD | |||
| // } | |||
| // val newPosition : Int = vm.pineMediaPlayerBean.getCurrentDefinitionPosition() | |||
| // vm.pineMediaPlayerBean.setCurrentDefinition(currentDefinition) | |||
| // mPlayer.savePlayMediaState() | |||
| // defaultVideoControllerAdapter.videoDefinitionSelected(vm.pineMediaPlayerBean, oldPosition, | |||
| // newPosition) | |||
| // } | |||
| // } | |||
| // } | |||
| // }) | |||
| //设置缓存位置、播放位置 | |||
| // LogUtil.e(TAG, "设置播放位置: " + vm.currentPlayTime) | |||
| // mPlayer.setCachPosition(vm.currentPlayTime as Int * 1000) | |||
| } | |||
| fun getMediaUriSourceList(mp4fileName : String) : PineMediaUriSource? { | |||
| val mp4 = FilePathManager.getMp4File(vm.lesson.subjectId, vm.lesson.coursePackId, vm.lesson.courseId, mp4fileName) | |||
| if (mp4.exists()) { | |||
| return PineMediaUriSource(Uri.parse(mp4.path), PineMediaUriSource.MEDIA_DEFINITION_VHD) | |||
| } | |||
| return null | |||
| } | |||
| // fun getMediaUriSourceList(basePath : String) : ArrayList<PineMediaUriSource> { | |||
| // val pinePlayerUriSourceList : ArrayList<PineMediaUriSource> = ArrayList<PineMediaUriSource>() | |||
| // var pineMediaUriSource : PineMediaUriSource | |||
| // pineMediaUriSource = PineMediaUriSource(Uri.parse("$basePath.mp4"), PineMediaUriSource.MEDIA_DEFINITION_VHD) | |||
| // pinePlayerUriSourceList.add(pineMediaUriSource) | |||
| // pineMediaUriSource = PineMediaUriSource(Uri.parse(basePath + "_720.mp4"), PineMediaUriSource.MEDIA_DEFINITION_HD) | |||
| // pinePlayerUriSourceList.add(pineMediaUriSource) | |||
| // pineMediaUriSource = PineMediaUriSource(Uri.parse(basePath + "_480.mp4"), PineMediaUriSource.MEDIA_DEFINITION_SD) | |||
| // pinePlayerUriSourceList.add(pineMediaUriSource) | |||
| // // pineMediaUriSource = new PineMediaUriSource( | |||
| //// Uri.parse(basePath + ".mp4"), | |||
| //// PineMediaUriSource.MEDIA_DEFINITION_VHD); | |||
| //// pinePlayerUriSourceList.add(pineMediaUriSource); | |||
| //// | |||
| //// pineMediaUriSource = new PineMediaUriSource( | |||
| //// Uri.parse("https://cdn.xuekaole.com/videos/cn_zw/test/ea81f0e8f3651adad34fe7add5a3d26d_720.mp4"), | |||
| //// PineMediaUriSource.MEDIA_DEFINITION_HD); | |||
| //// pinePlayerUriSourceList.add(pineMediaUriSource); | |||
| //// | |||
| //// pineMediaUriSource = new PineMediaUriSource( | |||
| //// Uri.parse("https://cdn.xuekaole.com/videos/cn_zw/test/ea81f0e8f3651adad34fe7add5a3d26d_480.mp4"), | |||
| //// PineMediaUriSource.MEDIA_DEFINITION_SD); | |||
| //// pinePlayerUriSourceList.add(pineMediaUriSource); | |||
| // return pinePlayerUriSourceList | |||
| // } | |||
| //控制器事件监听器 | |||
| private val controllerListener : PineMediaController.ControllersActionListener = object : | |||
| PineMediaController.ControllersActionListener() { | |||
| //小屏退出 | |||
| override fun onBackBtnClick(goBackBtn : View, | |||
| player : PineMediaWidget.IPineMediaPlayer, | |||
| isFullScreenMode : Boolean) : Boolean { | |||
| onBackPressed() | |||
| return false | |||
| } | |||
| /** 全屏退出 */ | |||
| override fun onGoBackBtnClick(goBackBtn : View, | |||
| player : PineMediaWidget.IPineMediaPlayer, | |||
| isFullScreenMode : Boolean) : Boolean { | |||
| fullScreenSwitch(false) | |||
| return false | |||
| } | |||
| /** 屏幕切换 */ | |||
| override fun onFullScreenBtnClick(fullScreenBtn : View, player : PineMediaWidget.IPineMediaPlayer) : Boolean { | |||
| if (requestedOrientation != ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { //竖屏切换横屏 | |||
| fullScreenSwitch(true) | |||
| } else { //切换竖屏 | |||
| fullScreenSwitch(false) | |||
| } | |||
| return false | |||
| } | |||
| } | |||
| private fun fullScreenSwitch(isFullScreen : Boolean) { | |||
| if (isFullScreen) { | |||
| //全屏 隐藏状态栏 | |||
| hideSystemUI() | |||
| if (vm.isStop.value!!) { | |||
| binding.stopView.setVisibility(View.GONE) | |||
| binding.stopViewFull.setVisibility(View.VISIBLE) | |||
| } | |||
| } else { | |||
| //退出全屏 | |||
| showSystemUI() | |||
| if (vm.isStop.value!!) { | |||
| binding.stopView.setVisibility(View.VISIBLE) | |||
| binding.stopViewFull.setVisibility(View.GONE) | |||
| } | |||
| } | |||
| } | |||
| private fun hideSystemUI() { | |||
| statusBarOnly { | |||
| fitWindow = false | |||
| } | |||
| window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | |||
| // Hide the nav bar and status bar | |||
| or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN) | |||
| } | |||
| private fun showSystemUI() { | |||
| window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) | |||
| initStatusBar() | |||
| } | |||
| override fun onRestart() { | |||
| super.onRestart() | |||
| //如果是横屏 | |||
| if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { | |||
| fullScreenSwitch(true) | |||
| } | |||
| } | |||
| override fun onStop() { | |||
| mPlayer?.stop() | |||
| super.onStop() | |||
| } | |||
| override fun onDestroy() { | |||
| mPlayer?.release() | |||
| super.onDestroy() | |||
| } | |||
| override fun onBackPressed() { | |||
| //如果没有学习,直接关闭 | |||
| if (!vm.isLearn){ | |||
| finish() | |||
| return | |||
| } | |||
| vm.showOrDismissBackDialogForTime(true) | |||
| CommonDialog.newInstance( | |||
| CommonDialogBean(titleText = R.string.quit_learn_title, contentText = R.string.quit_learn_content, leftText = R.string.quit, rightText = R.string.cancel)).apply { | |||
| onCommonDialogButtonClickListener = { dialog, isRightClick -> | |||
| dialog.dismissAllowingStateLoss() | |||
| when { | |||
| isRightClick -> { | |||
| vm.showOrDismissBackDialogForTime(false) | |||
| mController.doPauseResume() | |||
| } | |||
| else -> vm.saveData() | |||
| } | |||
| } | |||
| }.show(supportFragmentManager, "learn_back_dialog") | |||
| } | |||
| } | |||
| @@ -0,0 +1,346 @@ | |||
| package com.xkl.cdl.module.learn | |||
| import androidx.lifecycle.LifecycleOwner | |||
| import androidx.lifecycle.MutableLiveData | |||
| import com.jeremyliao.liveeventbus.LiveEventBus | |||
| import com.suliang.common.extension.diskIo2Main | |||
| import com.suliang.common.util.DateUtil | |||
| import com.suliang.common.util.LogUtil | |||
| import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.DataTransferHolder | |||
| import com.xkl.cdl.data.bean.VideoBean | |||
| import com.xkl.cdl.data.bean.course.Lesson | |||
| import com.xkl.cdl.data.event.LearnEventData | |||
| import com.xkl.cdl.data.manager.db.DBCourseManager | |||
| import com.xkl.cdl.data.manager.db.DbControlBase | |||
| import com.xkl.cdl.data.repository.DataRepository | |||
| import com.xkl.videoplayer.bean.PineMediaPlayerBean | |||
| import io.reactivex.rxjava3.core.Observable | |||
| import mqComsumerV1.Struct | |||
| class LearnCVideoViewModel : LearnBaseViewModel() { | |||
| val lesson = DataTransferHolder.instance.getData<Lesson>() //视频课时lesson | |||
| var currentPlayTime : Long = lesson.videoPlayTime //当前播放时间: 毫秒 | |||
| private val dbBaseControl : DbControlBase = DbControlBase(lesson.subjectId, lesson.coursePackId, lesson.coursePackType, lesson.courseId, lesson.courseType) | |||
| var isCompletion = false //是否播放完成 | |||
| var isLearn = false //是否有播放记录 | |||
| var mVideoBean : VideoBean? = null //视频数据 | |||
| //选中tab 0:板书 1:笔记 | |||
| var showTab = 0 | |||
| private var timeForLong : Long = 0 //本次学习开始的时间 | |||
| private var clientTag : String? = null | |||
| //视频源 | |||
| var pineMediaPlayerBean : PineMediaPlayerBean? = null | |||
| var isStop = MutableLiveData(false) | |||
| //我的笔记 | |||
| var mCollectVideoBlackBoradList : MutableList<VideoBean.VideoBlackBoard> = ArrayList<VideoBean.VideoBlackBoard>() | |||
| //上传数据监听 | |||
| val uploadDate = MutableLiveData<Boolean>() | |||
| init { | |||
| clientTag = "android" | |||
| timeForLong = System.currentTimeMillis() | |||
| isRunValidTime = false | |||
| } | |||
| /** 获取视频数据 */ | |||
| fun queryVideoData() : MutableLiveData<Boolean> { | |||
| val result = MutableLiveData<Boolean>() | |||
| // TODO: 2022/5/24 获取该课时下的收藏信息 板书设置收藏id,同时对笔记进行排序 | |||
| // io.reactivex.rxjava3.core.Observable.create<AppApi.CompCollectionDetailedResponse> { | |||
| // val build = CompCollectionDetailedRequest.newBuilder().apply { | |||
| // userId = 1 | |||
| // productId = lesson.courseId | |||
| // typeId = 2 | |||
| // columnId = lesson.chapterId | |||
| // }.build() | |||
| // } | |||
| io.reactivex.rxjava3.core.Observable.create<Boolean> { | |||
| mVideoBean = DBCourseManager.queryVideoData(dbBaseControl,lesson) | |||
| it.onNext(true) | |||
| it.onComplete() | |||
| }.compose(diskIo2Main()) | |||
| .subscribe{ | |||
| result.value = true | |||
| } | |||
| return result | |||
| } | |||
| /* *//** 取消笔记 *//* | |||
| fun removeCollect(item : VideoBean.VideoBlackBoard) : MutableLiveData<Boolean>? { | |||
| val result = MutableLiveData<Boolean>() | |||
| //体验账号,当前页假数据 | |||
| if (!UserInfoManager.getInstance().isOfficialAccount()) { | |||
| item.setCollectId(0) | |||
| mCollectVideoBlackBoradList.remove(item) | |||
| result.postValue(true) | |||
| return result | |||
| } | |||
| DataRepository.getInstance() | |||
| .removeCollect(item.getCollectId()) | |||
| .subscribe(object : BaseObserver<AppApi.CompRemoveCollectResponse?>(this) { | |||
| fun onSuccess(data : AppApi.CompRemoveCollectResponse?) { | |||
| item.setCollectId(0) | |||
| mCollectVideoBlackBoradList.remove(item) | |||
| result.postValue(true) | |||
| } | |||
| fun onFailed(code : Int, msg : String?) { | |||
| getToast().setValue(XKLApplication.getInstance().getString(R.string.remove_collect_error)) | |||
| } | |||
| }) | |||
| return result | |||
| } | |||
| *//** 添加笔记 *//* | |||
| fun addCollect(item : VideoBean.VideoBlackBoard) : MutableLiveData<Boolean>? { | |||
| val result = MutableLiveData<Boolean>() | |||
| //体验账号,当前页假数据 | |||
| if (!UserInfoManager.getInstance().isOfficialAccount()) { | |||
| item.setCollectId(mCollectVideoBlackBoradList.size + 1) | |||
| mCollectVideoBlackBoradList.add(item) | |||
| result.postValue(true) | |||
| return result | |||
| } | |||
| DataRepository.getInstance() | |||
| .collect(getLesson().getCourseId(), getLesson().getChapterId(), item.getVideoBlackboardId(), | |||
| SyncStateContract.Constants.COMPOSITON_COLLECT_VIDEO_BLACK_BOOK) | |||
| .subscribe(object : BaseObserver<AppApi.CompCollectResponse?>(this) { | |||
| fun onSuccess(data : AppApi.CompCollectResponse) { | |||
| val collectedMap = data.collectedMap | |||
| if (collectedMap != null) { | |||
| val aLong = collectedMap[item.getVideoBlackboardId()] | |||
| if (aLong != null) { | |||
| item.setCollectId(aLong) | |||
| mCollectVideoBlackBoradList.add(item) | |||
| Collections.sort(mCollectVideoBlackBoradList, | |||
| Comparator<Any?> { o1, o2 -> o1.getSort() - o2.getSort() }) | |||
| } | |||
| } | |||
| result.postValue(true) | |||
| } | |||
| fun onFailed(code : Int, msg : String?) { | |||
| getToast().setValue(XKLApplication.getInstance().getString(R.string.add_collect_error)) | |||
| } | |||
| }) | |||
| return result | |||
| } | |||
| */ | |||
| //是否显示返回弹窗 | |||
| private var isShowBackDialog = false | |||
| override fun onResume(owner : LifecycleOwner) { | |||
| super.onResume(owner) | |||
| if (!isShowBackDialog){ | |||
| startTotalCounting() | |||
| } | |||
| } | |||
| override fun onPause(owner : LifecycleOwner) { | |||
| super.onPause(owner) | |||
| if (!isShowBackDialog){ | |||
| stopTotalCountTing() | |||
| } | |||
| } | |||
| /** | |||
| * 显示或关闭的返回弹窗, 显示时关闭总计时 关闭时,开启总计时 | |||
| * @param isShow Boolean | |||
| */ | |||
| fun showOrDismissBackDialogForTime(isShow : Boolean) { | |||
| isShowBackDialog = isShow | |||
| when{ | |||
| isShowBackDialog -> stopTotalCountTing() | |||
| else -> startTotalCounting() | |||
| } | |||
| } | |||
| fun saveData() { | |||
| if (!isLearn) { //没有播放视频 | |||
| uploadDate.value = true | |||
| return | |||
| } | |||
| // TODO: 2022/5/26 保存视频的播放时间 | |||
| Observable.create<Boolean> { emitter -> | |||
| val record = Struct.Record.newBuilder().apply { | |||
| //进度点 | |||
| addEntity(Struct.LearnEntity.newBuilder().apply { | |||
| lesson.let { | |||
| projectId = it.subjectId.toLong() | |||
| packId = it.coursePackId | |||
| courseId = it.courseId | |||
| chapterId = it.chapterId | |||
| lessonId = it.lessonId | |||
| entityId = if (it.learnIsOver || isCompletion) -it.wordIds[0] else it.wordIds[0] // 学习完成entityId设置为负值,未学习完成为正值 | |||
| } | |||
| tag = "android" | |||
| created = getCurrentDateWithString() | |||
| }) | |||
| //时间 | |||
| addDuration(Struct.LearnDuration.newBuilder().apply { | |||
| lesson.let { | |||
| projectId = it.subjectId.toLong() | |||
| packId = it.coursePackId | |||
| courseId = it.courseId | |||
| categoryId = it.coursePackType.toLong() | |||
| } | |||
| tag = "android" | |||
| created = getCurrentDateWithString() | |||
| timeFrame = DateUtil.getTimeFrame(System.currentTimeMillis()) | |||
| isReview = false | |||
| duration = totalUseTime.value!! | |||
| totalDuration = totalUseTime.value!! | |||
| }) | |||
| } | |||
| emitter.onNext(DataRepository.saveRecord(record)) | |||
| emitter.onComplete() | |||
| }.compose(diskIo2Main()).subscribe { | |||
| lesson.videoPlayTime = currentPlayTime | |||
| LogUtil.e("当前播放时间:$currentPlayTime") | |||
| when{ | |||
| //本课时已完成,只发送消息更新进度点即可 | |||
| lesson.learnIsOver -> { | |||
| //发送数据事件 | |||
| LiveEventBus.get<LearnEventData>(AppConstants.EVENT_LESSON_DATA) | |||
| .post(LearnEventData(lesson.subjectId, lesson.courseId, | |||
| AppConstants.DATA_LESSON_LEARN_OVER).apply { | |||
| this.leesonPositionIndex = lesson.lessonPositionInList | |||
| isOnlyUpdatePoint = true | |||
| }) | |||
| } | |||
| //本次播放完成,也为第一次播放完成,需要发送消息,重新计算进度 | |||
| isCompletion -> { | |||
| //更新lesson | |||
| lesson.learnIsOver = true | |||
| //发送数据事件 | |||
| LiveEventBus.get<LearnEventData>(AppConstants.EVENT_LESSON_DATA) | |||
| .post(LearnEventData(lesson.subjectId, lesson.courseId, | |||
| AppConstants.DATA_LESSON_LEARN_OVER).apply { | |||
| this.leesonPositionIndex = lesson.lessonPositionInList | |||
| isOnlyUpdatePoint = false | |||
| }) | |||
| } | |||
| } | |||
| uploadDate.value = true | |||
| } | |||
| } | |||
| private fun getCurrentDateWithString() = DateUtil.format(System.currentTimeMillis(), DateUtil.FORMAT_1) | |||
| /* *//** | |||
| * 上传学习时间 | |||
| * 视频播放进度点 | |||
| *//* | |||
| fun upData() : MutableLiveData<Boolean>? { | |||
| val result = MutableLiveData<Boolean>() | |||
| if (!UserInfoManager.getInstance().isOfficialAccount()) { | |||
| dataToBack() //数据返回 | |||
| result.postValue(true) | |||
| return result | |||
| } | |||
| if (!isLearn) { //没有播放视频 | |||
| result.postValue(true) | |||
| return result | |||
| } | |||
| getLoadingDialog().postValue(true) | |||
| val subjectComposition : Long = SyncStateContract.Constants.SUBJECT_CHINESE | |||
| val coursePackId : Long = getLesson().getCoursePackId() | |||
| val courseId : Long = getLesson().getCourseId() | |||
| val videoId : Long = mVideoBean.getVideoId() | |||
| //上传时间、视频进度点,用于列表选中位置 | |||
| val mqPush = Observable.fromCallable(Callable<Boolean?> { //创建记录 : 只保存进度点 | |||
| val learnedEntiry = Struct.LearnEntity.newBuilder() | |||
| .setProjectId(subjectComposition) | |||
| .setPackId(coursePackId) | |||
| .setCourseId(courseId) | |||
| .setChapterId(getLesson().getChapterId()) | |||
| .setLessonId(getLesson().getLessonId()) | |||
| .setCreated(DateUtil.format(ServiceDateManager.getInstance().getTimeForLongMillSecond(), DateUtil.FORMAT_5)) | |||
| .setEntityId(videoId) | |||
| .setIsOnlySavePoint(true) | |||
| .build() | |||
| val record = Struct.Record.newBuilder().addEntity(learnedEntiry).addDuration(saveCurrentLearnDuration()) | |||
| MqRepository.getInstance().syncUpdate(this@ActivityVideoViewModel, record) | |||
| }) | |||
| //设置视频播放点 | |||
| val setVideoPointRequest = AppApi.SetVideoPointRequest.newBuilder() | |||
| .setProjectId(subjectComposition) | |||
| .setPackId(coursePackId) | |||
| .setCourseId(courseId) | |||
| .setVideoId(videoId) | |||
| .setTime((currentPlayTime / 1000).toString()) | |||
| .build() | |||
| val emptyObservable : Observable<Empty> = DataRepository.getInstance().setVideoPoint(setVideoPointRequest) | |||
| var observable : Observable<Boolean?>? = null | |||
| observable = if (isCompletion && !getLesson().isFirstLearnIsOver()) { | |||
| //视频播放完成解锁 | |||
| val emptyObservable1 : Observable<Empty> = DataRepository.getInstance() | |||
| .setUnLock(SyncStateContract.Constants.SUBJECT_CHINESE, coursePackId, courseId, getLesson().getChapterId(), | |||
| getLesson().getLessonId()) | |||
| Observable.zip(emptyObservable1, mqPush, emptyObservable, { empty, aBoolean, empty2 -> aBoolean }) | |||
| } else { | |||
| Observable.zip(mqPush, emptyObservable, { aBoolean, empty -> aBoolean }) | |||
| } | |||
| observable.compose(RxHelper.observableIO2Main()).subscribe(Consumer<R> { b : R -> | |||
| if (b) { | |||
| dataToBack() //数据返回 | |||
| } | |||
| getLoadingDialog().postValue(false) | |||
| result.postValue(b) | |||
| }, Consumer { throwable : Throwable? -> | |||
| getLoadingDialog().postValue(false) | |||
| result.postValue(false) | |||
| }) | |||
| return result | |||
| } | |||
| *//** 发送动作返回进行统计 *//* | |||
| private fun dataToBack() { | |||
| //修改lesson学习数据 | |||
| val learnLessson : CompositionLesson = getLesson() | |||
| learnLessson.setFirstLearnIsOver(learnLessson.isFirstLearnIsOver() || isCompletion) //第一次是否学习完成 | |||
| learnLessson.setPlayTime((currentPlayTime / 1000).toInt()) | |||
| if (learnLessson.isFirstLearnIsOver()) { | |||
| learnLessson.setLessonProgress(1) | |||
| } else { | |||
| learnLessson.setLessonProgress(learnLessson.getPlayTime() as Double / learnLessson.getTotalTime()) | |||
| } | |||
| //发送数据进行更新 | |||
| LiveDataBus.dataVideoBack().post(learnLessson) | |||
| } | |||
| *//** | |||
| * 保存当前的单词学习时长 | |||
| *//* | |||
| private fun saveCurrentLearnDuration() : Struct.LearnDuration.Builder? { | |||
| return Struct.LearnDuration.newBuilder() | |||
| .setProjectId(SyncStateContract.Constants.SUBJECT_CHINESE) | |||
| .setPackId(getLesson().getCoursePackId()) | |||
| .setCourseId(getLesson().getCourseId()) | |||
| .setCreated(DateUtil.format(timeForLong, DateUtil.FORMAT_5)) | |||
| .setTag(clientTag) | |||
| .setTimeFrame(DateUtil.getTimeFrame(timeForLong)) //设置时段 | |||
| .setIsReview(false) //是否复习时长 | |||
| .setDuration(getTotalUseTime()) //时长,毫秒 | |||
| .setTotalDuration(getTotalUseTime()) //总时间,包含空闲部分 | |||
| .setCategoryId(getLesson().getCategoryId()) | |||
| } | |||
| */ | |||
| } | |||
| @@ -13,6 +13,7 @@ import com.xkl.cdl.data.AppConstants | |||
| import com.xkl.cdl.data.DataTransferHolder | |||
| import com.xkl.cdl.data.bean.SpellItemBean | |||
| import com.xkl.cdl.data.bean.course.ExamBean | |||
| import com.xkl.cdl.data.bean.course.Lesson | |||
| import com.xkl.cdl.data.bean.intentdata.ExamData | |||
| import com.xkl.cdl.data.event.LearnEventData | |||
| import com.xkl.cdl.data.manager.db.DbControlBase | |||
| @@ -647,16 +648,11 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
| Observable.create<Boolean> { | |||
| viewModelScope.launch { | |||
| delay(1000) | |||
| DataRepository.saveRecord(record) | |||
| it.onNext(true) | |||
| it.onComplete() | |||
| } | |||
| // TODO: 2022/4/14 传递保存record信息 | |||
| DataRepository.saveRecord(record) | |||
| // record 已经实例化并已经将数据保存 | |||
| // LogUtil.e(JsonFormat.printToString(record.build())) | |||
| //知识点测试,标记当前课时为完成 | |||
| if (intentData.examType == AppConstants.TEST_TYPE_COMPOSITION){ | |||
| intentData.lesson!!.learnIsOver = true | |||
| } | |||
| }.compose(diskIo2Main()).subscribe({ | |||
| showHideLoading(false) | |||
| @@ -711,11 +707,18 @@ class LearnExamViewModel : LearnBaseViewModel() { | |||
| }) | |||
| } | |||
| AppConstants.TEST_TYPE_COMPOSITION -> { //知识点测试结束发送数据,当做学习完成处理 | |||
| LiveEventBus.get<LearnEventData>(AppConstants.EVENT_LESSON_DATA).post(LearnEventData( | |||
| intentData.subjectId, | |||
| intentData.courseId, | |||
| AppConstants.DATA_LESSON_LEARN_OVER).apply { | |||
| leesonPositionIndex = intentData.lesson!!.lessonPositionInList | |||
| if (intentData.lesson!!.learnIsOver){ //以及测试完成 | |||
| isOnlyUpdatePoint = true | |||
| }else{ // 标记当前课时为完成, 用于计算进度 | |||
| intentData.lesson!!.learnIsOver = true | |||
| } | |||
| }) | |||
| } | |||
| } | |||
| @@ -24,6 +24,7 @@ import com.xkl.cdl.dialog.CommonDialogBean | |||
| import com.xkl.cdl.dialog.LearnDialog | |||
| import com.xkl.cdl.module.learn.LearnCReadingActivity | |||
| import com.xkl.cdl.module.learn.LearnCTaskActivity | |||
| import com.xkl.cdl.module.learn.LearnCVideoActivity | |||
| import com.xkl.cdl.module.learn.LearnWordActivity | |||
| /** | |||
| @@ -118,11 +119,14 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| //视频 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_VIDEO -> vm.courseDetail.run { | |||
| val relationId = it.wordIds[0] | |||
| //更新视频播放点 | |||
| vp[relationId] = learnEventData.videoPlayTime | |||
| //更新视频播放时间点 | |||
| vp[relationId] = "${it.videoPlayTime}" | |||
| //更新课程学习点 | |||
| lesson_learn_point[key] = relationId //更新课时学习点 | |||
| course_learn_point = "${key}_${relationId}" //更新课程学习点 | |||
| lesson_learn_point[key] = if (it.learnIsOver) -relationId else relationId //更新课时学习点 | |||
| course_learn_point = "${key}_${relationId}" //更新课程学习点 | |||
| if (learnEventData.isOnlyUpdatePoint){ | |||
| return@observe | |||
| } | |||
| } | |||
| //知识点 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_KNOWLEDGE -> vm.courseDetail.run { | |||
| @@ -133,13 +137,20 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| } | |||
| //测试 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_EXAM -> vm.courseDetail.run { | |||
| course_learn_point = "${key}_0}" //更新课程学习点 | |||
| if (it.learnIsOver) { | |||
| lesson_learn_point[key] = 0 //课时进度点 | |||
| course_learn_point = "${key}_0}" //更新课程学习点 | |||
| } | |||
| if (learnEventData.isOnlyUpdatePoint) { | |||
| return@observe | |||
| } | |||
| } | |||
| //课堂练习 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_READING -> vm.courseDetail.run { | |||
| val point = if (it.learnedIndex == -1) -1 else it.wordIds[it.learnedIndex] | |||
| lesson_learn_point[key] = point //更新课时学习点 | |||
| course_learn_point = "${key}_$point" //更新课程学习点 | |||
| //此entityId仅做记录,用于下次显示的时候,进行显示 | |||
| val entityId = it.wordIds[it.learnedIndex] | |||
| lesson_learn_point[key] = if (it.learnIsOver) -entityId else entityId //更新课时学习点 | |||
| course_learn_point = "${key}_$entityId" //更新课程学习点 | |||
| if (learnEventData.isOnlyUpdatePoint){ | |||
| return@observe | |||
| } | |||
| @@ -308,7 +319,6 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| } | |||
| } | |||
| AppConstants.LESSON_TYPE_SENTENCE -> { | |||
| } | |||
| AppConstants.LESSON_TYPE_DIALOGUE -> { | |||
| @@ -316,7 +326,8 @@ class CourseLessonFragment : BaseFragmentVM<FragmentCourseLessonBinding, CourseM | |||
| } | |||
| //视频 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_VIDEO -> { | |||
| DataTransferHolder.instance.putData(value = entity) | |||
| startActivity(LearnCVideoActivity::class.java) | |||
| } | |||
| //知识点 : 直接进入学习,没有学前学后测试 判断学习是否完成,完成的情况下需要弹窗提示 | |||
| AppConstants.LESSON_TYPE_COMPOSITION_KNOWLEDGE -> when{ | |||
| @@ -0,0 +1,5 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <item android:state_checked="false" android:color = "#8A8A99"/> | |||
| <item android:state_checked="true" android:color = "#5082E6"/> | |||
| </selector> | |||
| @@ -0,0 +1,13 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <item android:state_checked="true"> | |||
| <shape android:shape="rectangle"> | |||
| <solid android:color="@color/white"/> | |||
| </shape> | |||
| </item> | |||
| <item android:state_checked="false"> | |||
| <shape android:shape="rectangle"> | |||
| <solid android:color="@color/white_1"/> | |||
| </shape> | |||
| </item> | |||
| </selector> | |||
| @@ -1,9 +1,174 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| <layout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| tools:context=".module.learn.LearnCVideoActivity"> | |||
| xmlns:tools="http://schemas.android.com/tools"> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| <data> | |||
| </data> | |||
| <LinearLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:orientation="vertical" | |||
| tools:context=".module.learn.LearnCVideoActivity" | |||
| android:background="@color/white"> | |||
| <RelativeLayout | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content"> | |||
| <!--播放器--> | |||
| <com.xkl.videoplayer.widget.PineMediaPlayerView | |||
| android:id="@+id/video_view" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="202dp" | |||
| android:layout_alignParentTop="true" | |||
| android:background="#121010" | |||
| android:gravity="center" /> | |||
| <!--返回键,视频加载失败--> | |||
| <RelativeLayout | |||
| android:id="@+id/stop_view" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="202dp" | |||
| android:background="@color/main_text_color" | |||
| android:visibility="gone" | |||
| tools:visibility="visible"> | |||
| <ImageView | |||
| android:id="@+id/stop_back" | |||
| android:layout_width="20dp" | |||
| android:layout_height="20dp" | |||
| android:layout_marginLeft="5dp" | |||
| android:layout_marginTop="20dp" | |||
| android:src="@drawable/pine_player_go_back_icon" /> | |||
| <!--加载失败--> | |||
| <LinearLayout | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_centerHorizontal="true" | |||
| android:layout_centerVertical="true" | |||
| android:orientation="vertical"> | |||
| <TextView | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_centerHorizontal="true" | |||
| android:layout_marginBottom="18dp" | |||
| android:drawableLeft="@mipmap/sigh_icon" | |||
| android:drawablePadding="8dp" | |||
| android:text="加载失败~" | |||
| android:textColor="#fff7874f" | |||
| android:textSize="14sp" /> | |||
| <TextView | |||
| android:id="@+id/reload_btn" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_gravity="center_horizontal" | |||
| android:padding="10dp" | |||
| android:text="重新加载" | |||
| android:textColor="#fff" /> | |||
| </LinearLayout> | |||
| </RelativeLayout> | |||
| <!--全屏加载失败界面--> | |||
| <RelativeLayout | |||
| android:id="@+id/stop_view_full" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:background="@color/main_text_color" | |||
| android:visibility="gone"> | |||
| <ImageView | |||
| android:id="@+id/stop_back_full" | |||
| android:layout_width="20dp" | |||
| android:layout_height="20dp" | |||
| android:layout_marginLeft="5dp" | |||
| android:layout_marginTop="20dp" | |||
| android:src="@drawable/pine_player_go_back_icon" /> | |||
| <LinearLayout | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_centerHorizontal="true" | |||
| android:layout_centerVertical="true" | |||
| android:orientation="vertical"> | |||
| <TextView | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_centerHorizontal="true" | |||
| android:layout_marginBottom="18dp" | |||
| android:drawableLeft="@mipmap/sigh_icon" | |||
| android:drawablePadding="8dp" | |||
| android:text="加载失败~" | |||
| android:textColor="#fff7874f" | |||
| android:textSize="14sp" /> | |||
| <TextView | |||
| android:id="@+id/reload_btn_full" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_gravity="center_horizontal" | |||
| android:padding="10dp" | |||
| android:text="重新加载" | |||
| android:textColor="#fff" | |||
| /> | |||
| </LinearLayout> | |||
| </RelativeLayout> | |||
| </RelativeLayout> | |||
| <RadioGroup | |||
| android:id="@+id/radio_group" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="38dp" | |||
| android:orientation="horizontal"> | |||
| <com.suliang.common.widget.MyRadioButton | |||
| android:id="@+id/rb_video_blackboard" | |||
| android:layout_width="0dp" | |||
| android:layout_height="match_parent" | |||
| android:layout_weight="1" | |||
| android:background="@drawable/video_tab_bg" | |||
| android:button="@null" | |||
| android:checked="true" | |||
| android:drawableStart="@drawable/ic_video_blackboard" | |||
| android:drawablePadding="5dp" | |||
| android:gravity="center|center_vertical" | |||
| android:text="视频板书" | |||
| android:textColor="@color/video_tab_color" | |||
| android:drawableTint="@color/video_tab_color"/> | |||
| <com.suliang.common.widget.MyRadioButton | |||
| android:id="@+id/rb_video_note" | |||
| android:layout_width="0dp" | |||
| android:layout_height="match_parent" | |||
| android:layout_weight="1" | |||
| android:background="@drawable/video_tab_bg" | |||
| android:button="@null" | |||
| android:checked="false" | |||
| android:drawableStart="@drawable/ic_video_note" | |||
| android:drawablePadding="5dp" | |||
| android:gravity="center|center_vertical" | |||
| android:text="我的笔记" | |||
| android:textColor="@color/video_tab_color" | |||
| android:drawableTint="@color/video_tab_color"/> | |||
| </RadioGroup> | |||
| <!--列表--> | |||
| <androidx.recyclerview.widget.RecyclerView | |||
| android:id="@+id/recyclerview" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:padding="@dimen/global_spacing" /> | |||
| </LinearLayout> | |||
| </layout> | |||
| @@ -14,7 +14,7 @@ | |||
| android:background="@drawable/shape_rounder_toplr_24_white" | |||
| tools:showIn="@layout/activity_course_main"> | |||
| <com.suliang.common.widget.TextViewCenterDrawable | |||
| <TextView | |||
| android:id="@+id/tab_discern" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="match_parent" | |||
| @@ -44,7 +44,7 @@ | |||
| android:visibility="gone" /> | |||
| <com.suliang.common.widget.TextViewCenterDrawable | |||
| <TextView | |||
| android:id="@+id/tab_spell" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="match_parent" | |||
| @@ -73,7 +73,7 @@ | |||
| android:src="@drawable/ic_arrow_right" | |||
| android:visibility="gone" /> | |||
| <com.suliang.common.widget.TextViewCenterDrawable | |||
| <TextView | |||
| android:id="@+id/tab_voice" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="match_parent" | |||
| @@ -7,8 +7,11 @@ | |||
| </data> | |||
| <com.google.android.material.imageview.ShapeableImageView | |||
| android:id="@+id/iv_photo" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:scaleType="fitXY" | |||
| app:shapeAppearance="@style/roundedCornerStyle" /> | |||
| app:shapeAppearance="@style/roundedCornerStyle" | |||
| android:layout_margin="4dp" | |||
| /> | |||
| </layout> | |||
| @@ -0,0 +1,25 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <layout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools"> | |||
| <data> | |||
| </data> | |||
| <com.suliang.common.widget.TextViewDrawableTopCenterWithFirst | |||
| android:id="@+id/tv_value" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginBottom="@dimen/global_spacing" | |||
| android:drawableStart="@drawable/ic_note" | |||
| android:drawablePadding="8dp" | |||
| android:text="1.高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情高效做好重要和紧急的事情,腾出大量时间做重要不尽快做好紧急不重要的事情,避免不重要不紧急的事情" | |||
| android:textSize="@dimen/smallSize" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| android:includeFontPadding="false"/> | |||
| </layout> | |||
| @@ -45,4 +45,6 @@ | |||
| <item name="android:backgroundDimEnabled">true</item> <!--模糊,背景透明是这个- 弹窗背景是否变暗 --> | |||
| </style> | |||
| </resources> | |||
| @@ -30,6 +30,10 @@ | |||
| <item name="android:windowBackground">@drawable/theme_splash_bg</item> | |||
| </style> | |||
| <style name="Theme.videoTheme" parent="Theme.XklLocal"> | |||
| <item name="android:windowIsTranslucent">false</item> <!--影响了视频切换后横屏的有白边问题--> | |||
| </style> | |||
| <style name="Theme.XklLocal.NoActionBar"> | |||
| <item name="windowActionBar">false</item> | |||
| <item name="windowNoTitle">true</item> | |||
| @@ -39,4 +43,6 @@ | |||
| <style name="Theme.XklLocal.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> | |||
| </resources> | |||
| @@ -1,6 +1,6 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="20dp" | |||
| android:height="20dp" | |||
| android:width="16dp" | |||
| android:height="16dp" | |||
| android:viewportWidth="20" | |||
| android:viewportHeight="20"> | |||
| <path | |||
| @@ -0,0 +1,12 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="16dp" | |||
| android:height="16dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:pathData="M12,2.4C17.3019,2.4 21.6,6.6981 21.6,12C21.6,17.3019 17.3019,21.6 12,21.6C6.6981,21.6 2.4,17.3019 2.4,12C2.4,6.6981 6.6981,2.4 12,2.4ZM14.7646,7.1732C14.7787,6.9677 14.4863,6.8579 14.3709,7.0604L14.3709,7.0604L11.9821,11.2439L10.5884,11.5498L10.5526,11.4079C10.5255,11.3137 10.492,11.2203 10.4509,11.1282C9.9592,10.0103 8.6539,9.5029 7.5351,9.9937C6.4163,10.4844 5.9099,11.7907 6.4025,12.9096L6.4025,12.9096L6.4712,13.0519C7.0074,14.0697 8.2482,14.5135 9.3183,14.0441L9.3183,14.0441L10.7386,13.4198L9.9686,14.7663L9.892,14.9126C9.3976,15.948 9.7791,17.2054 10.7921,17.7847C11.8525,18.3906 13.2028,18.0215 13.8086,16.9611C14.4145,15.9008 14.0464,14.5496 12.9851,13.9427L12.9851,13.9427L12.8527,13.8724C12.7634,13.829 12.6724,13.7926 12.5802,13.762L12.5802,13.762L13.0297,12.4119L17.4413,10.4704L17.4923,10.4401C17.654,10.3127 17.5387,10.0224 17.3111,10.0727L17.3111,10.0727L13.5322,10.9031L14.7528,7.2315ZM11.1325,15.4314C11.3715,15.0135 11.903,14.8686 12.3219,15.1067C12.7398,15.3457 12.8847,15.8772 12.6466,16.2961C12.4076,16.714 11.8761,16.8589 11.4572,16.6208C11.0384,16.3808 10.8935,15.8493 11.1325,15.4314ZM8.0768,11.2209C8.5174,11.0268 9.0317,11.2275 9.2257,11.6681C9.4197,12.1087 9.2191,12.623 8.7785,12.817C8.3379,13.011 7.8236,12.8104 7.6296,12.3698C7.4356,11.9292 7.6362,11.4149 8.0768,11.2209Z" | |||
| android:strokeWidth="1" | |||
| android:fillColor="#8A8A99" | |||
| android:fillType="evenOdd" | |||
| android:strokeColor="#00000000"/> | |||
| </vector> | |||
| @@ -0,0 +1,12 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="19dp" | |||
| android:height="18dp" | |||
| android:viewportWidth="19" | |||
| android:viewportHeight="18"> | |||
| <path | |||
| android:pathData="M9.0843,1.3757C9.3205,1.1414 9.7035,1.1414 9.9397,1.3757L9.9397,1.3757L12.7864,4.199L14.9552,4.2C15.9072,4.2 16.6879,4.9273 16.7636,5.8524L16.7696,6L16.7696,13.2C16.7696,14.1941 15.9573,15 14.9552,15L14.9552,15L4.0688,15C3.0667,15 2.2544,14.1941 2.2544,13.2L2.2544,13.2L2.2544,6C2.2544,5.0059 3.0667,4.2 4.0688,4.2L4.0688,4.2L6.2364,4.199ZM6.4757,5.3999L4.0688,5.4C3.7348,5.4 3.464,5.6686 3.464,6L3.464,6L3.464,13.2C3.464,13.5314 3.7348,13.8 4.0688,13.8L4.0688,13.8L14.9552,13.8C15.2892,13.8 15.56,13.5314 15.56,13.2L15.56,13.2L15.56,6C15.56,5.6686 15.2892,5.4 14.9552,5.4L14.9552,5.4L12.5474,5.399L12.536,5.4L6.488,5.4C6.4839,5.4 6.4798,5.4 6.4757,5.3999ZM9.2096,10.2C9.5436,10.2 9.8144,10.4686 9.8144,10.8C9.8144,11.1077 9.5809,11.3613 9.2801,11.396L9.2096,11.4L5.5808,11.4C5.2468,11.4 4.976,11.1314 4.976,10.8C4.976,10.4923 5.2095,10.2387 5.5103,10.204L5.5808,10.2L9.2096,10.2ZM13.4432,7.5C13.7772,7.5 14.048,7.7686 14.048,8.1C14.048,8.4077 13.8145,8.6613 13.5137,8.696L13.4432,8.7L5.5808,8.7C5.2468,8.7 4.976,8.4314 4.976,8.1C4.976,7.7923 5.2095,7.5387 5.5103,7.504L5.5808,7.5L13.4432,7.5ZM9.512,2.649L7.9484,4.199L11.0744,4.199L9.512,2.649Z" | |||
| android:strokeWidth="1" | |||
| android:fillColor="#5082E6" | |||
| android:fillType="evenOdd" | |||
| android:strokeColor="#00000000"/> | |||
| </vector> | |||
| @@ -0,0 +1,12 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="20dp" | |||
| android:height="18dp" | |||
| android:viewportWidth="20" | |||
| android:viewportHeight="18"> | |||
| <path | |||
| android:pathData="M16.4602,13.9729C16.7942,13.9729 17.065,14.2415 17.065,14.5729C17.065,14.8806 16.8316,15.1342 16.5308,15.1688L16.4602,15.1729L4.3642,15.1729C4.0302,15.1729 3.7594,14.9042 3.7594,14.5729C3.7594,14.2652 3.9929,14.0116 4.2937,13.9769L4.3642,13.9729L16.4602,13.9729ZM11.2022,2.1485C11.9051,1.4456 13.0494,1.4501 13.7579,2.1587L13.7579,2.1587L15.0409,3.4417C15.7495,4.1503 15.754,5.2945 15.0511,5.9975L15.0511,5.9975L8.9655,12.0831C8.6942,12.3543 8.3427,12.5311 7.9622,12.5877L7.9622,12.5877L5.6457,12.9322C4.9883,13.0299 4.3719,12.5756 4.2689,11.9174C4.2501,11.7969 4.2496,11.6743 4.2675,11.5539L4.2675,11.5539L4.6119,9.2374C4.6685,8.8569 4.8453,8.5054 5.1166,8.2341L5.1166,8.2341ZM12.9094,3.0072C12.6732,2.7711 12.2918,2.7695 12.0575,3.0038L12.0575,3.0038L5.9719,9.0895C5.8815,9.1799 5.8225,9.297 5.8037,9.4239L5.8037,9.4239L5.4592,11.7404L7.7758,11.396C7.9026,11.3771 8.0198,11.3182 8.1102,11.2277L8.1102,11.2277L14.1958,5.1421C14.4301,4.9078 14.4286,4.5264 14.1924,4.2902L14.1924,4.2902Z" | |||
| android:strokeWidth="1" | |||
| android:fillColor="#5082E6" | |||
| android:fillType="evenOdd" | |||
| android:strokeColor="#00000000"/> | |||
| </vector> | |||
| @@ -81,11 +81,11 @@ abstract class BaseRVAdapter<T> : | |||
| return DataBindingUtil.inflate(LayoutInflater.from(parent.context),layoutRes,parent,false) | |||
| } | |||
| @Synchronized | |||
| fun getItem(position: Int) : T { | |||
| open fun getItem(position: Int) : T { | |||
| return mData.get(position) | |||
| } | |||
| fun getData(): MutableList<T>{ | |||
| return mData | |||
| } | |||
| @@ -1,9 +1,9 @@ | |||
| package com.suliang.common.util | |||
| import android.annotation.SuppressLint | |||
| import android.text.TextUtils | |||
| import java.text.SimpleDateFormat | |||
| import java.util.* | |||
| import java.util.logging.SimpleFormatter | |||
| /** | |||
| * author suliang | |||
| @@ -16,7 +16,7 @@ class DateUtil { | |||
| val FORMAT_1 = "yyyy-MM-dd HH:mm:ss" | |||
| val FORMAT_2 = "HH:mm:ss" | |||
| val FORMAT_3 = "HH时mm分ss" | |||
| /** | |||
| * 设置时区从0开始的,正时,可格式化时分秒 | |||
| * @param formatValue Long 需要格式化的值 | |||
| @@ -25,14 +25,13 @@ class DateUtil { | |||
| */ | |||
| @SuppressLint("SimpleDateFormat") | |||
| @JvmStatic | |||
| fun formatGMT(formatValue : Long, format : String): String { | |||
| return SimpleDateFormat(format).apply { | |||
| fun formatGMT(formatValue : Long, format : String) : String { | |||
| return SimpleDateFormat(format).apply { | |||
| timeZone = TimeZone.getTimeZone("GMT+00:00") | |||
| }.format(formatValue) | |||
| } | |||
| /** | |||
| * 格式化时间戳为字符串样式 | |||
| * @param formatValue Long 时间戳 | |||
| @@ -41,10 +40,10 @@ class DateUtil { | |||
| */ | |||
| @SuppressLint("SimpleDateFormat") | |||
| @JvmStatic | |||
| fun format (formatValue : Long, format:String):String{ | |||
| fun format(formatValue : Long, format : String) : String { | |||
| return SimpleDateFormat(format).format(formatValue) | |||
| } | |||
| /** | |||
| * 获取时间段 | |||
| * @param timeForLong 毫秒时间 | |||
| @@ -63,7 +62,58 @@ class DateUtil { | |||
| else -> 4 | |||
| } | |||
| } | |||
| /** | |||
| * 将字符串格式的时间,转为秒数 | |||
| * @param string String 字符串格式 HH:mm:ss | |||
| * @return Long 秒数 | |||
| */ | |||
| fun getTimeSecond(time_point : String) : Long { | |||
| var hourSecond = 0 | |||
| var minuteSecond = 0 | |||
| var second = 0 | |||
| if (!TextUtils.isEmpty(time_point)) { | |||
| val split = time_point.split(":").map { | |||
| it.toInt() | |||
| } | |||
| when (split.size) { | |||
| 1 -> second = split[0] | |||
| 2 -> { | |||
| minuteSecond = split[0] * 60 | |||
| second = split[1] | |||
| } | |||
| 3 -> { | |||
| hourSecond = split[0] * 3600 | |||
| minuteSecond = split[1] * 60 | |||
| second = split[2] | |||
| } | |||
| } | |||
| } | |||
| return (hourSecond + minuteSecond + second).toLong() | |||
| } | |||
| /** | |||
| * 秒数格式化 00:00:00 00:00 | |||
| * @param second 秒数 | |||
| * @return | |||
| */ | |||
| fun formatSeconds(second : Long) : String { | |||
| return when { | |||
| second <= 0 -> { | |||
| "00:00" | |||
| } | |||
| second < 60 -> { | |||
| String.format(Locale.getDefault(), "00:%02d", second) | |||
| } | |||
| second < 3600 -> { | |||
| String.format(Locale.getDefault(), "%02d:%02d", second / 60, second % 60) | |||
| } | |||
| else -> { | |||
| String.format(Locale.getDefault(), "%02d:%02d:%02d", second / 3600, second % 3600 / 60, second % 60) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -7,6 +7,7 @@ import android.graphics.Canvas | |||
| import android.graphics.PixelFormat | |||
| import android.graphics.drawable.BitmapDrawable | |||
| import android.graphics.drawable.Drawable | |||
| import android.widget.TextView | |||
| import androidx.annotation.ColorRes | |||
| import androidx.annotation.DrawableRes | |||
| import androidx.annotation.NonNull | |||
| @@ -102,10 +103,21 @@ class DrawableUti { | |||
| val oldBitmap = vectorDrawableCompat.toBitmap(scale*vectorDrawableCompat.intrinsicWidth,scale*vectorDrawableCompat.intrinsicHeight,Bitmap.Config.ARGB_8888) | |||
| return BitmapDrawable(resource,oldBitmap) | |||
| // val newBitmap = Bitmap.createScaledBitmap(oldBitmap,scale*oldBitmap.width,scale*oldBitmap.height,true) | |||
| } | |||
| @JvmStatic | |||
| fun changeTextViewDrawableColor(view: TextView, svgColor:Int){ | |||
| view.compoundDrawables.forEachIndexed{ index: Int, drawable: Drawable? -> | |||
| drawable?.let { | |||
| when(index){ | |||
| 0 -> view.setCompoundDrawablesWithIntrinsicBounds(tintDrawable(it,svgColor),null,null,null) | |||
| 1 -> view.setCompoundDrawablesWithIntrinsicBounds(null,tintDrawable(it,svgColor),null,null) | |||
| 2 -> view.setCompoundDrawablesWithIntrinsicBounds(null,null,tintDrawable(it,svgColor),null) | |||
| 3 -> view.setCompoundDrawablesWithIntrinsicBounds(null,null,null,tintDrawable(it,svgColor)) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -27,6 +27,10 @@ object LogUtil { | |||
| fun e(message: String) { | |||
| log("e",message) | |||
| } | |||
| fun e(tag:String,message: String) { | |||
| log("e", "$tag $message") | |||
| } | |||
| fun i(message: String) { | |||
| log("i",message) | |||
| } | |||
| @@ -44,7 +44,7 @@ object AppExecutors { | |||
| }, new RejectedExecutionHandler() { | |||
| @Override | |||
| public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { | |||
| CommonLog.e(TAG,"rejectedExecution: scheduled executor queue overflow"); | |||
| LogUtil.e(TAG,"rejectedExecution: scheduled executor queue overflow"); | |||
| } | |||
| }); | |||
| }*/ | |||
| @@ -62,7 +62,7 @@ object AppExecutors { | |||
| }, new RejectedExecutionHandler() { | |||
| @Override | |||
| public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { | |||
| CommonLog.e(TAG,"rejectedExecution: diskIo executor queue overflow"); | |||
| LogUtil.e(TAG,"rejectedExecution: diskIo executor queue overflow"); | |||
| } | |||
| }); | |||
| } | |||
| @@ -78,7 +78,7 @@ object AppExecutors { | |||
| }, new RejectedExecutionHandler() { | |||
| @Override | |||
| public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { | |||
| CommonLog.e(TAG,"rejectedExecution: network executor queue overflow"); | |||
| LogUtil.e(TAG,"rejectedExecution: network executor queue overflow"); | |||
| } | |||
| }); | |||
| } | |||
| @@ -0,0 +1,51 @@ | |||
| package com.suliang.common.widget; | |||
| import android.content.Context; | |||
| import android.graphics.Canvas; | |||
| import android.graphics.drawable.Drawable; | |||
| import android.util.AttributeSet; | |||
| import androidx.annotation.Nullable; | |||
| /** | |||
| * author suliang | |||
| * create 2021/4/12 9:27 | |||
| * Describe: 自定义 RadioButton 左侧图标紧挨着文字 | |||
| */ | |||
| public class MyRadioButton extends androidx.appcompat.widget.AppCompatRadioButton { | |||
| public MyRadioButton(Context context) { | |||
| super(context); | |||
| } | |||
| public MyRadioButton(Context context, @Nullable AttributeSet attrs) { | |||
| super(context, attrs); | |||
| } | |||
| public MyRadioButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |||
| super(context, attrs, defStyleAttr); | |||
| } | |||
| @Override | |||
| protected void onDraw(Canvas canvas) { | |||
| Drawable[] compoundDrawables = getCompoundDrawables(); | |||
| //左侧Drawable | |||
| Drawable drawableLeft = compoundDrawables[0]; | |||
| if(drawableLeft != null){ | |||
| float textWidth = getPaint().measureText(getText().toString()); //文字宽度 | |||
| int drawableWidth = drawableLeft.getIntrinsicWidth() ; //drawable图片宽度 | |||
| int drawableHeight = drawableLeft.getIntrinsicHeight() ; //drawable高度 | |||
| int drawablePadding = getCompoundDrawablePadding(); //设置的间距 | |||
| int left = (int) ((this.getWidth() - drawableWidth - drawablePadding - textWidth) / 2); | |||
| drawableLeft.setBounds(left, 0, left + drawableWidth, drawableHeight); //设置drawableLeft区域 | |||
| } | |||
| //指定drawable显示的大小 | |||
| setCompoundDrawables(drawableLeft, compoundDrawables[1], compoundDrawables[2], compoundDrawables[3]); | |||
| super.onDraw(canvas); | |||
| } | |||
| } | |||
| @@ -8,6 +8,9 @@ import android.graphics.drawable.Drawable; | |||
| import android.util.AttributeSet; | |||
| import android.widget.TextView; | |||
| /** | |||
| * 文字与drawable都居中的TextView | |||
| */ | |||
| @SuppressLint("AppCompatCustomView") | |||
| public class TextViewCenterDrawable extends TextView { | |||
| @@ -0,0 +1,40 @@ | |||
| package com.suliang.common.widget | |||
| import android.content.Context | |||
| import android.graphics.Canvas | |||
| import android.util.AttributeSet | |||
| import androidx.appcompat.widget.AppCompatTextView | |||
| import java.lang.reflect.Constructor | |||
| import android.graphics.drawable.Drawable | |||
| /** | |||
| * author suliang | |||
| * create 2021/11/17 16:28 | |||
| * Describe: left right drawable 与第一行居中的TextView | |||
| * https://www.jianshu.com/p/27b823faf3ee | |||
| */ | |||
| class TextViewDrawableTopCenterWithFirst @JvmOverloads constructor(context : Context, | |||
| attr : AttributeSet?, | |||
| defStyleAttr : Int = 0) : | |||
| AppCompatTextView(context, attr, defStyleAttr) { | |||
| override fun onLayout(changed : Boolean, left : Int, top : Int, right : Int, bottom : Int) { | |||
| super.onLayout(changed, left, top, right, bottom) | |||
| handleLeftDrawable() | |||
| } | |||
| private fun handleLeftDrawable() { | |||
| compoundDrawables[0]?.let { | |||
| //实际行数 | |||
| val lineCount = Math.min(lineCount, maxLines) | |||
| //文本高度 | |||
| val vSpace = bottom - top - compoundPaddingBottom - compoundPaddingTop | |||
| //计算位置差值 | |||
| val verticalOffset : Int = (-1 * (vSpace * (1 - 1.0f / lineCount)) / 2).toInt() | |||
| //重设bounds | |||
| it.setBounds(0, verticalOffset, it.intrinsicWidth, it.intrinsicHeight + verticalOffset) | |||
| } | |||
| } | |||
| } | |||
| @@ -12,3 +12,4 @@ dependencyResolutionManagement { | |||
| rootProject.name = "XklLocal" | |||
| include ':app' | |||
| include ':lib:common' | |||
| include ':videoplayer' | |||
| @@ -0,0 +1,10 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <resources> | |||
| <!--圆角图片--> | |||
| <style name="roundedCornerStyle"> | |||
| <item name="cornerFamily">rounded</item> | |||
| <item name="cornerSize">4dp</item> | |||
| </style> | |||
| </resources> | |||