Browse Source

悬浮词典实现

master
suliang 2 years ago
parent
commit
9b6a24ea88
35 changed files with 957 additions and 153 deletions
  1. 0
    24
      .idea/codeStyles/Project.xml
  2. 11
    0
      .idea/misc.xml
  3. 1
    1
      app/build.gradle
  4. 7
    0
      app/src/main/AndroidManifest.xml
  5. 18
    74
      app/src/main/java/com/xkl/cdl/data/exam_record/AppDatabase.kt
  6. 2
    0
      app/src/main/java/com/xkl/cdl/data/exam_record/DictionaryDao.kt
  7. 65
    0
      app/src/main/java/com/xkl/cdl/data/manager/CourseManager.kt
  8. 1
    1
      app/src/main/java/com/xkl/cdl/data/manager/UserInfoManager.kt
  9. 5
    2
      app/src/main/java/com/xkl/cdl/data/repository/DataRepository.kt
  10. 11
    1
      app/src/main/java/com/xkl/cdl/dialog/CommonDialog.kt
  11. 46
    0
      app/src/main/java/com/xkl/cdl/module/XKLApplication.kt
  12. 225
    0
      app/src/main/java/com/xkl/cdl/module/floating/DictionaryFloatingSearchActivity.kt
  13. 176
    0
      app/src/main/java/com/xkl/cdl/module/floating/DictionaryFloatingWindowManager.kt
  14. 15
    2
      app/src/main/java/com/xkl/cdl/module/learn/LearnExamViewModel.kt
  15. 9
    0
      app/src/main/java/com/xkl/cdl/module/learn/LearnWordViewModel.kt
  16. 37
    36
      app/src/main/java/com/xkl/cdl/module/m_memo/MemoFragmentViewModel.kt
  17. 14
    0
      app/src/main/java/com/xkl/cdl/module/m_memo/MemoListDetailViewModel.kt
  18. 11
    2
      app/src/main/java/com/xkl/cdl/module/m_service_center/DictionaryViewModel.kt
  19. 2
    1
      app/src/main/java/com/xkl/cdl/module/m_service_center/TestScoreActivity.kt
  20. 4
    1
      app/src/main/java/com/xkl/cdl/module/m_statics/StaticsFragment.kt
  21. 42
    0
      app/src/main/java/com/xkl/cdl/module/main/MainActivity.kt
  22. 5
    1
      app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt
  23. 6
    0
      app/src/main/res/drawable/shape_rounder_bottomlr_16_solider_white.xml
  24. 1
    0
      app/src/main/res/layout/activity_dictionary.xml
  25. 131
    0
      app/src/main/res/layout/activity_dictionary_floating_search.xml
  26. 5
    4
      app/src/main/res/layout/activity_test_detail.xml
  27. 3
    1
      app/src/main/res/layout/dialog_common.xml
  28. 39
    0
      app/src/main/res/layout/dictionary_floating_layout.xml
  29. 3
    0
      app/src/main/res/values/strings.xml
  30. 14
    1
      app/src/main/res/values/styles.xml
  31. 12
    0
      app/svg/drawable/ic_dictionary_floating.xml
  32. 4
    0
      lib/common/src/main/java/com/suliang/common/base/viewmodel/BaseViewModel.kt
  33. 23
    0
      lib/common/src/main/java/com/suliang/common/util/ActivityStackManager.kt
  34. 1
    1
      lib/common/src/main/java/com/suliang/common/util/os/FloatingWindowUtil.kt
  35. 8
    0
      lib/common/src/main/java/com/suliang/common/util/os/IntentUtils.kt

+ 0
- 24
.idea/codeStyles/Project.xml View File

@@ -40,30 +40,6 @@
<option name="IF_RPAREN_ON_NEW_LINE" value="false" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>

+ 11
- 0
.idea/misc.xml View File

@@ -228,6 +228,17 @@
<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" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.1" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/drawable/shape_rounder_bottomlr_16_solider_white.xml" value="0.203125" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/drawable/shape_rounder_bottomlr_8_solider_white_1.xml" value="0.203125" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/layout/activity_dictionary.xml" value="0.1933876811594203" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/layout/activity_dictionary_floating_search.xml" value="0.75" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/layout/activity_learn_exam.xml" value="0.21666666666666667" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/layout/activity_test_detail.xml" value="0.21666666666666667" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/layout/dialog_common.xml" value="0.33" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/layout/dictionary_floating_layout.xml" value="0.5" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/layout/fragment_course_pack.xml" value="0.3338541666666667" />
<entry key="..\:/xuekaole/XKLLocal/app/src/main/res/layout/fragment_learn_center.xml" value="0.3338541666666667" />
<entry key="..\:/xuekaole/XKLLocal/lib/common/src/main/res/drawable/ic_search.xml" value="0.2212962962962963" />
</map>
</option>
</component>

+ 1
- 1
app/build.gradle View File

@@ -150,5 +150,5 @@ dependencies {
// implementation "me.zhanghai.android.materialratingbar:library:1.4.0"
implementation 'com.xl.ratingbar:ratingbar:0.1.1'
implementation 'com.github.HuanTanSheng:EasyPhotos:3.1.5'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}

+ 7
- 0
app/src/main/AndroidManifest.xml View File

@@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.xkl.cdl">

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

<application
android:name=".module.XKLApplication"
android:allowBackup="true"
@@ -12,6 +14,11 @@
android:supportsRtl="true"
android:theme="@style/Theme.XklLocal"
tools:ignore="LockedOrientationActivity">
<activity
android:name=".module.floating.DictionaryFloatingSearchActivity"
android:theme="@style/DialogActivityTheme"
android:launchMode="singleTask"
android:exported="true" />
<activity
android:name=".module.m_my.CacheClearActivity"
android:exported="true" />

+ 18
- 74
app/src/main/java/com/xkl/cdl/data/exam_record/AppDatabase.kt View File

