@@ -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> |
@@ -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> |
@@ -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' | |||
} |
@@ -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" /> |
@@ -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)") | |||
// } | |||
// } | |||
} | |||
} |
@@ -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) | |||
} |
@@ -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() | |||
} | |||
} |
@@ -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) |
@@ -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() | |||
} |
@@ -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 | |||
} | |||
} | |||
} | |||
} |
@@ -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) { | |||
} | |||
} | |||
} |
@@ -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() | |||
} | |||
} |
@@ -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) | |||
} | |||
} | |||
} |
@@ -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() | |||
}) | |||
} |
@@ -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() | |||
} | |||
/** |
@@ -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 ?: "" |
@@ -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() | |||
} | |||
} |
@@ -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) | |||
} | |||
} | |||
} |
@@ -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) |
@@ -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 { |
@@ -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) | |||
} | |||
} |
@@ -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() | |||
//定时跳跃到住主界面 |
@@ -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> |
@@ -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="输入你要搜索的内容…"/> |
@@ -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> |
@@ -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" |
@@ -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" |
@@ -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> |
@@ -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> |
@@ -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><!– 进入和退出动画 –>--> | |||
<item name="windowNoTitle">true</item><!-- 无标题 --> | |||
<item name="android:windowActionBar">false</item> | |||
</style> | |||
</resources> |
@@ -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> |
@@ -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) | |||
} | |||
@@ -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 | |||
} | |||
} |
@@ -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 |
@@ -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){ | |||
} | |||
// /** | |||
// * 发送广播让相册扫描文件进入相册 |