@@ -16,7 +16,7 @@ import java.io.File
* create 2022/7/7 16:50
* Describe:
*/
@Database(entities = [Exam::class,ExamItem::class,DictionaryItem::class,Collect::class], version = 3, exportSchema = true)
@Database(entities = [Exam::class,ExamItem::class,DictionaryItem::class,Collect::class], version = 1, exportSchema = true)
abstract class AppDatabase : RoomDatabase() {
abstract fun examDao() : ExamDao
abstract fun examItemDao(): ExamItemDao
@@ -26,80 +26,24 @@ abstract class AppDatabase : RoomDatabase() {
companion object{
private var DATABASE_NAME :String = "${FileUtil.getSaveDirPath("db")}${File.separator}app.db"
private val MIGRATION_1_2 = object :Migration(1,2){
override fun migrate(database : SupportSQLiteDatabase) {
//版本升级策略,增加一个dictionary表
database.execSQL("CREATE TABLE IF NOT EXISTS `dic_history` (`courseId` INTEGER NOT NULL, `queryTime` INTEGER NOT NULL, `id` INTEGER NOT NULL, `word` TEXT NOT NULL, `form` INTEGER NOT NULL, `pre_index` TEXT, `phonetic_uk` TEXT, `phonectic_us` TEXT, `phonectic_cn` TEXT, `basic_explaination` TEXT, `all_explaination` TEXT, `phrase` TEXT, `example` TEXT, `reference` TEXT, PRIMARY KEY(`word`))")
}
}
private val MIGRATION_2_3 = object :Migration(2,3){
override fun migrate(database : SupportSQLiteDatabase) {
//版本升级策略,增加一个dictionary表
database.execSQL("CREATE TABLE IF NOT EXISTS `c_collect` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `courseId` INTEGER NOT NULL, `chapterId` INTEGER NOT NULL, `wordId` INTEGER NOT NULL, `lessonType` INTEGER NOT NULL)")
}
}
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Room.databaseBuilder(XKLApplication.instance(), AppDatabase::class.java, DATABASE_NAME)
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3).build()
}
@JvmStatic
fun examBuilderToExamWithDetail(learnExam : Struct.LearnExam) : ExamWithDetail {
val exam = learnExam.run {
Exam(projectId.toInt(), packId, courseId, chapterId, lessonId, title, type.toInt(), score, totalNum.toInt(),
correctNum.toInt(), errorNum.toInt(), unanswerNum.toInt(), typeId, duration, ext, created, coverRate.toInt(),
stage)
}
val examItemList = mutableListOf<ExamItem>().apply {
learnExam.recordList.forEach {
add(ExamItem(it.questionId, it.questionType.toInt(), it.question, it.userAnswer, it.answerStatus.toInt(),
it.duration))
}
}
return ExamWithDetail(exam, examItemList)
}
@JvmStatic
fun examWithDetailToExamBuild(examWithDetail : ExamWithDetail) : Struct.LearnExam {
val examRecordList = mutableListOf<Struct.ExamRecord>().apply {
examWithDetail.examItemList.forEach {
add(Struct.ExamRecord.newBuilder().apply {
questionId = it.questionId
questionType = it.questionType.toLong()
question = it.question
userAnswer = it.userAnswer
answerStatus = it.answerStatus.toLong()
duration = it.duration
}.build())
}
}
return Struct.LearnExam.newBuilder().apply {
examWithDetail.exam.let {
projectId = it.projectId.toLong()
packId = it.packId
courseId = it.courseId
chapterId = it.chapterId
lessonId = it.lessonId
title = it.title
type = it.examType.toLong()
score = it.score
totalNum = it.totalNum.toLong()
correctNum = it.correctNum.toLong()
errorNum = it.errorNum.toLong()
unanswerNum = it.unAnswerNum.toLong()
duration = it.duration
ext = it.ext
created = it.createTime
typeId = it.typeId
coverRate = it.coverRate.toFloat()
stage = it.stage
}
addAllRecord(examRecordList)
}.build()
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Room.databaseBuilder(XKLApplication.instance(), AppDatabase::class.java, DATABASE_NAME).build()
// .addMigrations(MIGRATION_1_2)
// .addMigrations(MIGRATION_2_3).build()
}
// private val MIGRATION_1_2 = object :Migration(1,2){
// override fun migrate(database : SupportSQLiteDatabase) {
// //版本升级策略,增加一个dictionary表
// database.execSQL("CREATE TABLE IF NOT EXISTS `dic_history` (`courseId` INTEGER NOT NULL, `queryTime` INTEGER NOT NULL, `id` INTEGER NOT NULL, `word` TEXT NOT NULL, `form` INTEGER NOT NULL, `pre_index` TEXT, `phonetic_uk` TEXT, `phonectic_us` TEXT, `phonectic_cn` TEXT, `basic_explaination` TEXT, `all_explaination` TEXT, `phrase` TEXT, `example` TEXT, `reference` TEXT, PRIMARY KEY(`word`))")
// }
// }
// private val MIGRATION_2_3 = object :Migration(2,3){
// override fun migrate(database : SupportSQLiteDatabase) {
// //版本升级策略,增加一个dictionary表
// database.execSQL("CREATE TABLE IF NOT EXISTS `c_collect` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `courseId` INTEGER NOT NULL, `chapterId` INTEGER NOT NULL, `wordId` INTEGER NOT NULL, `lessonType` INTEGER NOT NULL)")
// }
// }
}
}

+ 2
- 0
app/src/main/java/com/xkl/cdl/data/exam_record/DictionaryDao.kt View File

@@ -18,5 +18,7 @@ interface DictionaryDao {
@Query("DELETE FROM dic_history WHERE courseId = :courseId")
fun clear(courseId: Long)
@Query("DELETE FROM dic_history WHERE courseId = :courseId AND id not in(SELECT id FROM dic_history WHERE courseId =:courseId ORDER BY queryTime DESC LIMIT 30)")
fun clearMore(courseId : Long)
}

+ 65
- 0
app/src/main/java/com/xkl/cdl/data/manager/CourseManager.kt View File

@@ -9,6 +9,10 @@ import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.bean.course.CoursePack
import com.xkl.cdl.data.bean.course.ExamBean
import com.xkl.cdl.data.bean.course.Lesson
import com.xkl.cdl.data.exam_record.Exam
import com.xkl.cdl.data.exam_record.ExamItem
import com.xkl.cdl.data.exam_record.ExamWithDetail
import mqComsumerV1.Struct
import org.json.JSONObject
import java.io.*
import java.lang.Exception
@@ -520,4 +524,65 @@ object CourseManager {
return false
}
/**
* Struct.LearnExam 转为 ExamWithDetail
*/
@JvmStatic
fun examBuilderToExamWithDetail(learnExam : Struct.LearnExam) : ExamWithDetail {
val exam = learnExam.run {
Exam(projectId.toInt(), packId, courseId, chapterId, lessonId, title, type.toInt(), score, totalNum.toInt(),
correctNum.toInt(), errorNum.toInt(), unanswerNum.toInt(), typeId, duration, ext, created, coverRate.toInt(),
stage)
}
val examItemList = mutableListOf<ExamItem>().apply {
learnExam.recordList.forEach {
add(ExamItem(it.questionId, it.questionType.toInt(), it.question, it.userAnswer, it.answerStatus.toInt(),
it.duration))
}
}
return ExamWithDetail(exam, examItemList)
}
/**
* ExamWithDetail 转为 Struct.LearnExam
*/
@JvmStatic
fun examWithDetailToExamBuild(examWithDetail : ExamWithDetail) : Struct.LearnExam {
val examRecordList = mutableListOf<Struct.ExamRecord>().apply {
examWithDetail.examItemList.forEach {
add(Struct.ExamRecord.newBuilder().apply {
questionId = it.questionId
questionType = it.questionType.toLong()
question = it.question
userAnswer = it.userAnswer
answerStatus = it.answerStatus.toLong()
duration = it.duration
}.build())
}
}
return Struct.LearnExam.newBuilder().apply {
examWithDetail.exam.let {
projectId = it.projectId.toLong()
packId = it.packId
courseId = it.courseId
chapterId = it.chapterId
lessonId = it.lessonId
title = it.title
type = it.examType.toLong()
score = it.score
totalNum = it.totalNum.toLong()
correctNum = it.correctNum.toLong()
errorNum = it.errorNum.toLong()
unanswerNum = it.unAnswerNum.toLong()
duration = it.duration
ext = it.ext
created = it.createTime
typeId = it.typeId
coverRate = it.coverRate.toFloat()
stage = it.stage
}
addAllRecord(examRecordList)
}.build()
}
}

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

@@ -24,7 +24,7 @@ object UserInfoManager {
/** 存放用户头像 */
fun putUserHeadPortrait(readFile : File){
AppExecutors.io.run {
AppExecutors.io.execute {
val file = File(FileUtil.getSaveDirFile("user"),"head")
FileUtil.copyFile(readFile,file,false)
SpUtils.instance.encode("head_portrait",file.path)

+ 5
- 2
app/src/main/java/com/xkl/cdl/data/repository/DataRepository.kt View File

@@ -10,6 +10,7 @@ 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.exam_record.*
import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.data.manager.db.DBCourseManager
import com.xkl.cdl.data.manager.db.DbControlBase
import com.xkl.cdl.module.XKLApplication
@@ -140,7 +141,7 @@ object DataRepository {
//如果是测试数据,进行数据转换,并将数据进行保存
if (record.examCount > 0) {
val examWithDetail = AppDatabase.examBuilderToExamWithDetail(record.getExam(0))
val examWithDetail = CourseManager.examBuilderToExamWithDetail(record.getExam(0))
AppDatabase.instance.examMiddleDao().insert(AppDatabase.instance,examWithDetail)
}
@@ -182,7 +183,9 @@ object DataRepository {
return Observable.create {
//近90天词条数统计
val entityCountList = XKLApplication.mobileCache.entityCountList(projectId.toLong(), 0)
val parseFrom = AppApi.EntityCountListResponse.parseFrom(entityCountList)
val parseFrom = entityCountList?.let {
AppApi.EntityCountListResponse.parseFrom(entityCountList)
} ?: AppApi.EntityCountListResponse.newBuilder().build()
it.onNext(parseFrom)
it.onComplete()
}

+ 11
- 1
app/src/main/java/com/xkl/cdl/dialog/CommonDialog.kt View File

@@ -2,12 +2,14 @@ package com.xkl.cdl.dialog

import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.Gravity
import android.view.View
import androidx.core.content.ContextCompat
import com.suliang.common.AppConfig
import com.suliang.common.base.BaseDialogFragment
import com.suliang.common.extension.click
import com.suliang.common.util.DrawableUti
import com.suliang.common.util.os.ScreenUtil
import com.xkl.cdl.databinding.DialogCommonBinding

/**
@@ -95,5 +97,13 @@ class CommonDialog private constructor() : BaseDialogFragment<DialogCommonBindin
}
override fun resizeDialog(){}
override fun resizeDialog(){
//设置dialog位置
dialog?.window?.let {
it.attributes.run {
this.width = ScreenUtil.dp2px(300f).toInt()
it.attributes = this
}
}
}
}

+ 46
- 0
app/src/main/java/com/xkl/cdl/module/XKLApplication.kt View File

@@ -1,9 +1,14 @@
package com.xkl.cdl.module

import android.app.Activity
import android.app.Application
import android.os.Bundle
import com.suliang.common.util.ActivityStackManager
import com.suliang.common.util.LogUtil
import com.suliang.common.util.file.FileUtil
import com.tencent.mmkv.MMKV
import com.xkl.cdl.module.floating.DictionaryFloatingWindowManager
import com.xkl.cdl.module.main.MainActivity
import io.reactivex.rxjava3.exceptions.UndeliverableException
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.plugins.RxJavaPlugins
@@ -42,6 +47,7 @@ class XKLApplication : Application() {
LogUtil.e(rootDir)
setRxJavaErrorHandler()
// HookMobileCache().hook()
// registerActivityLifecycleCallbacks(lifecycleCallback)
}
/***
@@ -72,5 +78,45 @@ class XKLApplication : Application() {
})
}
private object lifecycleCallback : Application.ActivityLifecycleCallbacks {
private var count = 0
override fun onActivityCreated(activity : Activity, savedInstanceState : Bundle?) {
}
override fun onActivityStarted(activity : Activity) {
count ++
if (ActivityStackManager.isExistActivity(MainActivity::class.java)){
DictionaryFloatingWindowManager.getInstance().backToFront()
}
}
override fun onActivityResumed(activity : Activity) {
}
override fun onActivityPaused(activity : Activity) {
}
override fun onActivityStopped(activity : Activity) {
count --
if (count == 0){
DictionaryFloatingWindowManager.getInstance().frontToBack()
}
}
override fun onActivitySaveInstanceState(activity : Activity, outState : Bundle) {
}
override fun onActivityDestroyed(activity : Activity) {
}
}

}

+ 225
- 0
app/src/main/java/com/xkl/cdl/module/floating/DictionaryFloatingSearchActivity.kt View File

@@ -0,0 +1,225 @@
package com.xkl.cdl.module.floating

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.suliang.common.AppConfig
import com.suliang.common.base.activity.BaseActivityVM
import com.suliang.common.extension.click
import com.suliang.common.util.LogUtil
import com.suliang.common.util.media.MPManager
import com.suliang.common.util.os.KeyboardUtil
import com.xkl.cdl.R
import com.xkl.cdl.data.repository.AudioCache
import com.xkl.cdl.databinding.ActivityDictionaryFloatingSearchBinding
import com.xkl.cdl.dialog.CommonDialog
import com.xkl.cdl.dialog.CommonDialogBean
import com.xkl.cdl.module.m_service_center.DictionaryViewModel
import android.view.WindowManager

import android.os.Build




class DictionaryFloatingSearchActivity : BaseActivityVM<ActivityDictionaryFloatingSearchBinding,DictionaryViewModel>() {
companion object{
fun instance(activity: Activity,courseId:Long = 0L){
activity.startActivity(Intent(activity,DictionaryFloatingSearchActivity::class.java).apply {
putExtra(AppConfig.INTENT_1,courseId)
})
}
}

override fun initViewModel() : DictionaryViewModel {
return ViewModelProvider(this)[DictionaryViewModel::class.java]
}
override fun initStatusBar() {
super.initStatusBar()
}
override fun initActivity(savedInstanceState : Bundle?) {
window?.let {
it.setLayout(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)
it.setGravity(Gravity.TOP)
//避免状态栏不显示
if (Build.VERSION.SDK_INT >= 28) {
val params = it.attributes
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
it.attributes = params
}
}
vm.courseId = intent.getLongExtra(AppConfig.INTENT_1, 0)
initHistoryRecyclerView()
binding.tvClose.click {
KeyboardUtil.hideKeyboard(it)
finish()
}
//点击 查看更多
binding.tvSeeMore.click {
when (vm.detailState) {
DictionaryViewModel.STATE_HISTROY -> {
//显示页面加1, 列表更新
vm.histroyShowPage++
vm.adapter.notifyDataSetChanged()
//根据数量判断,是否还要显示
binding.tvSeeMore.visibility = if (vm.historyList.size > vm.histroyShowPage * vm.pageShowCount) View.VISIBLE else View.GONE
binding.rv.smoothScrollToPosition(vm.histroyShowPage * vm.pageShowCount - 1)
}
DictionaryViewModel.STATE_SEARCH -> {
//显示页面加1, 列表更新
vm.searchShowPage++
vm.adapter.notifyDataSetChanged()
//根据数量判断,是否还要显示
binding.tvSeeMore.visibility = if (vm.searchAssociateList.size > vm.searchShowPage * vm.pageShowCount) View.VISIBLE else View.GONE
binding.rv.smoothScrollToPosition(vm.searchShowPage * vm.pageShowCount - 1)
}
}
}
//删除历史记录,弹窗提示
binding.ivDelete.click {
val commonDialogBean = CommonDialogBean(titleText = R.string.dialog_delete_history, leftText = R.string.delete,
rightText = R.string.cancel)
CommonDialog.newInstance(commonDialogBean).apply {
onCommonDialogButtonClickListener = { dialog : CommonDialog, isRightClick : Boolean ->
if (!isRightClick) { //删除
vm.clearHistory()
}
dialog.dismissAllowingStateLoss()
}
}.show(supportFragmentManager, javaClass.name)
}
//监听EditText变化
// 1 点清空或没有内容时 显示历史记录
// 2 关键字搜索 显示搜索记录
// 3 点击历史记录或者点击搜索记录,更新显示详情
// 4 单独定义一个值,定义为点击了历史记录或搜索记录
binding.etSearch.addTextChangedListener{
vm.handler.removeCallbacksAndMessages(null)
when {
it.isNullOrEmpty() -> { //内容为空,显示为历史记录
LogUtil.e("EditText输入数据为空: 空数据,显示历史记录")
vm.detailState = DictionaryViewModel.STATE_HISTROY
vm.updateHistoryAdapterLiveData.value = vm.detailState
}
else -> { //不为空
when(vm.detailState){
DictionaryViewModel.STATE_DETAIL -> { //点击显示详情了, 更新详情显示
LogUtil.e("EditText输入数据 : 点击item显示详情")
KeyboardUtil.hideKeyboard(binding.etSearch) //关闭键盘
vm.updateHistoryAdapterLiveData.value = vm.detailState
}
else -> { //关键字搜索
LogUtil.e("EditText输入数据 : 进行关键字搜索")
vm.detailState = DictionaryViewModel.STATE_SEARCH
vm.searchKeyWord(it.toString())
}
}
}
}
}
/**搜索监听*/
vm.etSearchLiveData.observe(this){
binding.etSearch.setText(it)
binding.etSearch.setSelection(it.length)
}
//更新历史记录适配器
// true 跟新搜索结果
// false 更新列表
vm.updateHistoryAdapterLiveData.observe(this) {
LogUtil.e("Adapter 进行数据更新 $it")
when(it){
DictionaryViewModel.STATE_HISTROY -> { //历史记录
//布局显示处理
binding.run {
tvHistoryTitle.visibility = View.VISIBLE
ivDelete.visibility = View.VISIBLE
vLine.visibility = View.VISIBLE
layoutDetail.visibility = if (vm.historyList.isEmpty()) View.GONE else View.VISIBLE
}
binding.tvSeeMore.visibility = if (vm.historyList.size > vm.histroyShowPage * vm.pageShowCount) View.VISIBLE else View.GONE
vm.adapter.run {
needShowEmptyView = false
setData(vm.historyList)
}
}
DictionaryViewModel.STATE_SEARCH -> { //关键字搜索记录
binding.run {
vLine.visibility = View.GONE
tvHistoryTitle.visibility = View.GONE
ivDelete.visibility = View.GONE
layoutDetail.visibility = View.VISIBLE
tvSeeMore.visibility = if (vm.searchAssociateList.size > vm.searchShowPage * vm.pageShowCount) View.VISIBLE else View.GONE
}
vm.searchShowPage = 1
vm.adapter.run {
needShowEmptyView = true
setData(vm.searchAssociateList)
}
}
DictionaryViewModel.STATE_DETAIL -> { //item详情
binding.run {
vLine.visibility = View.GONE
tvHistoryTitle.visibility = View.GONE
ivDelete.visibility = View.GONE
layoutDetail.visibility = View.VISIBLE
tvSeeMore.visibility = View.GONE
}
vm.adapter.run {
needShowEmptyView = false
setData(vm.detailItemList)
}
}
}
}
/**发音监听*/
AudioCache.initAudioLiveData().observe(this){
it?.let {
MPManager.play(it)
}?: showToast("未找到发音文件")
}
}
private fun initHistoryRecyclerView() {
binding.rv.apply {
layoutManager = LinearLayoutManager(this@DictionaryFloatingSearchActivity, LinearLayoutManager.VERTICAL, false)
adapter = vm.adapter
}
}
@SuppressLint("NotifyDataSetChanged")
override fun loadData() {
//查询历史记录
vm.loadHistory()
}
override fun onResume() {
super.onResume()
DictionaryFloatingWindowManager.getInstance().hide()
}

}

+ 176
- 0
app/src/main/java/com/xkl/cdl/module/floating/DictionaryFloatingWindowManager.kt View File

@@ -0,0 +1,176 @@
package com.xkl.cdl.module.floating

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.net.Uri
import android.provider.Settings
import android.view.*
import androidx.databinding.DataBindingUtil
import com.suliang.common.extension.click
import com.suliang.common.util.ActivityStackManager
import com.suliang.common.util.os.ScreenUtil
import com.suliang.common.util.os.VersionUtil
import com.xkl.cdl.R
import com.xkl.cdl.databinding.DictionaryFloatingLayoutBinding
import com.xkl.cdl.module.XKLApplication
import kotlin.math.abs

/**
* @Author: suliang
* @create: 2022/8/19 15:27
* @Description: 词典悬浮窗管理类
*/
class DictionaryFloatingWindowManager private constructor(){
companion object{
@JvmStatic
fun getInstance():DictionaryFloatingWindowManager {
return SingletonHolder.mInstance
}
}
private object SingletonHolder{
val mInstance = DictionaryFloatingWindowManager()
}
private var mWindowManager : WindowManager? = null
private var windowParams : WindowManager.LayoutParams? = null
private var layoutBinding : DictionaryFloatingLayoutBinding? = null
private var isShowing = false
var courseId = 0
set(value) {
field = value
}
fun show() {
if (!checkOverlayerPermission()) return
windowParams?.let {
layoutBinding?.root?.visibility = View.VISIBLE
} ?: let {
initLayout()
}
isShowing = true
}
fun hide() {
isShowing = false
layoutBinding?.root?.visibility = View.INVISIBLE
}
fun backToFront(){
if (isShowing){
show()
}
}
fun frontToBack(){
if (isShowing){
layoutBinding?.root?.visibility = View.INVISIBLE
}
}
/**
* 检查是否有悬浮窗权限
*/
fun checkOverlayerPermission() : Boolean {
if (VersionUtil.versionMorLater()) {
return Settings.canDrawOverlays(XKLApplication.instance())
}
return true
}
private fun initLayout() {
windowParams = WindowManager.LayoutParams().apply {
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL.or (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
type = if (VersionUtil.versionOorLater()) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE
format = PixelFormat.RGBA_8888
gravity = Gravity.LEFT.or (Gravity.TOP)
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
x = ScreenUtil.getScreenWidth() - ScreenUtil.dp2px(70f).toInt()
y = ScreenUtil.getScreenHeight() - ScreenUtil.dp2px(150f).toInt()
layoutBinding = DataBindingUtil.inflate(LayoutInflater.from(XKLApplication.instance()), R.layout.dictionary_floating_layout,null,false)
layoutBinding?.let {
it.root.isFocusable = true
initEvent(it)
}
mWindowManager = XKLApplication.instance().applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager?
mWindowManager?.addView(layoutBinding?.root,this)
}
}
private fun initEvent(binding : DictionaryFloatingLayoutBinding) {
binding.root.let {
// it.click {
//
// }
it.setOnTouchListener(object:View.OnTouchListener{
private var mLastX = 0f
private var mLastY = 0f
private var mDownX = 0f
private var mDownY = 0f
private val scaledTouchSlop = ViewConfiguration.get(XKLApplication.instance()).scaledTouchSlop
private var isDrag = false
override fun onTouch(p0 : View?, p1 : MotionEvent) : Boolean {
var x = p1.rawX
var y = p1.rawY
when(p1.action){
MotionEvent.ACTION_DOWN -> {
isDrag = false
mDownX = x
mDownY = y
mLastX = x
mLastY = y
}
MotionEvent.ACTION_MOVE -> {
val moveX = x - mLastX
val moveY = y - mLastY
windowParams?.let {
it.x = it.x + moveX.toInt()
it.y = it.y + moveY.toInt()
mWindowManager?.updateViewLayout(binding.root,it)
}
mLastX = x
mLastY = y
}
MotionEvent.ACTION_UP -> {
isDrag = abs(x - mDownX) > scaledTouchSlop || abs(y - mDownY) > scaledTouchSlop
if (!isDrag){
ActivityStackManager.topActivity()?.let{
DictionaryFloatingSearchActivity.instance(it)
}
}
}
}
return isDrag
}
})
}
}
/**
* 跳转打开悬浮窗权限设置界面
* @param activity Activity
*/
fun applyOverLayer(activity: Activity) {
if (VersionUtil.versionMorLater()) {
val intent = Intent()
intent.action = Settings.ACTION_MANAGE_OVERLAY_PERMISSION
intent.data = Uri.parse("package:" + activity.packageName)
activity.startActivityForResult(intent, 0)
} else {
val intent = Intent()
intent.action = Settings.ACTION_MANAGE_OVERLAY_PERMISSION
intent.data = Uri.fromParts("package", activity.packageName, null)
activity.startActivityForResult(intent, 0)
}
}
}

+ 15
- 2
app/src/main/java/com/xkl/cdl/module/learn/LearnExamViewModel.kt View File

@@ -8,6 +8,7 @@ import com.suliang.common.extension.createRandomNewChar
import com.suliang.common.extension.diskIo2Main
import com.suliang.common.util.DateUtil
import com.suliang.common.util.LogUtil
import com.suliang.common.util.media.MPManager
import com.xkl.cdl.data.AppConstants
import com.xkl.cdl.data.DataTransferHolder
import com.xkl.cdl.data.bean.SpellItemBean
@@ -17,6 +18,7 @@ import com.xkl.cdl.data.event.LearnEventData
import com.xkl.cdl.data.manager.db.DbControlBase
import com.xkl.cdl.data.repository.DataRepository
import com.xkl.cdl.databinding.IncludTestOptionItemBinding
import com.xkl.cdl.module.floating.DictionaryFloatingWindowManager
import io.reactivex.rxjava3.core.Observable
import mqComsumerV1.Struct.*
import org.json.JSONArray
@@ -167,7 +169,6 @@ class LearnExamViewModel : LearnBaseViewModel() {
}
}
}
/** onResume
* isAllOver = true 结束了,不处理了
* 开启总计时
@@ -178,11 +179,14 @@ class LearnExamViewModel : LearnBaseViewModel() {
super.onResume(owner)
if (isAllOver) return
startTotalCounting()
isShowBackDialog = false
when{
//弹窗未显示,可直接走弹窗关闭的流程
!isShowBackDialog -> showOrDismissBackDialogForTime(isShowBackDialog)
}
//不是在上传,且为英语类型,可显示词典浮窗
if (!isShowLoading && dbBaseControl.subjectId == AppConstants.SUBJECT_ENGLISH) {
DictionaryFloatingWindowManager.getInstance().show()
}
}
/** onPause时,停止总计时 标记进入后台 如果弹窗显示,就只需要关闭总计时就可以了,否则关闭所有计时 */
@@ -200,6 +204,12 @@ class LearnExamViewModel : LearnBaseViewModel() {
mHandler.removeCallbacks(currentCountingTimeRunnable)
}
}
DictionaryFloatingWindowManager.getInstance().hide()
}
override fun onDestroy(owner : LifecycleOwner) {
super.onDestroy(owner)
MPManager.removePlayListener()
}
/** 停止获取下一题 */
@@ -635,6 +645,7 @@ class LearnExamViewModel : LearnBaseViewModel() {
/** 保存处理数据 */
private fun saveData() {
DictionaryFloatingWindowManager.getInstance().hide()
showHideLoading(true)
Observable.create<Boolean> {
val saveRecord = DataRepository.saveRecord(record)
@@ -642,11 +653,13 @@ class LearnExamViewModel : LearnBaseViewModel() {
it.onComplete()
}.compose(diskIo2Main()).subscribe({
showHideLoading(false)
DictionaryFloatingWindowManager.getInstance().show()
sendEventBus() //返回发送数据
//数据保存完成后,通过数据为空进行通知,测试完成
currentExamBean.value = null
}, {
showHideLoading(false)
DictionaryFloatingWindowManager.getInstance().show()
it.printStackTrace()
})
}

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

@@ -16,6 +16,7 @@ import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.data.manager.db.DBCourseManager
import com.xkl.cdl.data.manager.db.DbControlBase
import com.xkl.cdl.data.repository.DataRepository
import com.xkl.cdl.module.floating.DictionaryFloatingWindowManager
import com.xkl.cdl.util.LearnRuleUtil
import io.reactivex.rxjava3.core.Observable
import mqComsumerV1.Struct
@@ -322,6 +323,7 @@ class LearnWordViewModel : LearnBaseViewModel() {
/** 封装保存数据,保存完后后,通过currentLearnWord为空通知activity显示完成界面*/
fun saveData() {
DictionaryFloatingWindowManager.getInstance().hide()
showHideLoading(true)
Observable.create<Boolean> {
// record 已经实例化并已经将数据保存
@@ -350,12 +352,14 @@ class LearnWordViewModel : LearnBaseViewModel() {
it.onComplete()
}.compose(diskIo2Main()).subscribe({
showHideLoading(false)
DictionaryFloatingWindowManager.getInstance().show()
sendEventBus() //返回发送数据
//数据保存完成后,通过数据为空进行通知,完成
saveDataLiveData.value = isAllOver
}, {
showHideLoading(false)
it.printStackTrace()
DictionaryFloatingWindowManager.getInstance().show()
})
}
@@ -401,11 +405,16 @@ class LearnWordViewModel : LearnBaseViewModel() {
}
startTotalCounting()
}
//不是在上传,且为英语类型,可显示词典浮窗
if (!isShowLoading && dbControlBase.subjectId == AppConstants.SUBJECT_ENGLISH) {
DictionaryFloatingWindowManager.getInstance().show()
}
}
override fun onPause(owner : LifecycleOwner) {
super.onPause(owner)
stopTotalCountTing()
DictionaryFloatingWindowManager.getInstance().hide()
}
/**

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

@@ -45,49 +45,50 @@ class MemoFragmentViewModel : BaseViewModel() {
val coursePackInResultIndex = mutableMapOf<Long, Int>()
val calendar = Calendar.getInstance()
val currentTimeMillis = System.currentTimeMillis()
AppApi.GetWordListResponse.parseFrom(wordList).wrongList?.forEach { item ->
item.split("_").let { splitValue ->
val projectId = splitValue[0].toInt()
val coursePackId = splitValue[1].toLong()
val courseId = splitValue[2].toLong()
//生成 MemoCoursePack
var memoCoursePack : MemoCoursePack? = null
coursePackInResultIndex[coursePackId]?.let { index ->
memoCoursePack = resultMemoCoursePack[index]
} ?: let {
CourseManager.getCoursePackWithCoursePackId(projectId, coursePackId)?.let { coursePack ->
memoCoursePack = MemoCoursePack(coursePack).apply {
resultMemoCoursePack.add(this)
coursePackInResultIndex[coursePackId] = resultMemoCoursePack.lastIndex
}
wordList?.let {
AppApi.GetWordListResponse.parseFrom(wordList).wrongList?.forEach { item ->
item.split("_").let { splitValue ->
val projectId = splitValue[0].toInt()
val coursePackId = splitValue[1].toLong()
val courseId = splitValue[2].toLong()
//生成 MemoCoursePack
var memoCoursePack : MemoCoursePack? = null
coursePackInResultIndex[coursePackId]?.let { index ->
memoCoursePack = resultMemoCoursePack[index]
} ?: let {
CourseManager.getCoursePackWithCoursePackId(projectId, coursePackId)?.let { coursePack ->
memoCoursePack = MemoCoursePack(coursePack).apply {
resultMemoCoursePack.add(this)
coursePackInResultIndex[coursePackId] = resultMemoCoursePack.lastIndex
}
}
memoCoursePack?.let {
it.coursePackChildrenMemo[courseId]?.add(item) ?: let { vm ->
val memoList = mutableListOf<String>()
memoList.add(item)
it.coursePackChildrenMemo[courseId] = memoList
}
}
//复习数量
if (CourseManager.calculateIsNeedInReview(calendar, splitValue[6].toInt(), splitValue[7],
currentTimeMillis)) {
it.coursePackChildrenReview[courseId]?.let { valueNumber ->
it.coursePackChildrenReview[courseId] = valueNumber + 1
} ?: let { vm ->
it.coursePackChildrenReview[courseId] = 1
memoCoursePack?.let {
it.coursePackChildrenMemo[courseId]?.add(item) ?: let { vm ->
val memoList = mutableListOf<String>()
memoList.add(item)
it.coursePackChildrenMemo[courseId] = memoList
}
//复习数量
if (CourseManager.calculateIsNeedInReview(calendar, splitValue[6].toInt(), splitValue[7],
currentTimeMillis)) {
it.coursePackChildrenReview[courseId]?.let { valueNumber ->
it.coursePackChildrenReview[courseId] = valueNumber + 1
} ?: let { vm ->
it.coursePackChildrenReview[courseId] = 1
}
}
}
}
}
memoCoursePackList.clear()
//排序,根据课程id固定排序
resultMemoCoursePack.sortBy {
it.coursePack.coursePackId
}
memoCoursePackList.addAll(resultMemoCoursePack)
}
memoCoursePackList.clear()
//排序,根据课程id固定排序
resultMemoCoursePack.sortBy {
it.coursePack.coursePackId
}
memoCoursePackList.addAll(resultMemoCoursePack)
return@fromCallable true
}.compose(diskIo2Main()).subscribe({
etSearchLiveData.value = etSearchLiveData.value ?: ""

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

@@ -1,5 +1,6 @@
package com.xkl.cdl.module.m_memo

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.suliang.common.base.activity.ToastEvent
import com.suliang.common.base.viewmodel.BaseViewModel
@@ -13,6 +14,8 @@ import com.xkl.cdl.data.bean.intentdata.MemoData
import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.data.manager.db.DBCourseManager
import com.xkl.cdl.data.manager.db.DbControlBase
import com.xkl.cdl.data.manager.db.DictionaryManager
import com.xkl.cdl.module.floating.DictionaryFloatingWindowManager
import io.reactivex.rxjava3.core.Observable
import java.util.*

@@ -159,4 +162,15 @@ class MemoListDetailViewModel : BaseViewModel() {
}
override fun onResume(owner : LifecycleOwner) {
super.onResume(owner)
if (dbControlBase.subjectId == AppConstants.SUBJECT_ENGLISH){
DictionaryFloatingWindowManager.getInstance().show()
}
}
override fun onPause(owner : LifecycleOwner) {
super.onPause(owner)
DictionaryFloatingWindowManager.getInstance().hide()
}
}

+ 11
- 2
app/src/main/java/com/xkl/cdl/module/m_service_center/DictionaryViewModel.kt View File

@@ -2,6 +2,7 @@ package com.xkl.cdl.module.m_service_center

import android.os.Handler
import android.os.Looper
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.suliang.common.base.viewmodel.BaseViewModel
import com.suliang.common.extension.io2Main
@@ -118,7 +119,7 @@ class DictionaryViewModel : BaseViewModel() {
/**
* 查询中文item下的多个英文item集合
* @param it DictionaryItem
* @param chineseItem DictionaryItem
* @return MutableList<DictionaryItem>?
*/
fun queryChineseForEnglishList(chineseItem : DictionaryItem) {
@@ -155,7 +156,7 @@ class DictionaryViewModel : BaseViewModel() {
* @param position Int 搜索列表中的位置
*/
fun insertAndUpdateHistory(item : DictionaryItem, position : Int) {
detailState = STATE_DETAIL
Observable.fromCallable {
item.queryTime = System.currentTimeMillis()
//添加到历史记录第一个
@@ -173,6 +174,7 @@ class DictionaryViewModel : BaseViewModel() {
}
//添加到历史记录第一个
historyList.add(0, item)
detailState = STATE_DETAIL
//重新赋值需要用于刷新的集合
detailItemList = mutableListOf(item)
@@ -185,4 +187,11 @@ class DictionaryViewModel : BaseViewModel() {
}.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe()
}
override fun onDestroy(owner : LifecycleOwner) {
super.onDestroy(owner)
AppExecutors.io.execute {
AppDatabase.instance.dictionaryDao().clearMore(courseId)
}
}
}

+ 2
- 1
app/src/main/java/com/xkl/cdl/module/m_service_center/TestScoreActivity.kt View File

@@ -18,6 +18,7 @@ import com.xkl.cdl.adapter.AdapterTestFilter
import com.xkl.cdl.adapter.AdapterTestScore
import com.xkl.cdl.data.DataTransferHolder
import com.xkl.cdl.data.exam_record.AppDatabase
import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.databinding.ActivityTestScoreBinding
import com.xkl.cdl.widget.MyFlexboxLayoutManager
import io.reactivex.rxjava3.core.Observable
@@ -85,7 +86,7 @@ class TestScoreActivity : BaseActivityVM<ActivityTestScoreBinding, TestScoreView
onItemClick = {_, _, item ->
Observable.fromCallable {
val queryDetail = AppDatabase.instance.examMiddleDao().queryDetail(item.id)
AppDatabase.examWithDetailToExamBuild(queryDetail)
CourseManager.examWithDetailToExamBuild(queryDetail)
}.compose(diskIo2Main()).subscribe{
DataTransferHolder.instance.putData(value = it)
startActivity(TestDetailActivity::class.java)

+ 4
- 1
app/src/main/java/com/xkl/cdl/module/m_statics/StaticsFragment.kt View File

@@ -80,7 +80,10 @@ class StaticsFragment : BaseFragmentVM<FragmentStaticsBinding, StatisticsFragmen
//监听图标数据初始结果
vm.chartLineInitLiveData.observe(this) {
when {
!it -> binding.chart.setNoDataText("暂无数据")
!it -> binding.chart.run {
setNoDataText("暂无数据")
clear()
}
else -> {
setChartData()
val lineData = LineData(vm.linDataSet_new, vm.linDataSet_review).apply {

+ 42
- 0
app/src/main/java/com/xkl/cdl/module/main/MainActivity.kt View File

@@ -2,12 +2,17 @@ package com.xkl.cdl.module.main

import android.graphics.Color
import android.os.Bundle
import android.view.KeyEvent
import androidx.lifecycle.ViewModelProvider
import com.suliang.common.base.activity.BaseActivityVM
import com.suliang.common.extension.loadFragment
import com.suliang.common.extension.showHideFragment
import com.xkl.cdl.R
import com.xkl.cdl.databinding.ActivityMainBinding
import com.xkl.cdl.dialog.CommonDialog
import com.xkl.cdl.dialog.CommonDialogBean
import com.xkl.cdl.module.XKLApplication
import com.xkl.cdl.module.floating.DictionaryFloatingWindowManager
import com.xkl.cdl.module.m_center_learn.LearnCenterFragment
import com.xkl.cdl.module.m_memo.MemoFragment
import com.xkl.cdl.module.m_my.MyFragment
@@ -55,6 +60,7 @@ class MainActivity : BaseActivityVM<ActivityMainBinding, MainActivityViewModel>(
//加载Fragment并显示
loadFragment(R.id.layout_container, 2, mMemo, mStatistics, mLearnCenter, mServiceCenter, mMy)
binding.layoutNavContainer.check(R.id.nav_learnCenter)
showNotOpenFloatPermissionDialog()
}

fun setStatusBar(isMyFragment : Boolean){
@@ -68,4 +74,40 @@ class MainActivity : BaseActivityVM<ActivityMainBinding, MainActivityViewModel>(
}
}
/** 词典悬浮窗权限 */
private fun showNotOpenFloatPermissionDialog() {
if (!DictionaryFloatingWindowManager.getInstance().checkOverlayerPermission()) {
val commonDialogBean = CommonDialogBean(titleText = R.string.floating_dictionary_tips,
contentText = R.string.floating_dictionary_content,
rightText = R.string.floating_dictionary_right_button,
rightColor = R.color.theme_color,
leftText = R.string.cancel)
CommonDialog.newInstance(commonDialogBean).apply {
onCommonDialogButtonClickListener = { dialog : CommonDialog, isRightClick : Boolean ->
if (isRightClick) {
DictionaryFloatingWindowManager.getInstance().applyOverLayer(this@MainActivity)
}
dialog.dismissAllowingStateLoss()
}
}.show(supportFragmentManager,"floating")
}
}
//按返回按键的时间,两次返回键间隔2秒即可推出程序
private var mExitTime : Long = 0
override fun onKeyDown(keyCode : Int, event : KeyEvent?) : Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (System.currentTimeMillis() - mExitTime > 2000) {
showToast("再按一次退出程序")
mExitTime = System.currentTimeMillis()
} else {
finish()
System.exit(0)
}
return true
}
return super.onKeyDown(keyCode, event)
}
}

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

@@ -13,7 +13,9 @@ import com.xkl.cdl.data.manager.CourseManager
import com.xkl.cdl.data.manager.FilePathManager
import com.xkl.cdl.data.manager.db.DbCoursePackManager
import com.xkl.cdl.databinding.ActivitySplashBinding
import com.xkl.cdl.databinding.DictionaryFloatingLayoutBinding
import com.xkl.cdl.module.XKLApplication
import com.xkl.cdl.module.floating.DictionaryFloatingWindowManager
import com.xkl.cdl.module.main.MainActivity
import io.reactivex.rxjava3.core.Observable
import java.io.File
@@ -65,7 +67,9 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
}
// TODO: 2022/3/22 读取当前app绑定的课程数据,
DbCoursePackManager().queryBindingCoursePack("262,261,264,136,547,615,516,411")
// DbCoursePackManager().queryBindingCoursePack("262,261,264,136,547,615,516,411")
//暂时不用口语课程
DbCoursePackManager().queryBindingCoursePack("262,261,264,136,547,516,411")
//复制课程的数据库到对应位置
CourseManager.checkCourseDb()
//定时跳跃到住主界面

+ 6
- 0
app/src/main/res/drawable/shape_rounder_bottomlr_16_solider_white.xml View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white"/>
<corners android:bottomLeftRadius="16dp"
android:bottomRightRadius="16dp"/>
</shape>

+ 1
- 0
app/src/main/res/layout/activity_dictionary.xml View File

@@ -60,6 +60,7 @@
android:paddingEnd="@dimen/global_spacing"
android:drawablePadding="4dp"
android:textSize="@dimen/smallSize"
android:textColor="@color/main_text_color"
android:gravity="center_vertical"
android:singleLine="true"
android:hint="输入你要搜索的内容…"/>

+ 131
- 0
app/src/main/res/layout/activity_dictionary_floating_search.xml View File

@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".module.floating.DictionaryFloatingSearchActivity"
android:background="@drawable/shape_rounder_bottomlr_16_solider_white">

<com.suliang.common.widget.InputSearchEditText
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_height="40dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tv_close"
app:layout_constraintBottom_toTopOf="@+id/guide_line"
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginEnd="@dimen/global_spacing"
android:background="@drawable/et_search_bg"
android:imeOptions="actionDone"
android:paddingStart="@dimen/global_spacing"
android:paddingEnd="@dimen/global_spacing"
android:drawablePadding="4dp"
android:textSize="@dimen/smallSize"
android:textColor="@color/main_text_color"
android:gravity="center_vertical"
android:singleLine="true"
android:hint="输入你要搜索的内容…"/>

<TextView
android:id="@+id/tv_close"
android:layout_width="50dp"
android:layout_height="36dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:gravity="center"
android:text="关闭"
android:textColor="@color/gray_2"
android:textSize="@dimen/smallSize"
app:layout_constraintStart_toEndOf="@+id/et_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/et_search"
app:layout_constraintBottom_toBottomOf="@+id/et_search"/>

<androidx.constraintlayout.widget.Guideline
android:id="@+id/guide_line"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="76dp"/>

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/guide_line"
app:layout_constraintVertical_bias="0"
android:layout_marginBottom="34dp"
android:background="@drawable/shape_rounder_8_solid_green1"
android:backgroundTint="@color/gray_3"
android:layout_marginStart="@dimen/global_spacing"
android:layout_marginEnd="@dimen/global_spacing"
app:layout_constrainedHeight="true"
android:visibility="gone">

<View
android:id="@+id/v_line"
android:layout_width="match_parent"
android:layout_height="@dimen/line_height"
android:background="@color/gray_1"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="40dp" />

<TextView
android:id="@+id/tv_history_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/smallSize"
android:textColor="@color/gray_2"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/v_line"
app:layout_constraintStart_toStartOf="parent"
android:text="历史搜索记录"
android:layout_marginStart="@dimen/global_spacing" />

<ImageView
android:id="@+id/iv_delete"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/v_line"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:layout_marginEnd="4dp"
android:src="@drawable/ic_rubbish" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/v_line"
app:layout_constraintBottom_toTopOf="@+id/tv_see_more"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_bias="0"
app:layout_constrainedHeight="true"
tools:itemCount="10"/>

<TextView
android:id="@+id/tv_see_more"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:textSize="@dimen/smallerSize"
android:textColor="@color/gray_2"
app:layout_constraintTop_toBottomOf="@+id/rv"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0"
android:text="查看更多"
android:drawableEnd="@drawable/ic_down"
android:drawableTint="@color/gray_2"
android:gravity="center_vertical"
android:drawablePadding="4dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

+ 5
- 4
app/src/main/res/layout/activity_test_detail.xml View File

@@ -41,7 +41,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_bar"
android:visibility="gone"
tools:visibility="visible"
tools:visibility="gone"
>

<TextView
@@ -206,7 +206,7 @@
<androidx.core.widget.NestedScrollView
android:id="@+id/layout_memo_test"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/line_height"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
@@ -214,8 +214,9 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_bar"
app:layout_constraintVertical_bias="0"
app:layout_constrainedHeight="true"
android:visibility="gone"
tools:visibility="gone">
tools:visibility="visible">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@@ -389,7 +390,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

+ 3
- 1
app/src/main/res/layout/dialog_common.xml View File

@@ -36,12 +36,14 @@

<TextView
android:id="@+id/tv_content"
tools:text="删除后将不可再恢复标签中的内容"
tools:text="删除后将不可再恢复标签中的内容删除后将不可再恢复标签中的内容"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
android:layout_marginTop="10dp"
app:layout_goneMarginTop="24dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textColor="#8A8A99"

+ 39
- 0
app/src/main/res/layout/dictionary_floating_layout.xml View File

@@ -0,0 +1,39 @@
<?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">

<data>

</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<androidx.cardview.widget.CardView
android:id="@+id/float_view"
android:layout_width="50dp"
android:layout_height="50dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="25dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="15dp"
app:cardMaxElevation="12dp"
app:cardElevation="8dp">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_dictionary_floating"
android:gravity="center"
android:layout_gravity="center"
android:text="词典"
android:textColor="@color/main_text_color"
android:textSize="@dimen/smallerSize" />
</androidx.cardview.widget.CardView>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

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

@@ -109,5 +109,8 @@
<string name="setting">设置</string>
<string name="default_sound">单词默认发音</string>
<string name="clear_cache">清除缓存</string>
<string name="floating_dictionary_tips">词典使用提示</string>
<string name="floating_dictionary_content">为了在学习中可以使用词典功能,需要手动打开并同意悬浮窗权限!</string>
<string name="floating_dictionary_right_button">去打开</string>

</resources>

+ 14
- 1
app/src/main/res/values/styles.xml View File

@@ -50,6 +50,19 @@
<item name="android:backgroundDimEnabled">true</item> <!--模糊,背景透明是这个- 弹窗背景是否变暗 -->
</style>


<!--dialog activity-->
<style name="DialogActivityTheme" parent="Theme.AppCompat.Dialog">
<item name="android:windowTranslucentStatus">true</item><!--透明状态栏并占用状态栏位置 -->
<item name="android:windowIsTranslucent">true</item><!-- 半透明 -->
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowCloseOnTouchOutside">false</item>
<item name="android:backgroundDimAmount">0.5</item><!-- 背景模糊的透明度 数值越小越透明-->
<item name="android:windowIsFloating">false</item><!-- 浮现在Activity之上 -->
<!-- <item name="android:windowAnimationStyle">@null</item>&lt;!&ndash; 进入和退出动画 &ndash;&gt;-->
<item name="windowNoTitle">true</item><!-- 无标题 -->
<item name="android:windowActionBar">false</item>
</style>

</resources>

+ 12
- 0
app/svg/drawable/ic_dictionary_floating.xml View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16.41,2.4L20.8,6.789L20.8,18.6C20.8,20.2569 19.4569,21.6 17.8,21.6L7.8,21.6C6.1431,21.6 4.8,20.2569 4.8,18.6L4.8,5.4C4.8,3.7431 6.1431,2.4 7.8,2.4L16.41,2.4ZM9.1341,8.3341C7.7034,9.7648 7.6155,12.0558 8.9322,13.5919C10.249,15.1281 12.5264,15.3915 14.1591,14.1964L16.582,16.6253C16.7327,16.776 16.9522,16.8348 17.158,16.7797C17.3638,16.7245 17.5245,16.5638 17.5797,16.358C17.6348,16.1522 17.576,15.9327 17.4253,15.782L14.9964,13.3591C16.1915,11.7264 15.9281,9.449 14.3919,8.1322C12.8558,6.8155 10.5648,6.9034 9.1341,8.3341ZM11.8846,8.4122C13.3605,8.4122 14.557,9.6086 14.557,11.0846C14.557,12.5605 13.3605,13.757 11.8846,13.757C10.4086,13.757 9.2122,12.5605 9.2122,11.0846C9.2122,9.6086 10.4086,8.4122 11.8846,8.4122Z"
android:strokeWidth="1"
android:fillColor="#5082E6"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

+ 4
- 0
lib/common/src/main/java/com/suliang/common/base/viewmodel/BaseViewModel.kt View File

@@ -25,7 +25,11 @@ open class BaseViewModel : ViewModel(), ViewModelLifecycle, ViewBehavior {
val toastEvent = MutableLiveData<ToastEvent>()
val pageEvent = MutableLiveData<Class<*>>()
//记录是否在显示loading 弹窗
var isShowLoading = false
override fun showHideLoading(isShow : Boolean) {
isShowLoading = isShow
loadingEvent.postValue(isShow)
}

+ 23
- 0
lib/common/src/main/java/com/suliang/common/util/ActivityStackManager.kt View File

@@ -41,4 +41,27 @@ object ActivityStackManager {
}
}
}
/**
* 获取顶部Activity
*/
fun topActivity() : Activity? {
return if (activityStack.isNotEmpty()){
activityStack.peek()
} else null
}
fun isExistActivity(cls : Class<*>) : Boolean {
var result = false
run m@{
activityStack.forEach {
if (it.javaClass == cls){
result = true
return@m
}
}
}
return result
}
}

+ 1
- 1
lib/common/src/main/java/com/suliang/common/util/os/FloatingWindowUtil.kt View File

@@ -40,7 +40,7 @@ class FloatingWindowUtil {
* 跳转打开悬浮窗权限设置界面
* @param activity Activity
*/
fun applyOverLayerPermission(activity: Activity) {
fun c(activity: Activity) {
if (VersionUtil.versionMorLater()) {
val intent = Intent()
intent.action = Settings.ACTION_MANAGE_OVERLAY_PERMISSION

+ 8
- 0
lib/common/src/main/java/com/suliang/common/util/os/IntentUtils.kt View File

@@ -1,5 +1,6 @@
package com.suliang.common.util.os

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
@@ -17,6 +18,13 @@ object IntentUtils {
val intent = Intent(Intent.ACTION_VIEW, uri)
context.startActivity(intent)
}
/**
* 打开悬浮窗设置
*/
fun applyOverLayer(activity: Activity){
}

// /**
// * 发送广播让相册扫描文件进入相册

Loading…
Cancel
Save