@@ -130,7 +130,7 @@ | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_checkbox.xml" value="1.0" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_course_lesson.xml" value="0.4785615491009682" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_chinese.xml" value="0.33242753623188404" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_chinese_for_english_item.xml" value="0.75" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_chinese_for_english_item.xml" value="0.6032138442521632" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_detail_english.xml" value="0.5" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_history_chinese.xml" value="0.23632218844984804" /> | |||
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_history_english.xml" value="0.23632218844984804" /> |
@@ -16,10 +16,20 @@ android { | |||
versionName androidConfig.version_name | |||
multiDexEnabled true //解决64k 分包限制 | |||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
javaCompileOptions { | |||
annotationProcessorOptions { | |||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] | |||
//java环境roomSchemaLocation配置 | |||
// javaCompileOptions { | |||
// annotationProcessorOptions { | |||
// arguments = [ | |||
// "room.schemaLocation" : "$projectDir/schemas".toString(), | |||
// "room.incremental" : "true", | |||
// "room.expandProjection" :"true" | |||
// ] | |||
// } | |||
// } | |||
//Kotlin环境RoomSchemaLocation配置 | |||
kapt{ | |||
arguments{ | |||
arg("room.schemaLocation","$projectDir/schemas") | |||
} | |||
} | |||
@@ -1,11 +1,24 @@ | |||
package com.xkl.cdl.adapter | |||
import android.view.View | |||
import android.view.ViewGroup | |||
import androidx.core.content.ContextCompat | |||
import androidx.recyclerview.widget.LinearLayoutManager | |||
import com.suliang.common.base.adapter.BaseAdapterViewHolder | |||
import com.suliang.common.base.adapter.BaseRVAdapterVM | |||
import com.suliang.common.databinding.ItemEmptyBinding | |||
import com.suliang.common.extension.click | |||
import com.suliang.common.util.StringUtil | |||
import com.xkl.cdl.R | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.data.bean.DictionaryItem | |||
import com.xkl.cdl.data.manager.UserInfoManager | |||
import com.xkl.cdl.data.repository.AudioCache | |||
import com.xkl.cdl.databinding.ItemDicDetailChineseBinding | |||
import com.xkl.cdl.databinding.ItemDicDetailChineseForEnglishItemBinding | |||
import com.xkl.cdl.databinding.ItemDicDetailEnglishBinding | |||
import com.xkl.cdl.databinding.ItemDicSearchBinding | |||
import com.xkl.cdl.initValue | |||
import com.xkl.cdl.module.m_service_center.DictionaryViewModel | |||
/** | |||
@@ -13,71 +26,207 @@ import com.xkl.cdl.module.m_service_center.DictionaryViewModel | |||
* create 2022/8/4 10:41 | |||
* Describe: | |||
*/ | |||
class DictionaryAdapter(vm:DictionaryViewModel) : BaseRVAdapterVM<DictionaryItem,DictionaryViewModel>(vm) { | |||
class DictionaryAdapter(vm : DictionaryViewModel) : BaseRVAdapterVM<DictionaryItem, DictionaryViewModel>(vm) { | |||
override fun getItemCount() : Int { | |||
//历史记录,不显示空状态 | |||
return if (vm.detailState == 1) { | |||
//大于页数显示的总数量,则显示指定数量,否则显示全部 | |||
val showCount = vm.histroyShowPage * vm.historyPageSize | |||
if (getData().size >= showCount){ | |||
showCount | |||
}else{ | |||
getData().size | |||
/** | |||
* 获取显示 viewType | |||
* 1 如果允许显示空 则显示空 | |||
* 2 历史记录列表和关键字搜索列表,展示type为 3 | |||
* 3 详情 item type 为item的form 1英文 2中文 | |||
* @param position Int | |||
* @return Int | |||
*/ | |||
override fun getItemViewType(position : Int) : Int { | |||
return when { | |||
enableEmptyPosition(position) -> TYPE_EMPTY | |||
else -> when(vm.detailState){ | |||
//历史记录和关键字搜索结果列表 | |||
DictionaryViewModel.STATE_HISTROY,DictionaryViewModel.STATE_SEARCH -> { | |||
3 | |||
} | |||
//单条记录结果 | |||
else -> getItem(position).form //详情展示 | |||
} | |||
}else if (needShowEmptyView && getData().isEmpty()) 1 else getData().size | |||
} | |||
} | |||
override fun onBindEmptyViewHolder(holder : BaseAdapterViewHolder) { | |||
(holder.binding as ItemEmptyBinding).run { | |||
//根据监听,显示具体的内容 | |||
vm.etSearchLiveData.value?.let { | |||
if (it.isNotEmpty()) { | |||
imgEmpty.setImageResource(R.mipmap.empty_nothing_search) | |||
tvContent.text = "没有搜索到任何内容" | |||
} else { | |||
imgEmpty.setImageResource(R.mipmap.empty_nothing) | |||
tvContent.text = "没有数据" | |||
/** | |||
* 空状态为 1 | |||
* 列表状态根据可显示数量,进行显示 | |||
* 详情就直接显示数量 | |||
* @return Int | |||
*/ | |||
override fun getItemCount() : Int { | |||
return when { | |||
//允许空显示,且数据为空时,显示空界面 | |||
needShowEmptyView && getData().isEmpty() -> { | |||
1 | |||
} | |||
else -> when (vm.detailState) { | |||
DictionaryViewModel.STATE_HISTROY -> { | |||
val showCount = vm.histroyShowPage * vm.pageShowCount | |||
if (getData().size >= showCount) { | |||
showCount | |||
} else { | |||
getData().size | |||
} | |||
} | |||
DictionaryViewModel.STATE_SEARCH -> { | |||
val showCount = vm.searchShowPage * vm.pageShowCount | |||
if (getData().size >= showCount) { | |||
showCount | |||
} else { | |||
getData().size | |||
} | |||
} | |||
//单条记录结果 | |||
else -> { | |||
getData().size | |||
} | |||
} | |||
} | |||
} | |||
override fun getItemViewType(position : Int) : Int { | |||
return when{ | |||
enableEmptyPosition(position) -> TYPE_EMPTY | |||
//单条详情 | |||
vm.searchItemDetailAble -> { | |||
val item = getItem(position) | |||
item.form //1英文 2 中文 | |||
} | |||
//多条记录 | |||
else -> 3 | |||
/** | |||
* 空状态显示 | |||
* @param holder BaseAdapterViewHolder | |||
*/ | |||
override fun onBindEmptyViewHolder(holder : BaseAdapterViewHolder) { | |||
(holder.binding as ItemEmptyBinding).run { | |||
imgEmpty.setImageResource(R.mipmap.empty_nothing_search) | |||
tvContent.text = "没有搜索到任何内容" | |||
} | |||
} | |||
override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder { | |||
return when(viewType){ | |||
1 -> BaseAdapterViewHolder(inflateBinding(parent,R.layout.item_dic_detail_english)) //英文搜索结果详情 | |||
2 -> BaseAdapterViewHolder(inflateBinding(parent,R.layout.item_dic_detail_chinese)) //中文搜索结果详情 | |||
else -> BaseAdapterViewHolder(inflateBinding(parent,R.layout.item_dic_search)) //记录列表 历史和搜索记录列表 | |||
return when (viewType) { | |||
1 -> BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_dic_detail_english)) //英文搜索结果详情 | |||
2 -> BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_dic_detail_chinese)) //中文搜索结果详情 | |||
else -> BaseAdapterViewHolder(inflateBinding(parent, R.layout.item_dic_search)) //记录列表 历史和搜索记录列表 | |||
} | |||
} | |||
override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) { | |||
val itemViewType = getItemViewType(position) | |||
when(itemViewType){ | |||
when (itemViewType) { | |||
//英文 | |||
1 -> { | |||
getItem(position).let { | |||
(holder.binding as ItemDicDetailEnglishBinding).run { | |||
tvWord.text = it.word | |||
when { | |||
it.phonetic_uk.isNullOrEmpty() -> tvPhoneticUk.visibility = View.GONE | |||
else -> tvPhoneticUk.apply { | |||
visibility = View.VISIBLE | |||
text = "英 ${it.phonetic_uk}" | |||
click { v -> //发音 | |||
AudioCache.getDictionaryVoice(it.id,AppConstants.SOUND_TYPE_UK) | |||
} | |||
} | |||
} | |||
when { | |||
it.phonectic_us.isNullOrEmpty() -> tvPhoneticUs.visibility = View.GONE | |||
else -> tvPhoneticUs.apply { | |||
visibility = View.VISIBLE | |||
text = "美 ${it.phonectic_us}" | |||
click { v -> //发音 | |||
AudioCache.getDictionaryVoice(it.id,AppConstants.SOUND_TYPE_US) | |||
} | |||
} | |||
} | |||
when { | |||
it.basic_explaination.isNullOrEmpty() -> tvExplain.visibility = View.GONE | |||
else -> tvExplain.apply { | |||
visibility = View.VISIBLE | |||
text = it.basic_explaination | |||
} | |||
} | |||
when { | |||
it.all_explaination.isNullOrEmpty() -> tvExpandExplain.visibility = View.GONE | |||
else -> tvExpandExplain.apply { | |||
visibility = View.VISIBLE | |||
text = "扩展释义: ${it.all_explaination}" | |||
} | |||
} | |||
incWordDetail.initValue(it.phrase, it.example, it.reference) | |||
} | |||
} | |||
} | |||
//中文 | |||
2 -> { | |||
getItem(position).let { | |||
(holder.binding as ItemDicDetailChineseBinding).run { | |||
tvWord.text = it.word | |||
when { | |||
it.phonectic_cn.isNullOrEmpty() -> tvPhonetic.visibility = View.GONE | |||
else -> tvPhonetic.apply { | |||
visibility = View.VISIBLE | |||
text = it.phonectic_cn | |||
} | |||
} | |||
recyclerView.apply { | |||
layoutManager = LinearLayoutManager(context,LinearLayoutManager.VERTICAL,false) | |||
adapter = adapterChinese | |||
} | |||
//查询数据后更新显示 | |||
vm.queryChineseForEnglishList(it) | |||
} | |||
} | |||
} | |||
//多条记录 搜索结果与历史记录 | |||
3 -> { | |||
val binding = holder.binding as ItemDicSearchBinding | |||
getItem(position).let { | |||
binding.apply { | |||
iv.setImageResource(if (it.inHistory) R.drawable.ic_clock else R.drawable.ic_search) | |||
tvValue.text = it.basic_explaination | |||
tvWord.text = when (vm.detailState) { | |||
DictionaryViewModel.STATE_HISTROY -> it.word | |||
DictionaryViewModel.STATE_SEARCH -> StringUtil.highString(it.word, | |||
vm.keyWord, | |||
ContextCompat.getColor(context, | |||
R.color.theme_color)) | |||
else -> "" | |||
} | |||
//点击,进入详情 | |||
binding.root.click { v -> | |||
vm.insertAndUpdateHistory(it, position) | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
val adapterChinese = DicDetailChinese(vm) | |||
class DicDetailChinese(_vm:DictionaryViewModel) : BaseRVAdapterVM<DictionaryItem,DictionaryViewModel>(_vm){ | |||
override fun coverViewHolder(parent : ViewGroup, viewType : Int) : BaseAdapterViewHolder { | |||
return BaseAdapterViewHolder(inflateBinding(parent,R.layout.item_dic_detail_chinese_for_english_item)) | |||
} | |||
override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) { | |||
getItem(position).let { | |||
(holder.binding as ItemDicDetailChineseForEnglishItemBinding).apply { | |||
tvValue.text = it.basic_explaination | |||
tvWord.text = it.word | |||
ivHoruns.click { v -> //发音 | |||
AudioCache.getDictionaryVoice(it.id,UserInfoManager.getDefaultSoundWay()) | |||
} | |||
//点击,进入详情 | |||
root.click { v -> | |||
//更改或添加进入数据库,更新插入时间,更新历史记录列表位置 | |||
vm.insertAndUpdateHistory(it, position) | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -7,7 +7,7 @@ package com.xkl.cdl.data.bean | |||
*/ | |||
open class DictionaryBean() { | |||
var id : Long = 0 | |||
var word : String? = null | |||
var word : String = "" | |||
var form : Int = 0 // 1 英文 2中文 | |||
var pre_index : String? = null //前缀 | |||
var phonetic_uk : String? = null |
@@ -16,9 +16,11 @@ class DictionaryItem : DictionaryBean() { | |||
//查询时间 | |||
var queryTime: Long = 0 | |||
//词典联想结果判断其是否在历史记录中,如果在历史记录中,则进行时间重排序 | |||
//词典联想结果判断其是否在历史记录中,如果在历史记录中,则进行时间重排序, | |||
//历史记录表中查询,默认是true | |||
//词典查询时,需要设置为false | |||
@Ignore | |||
var inHistory : Boolean = false | |||
var inHistory : Boolean = true | |||
} |
@@ -3,7 +3,11 @@ package com.xkl.cdl.data.exam_record | |||
import androidx.room.Database | |||
import androidx.room.Room | |||
import androidx.room.RoomDatabase | |||
import androidx.room.migration.Migration | |||
import androidx.sqlite.db.SupportSQLiteDatabase | |||
import com.suliang.common.util.file.FileUtil | |||
import com.xkl.cdl.data.bean.DictionaryBean | |||
import com.xkl.cdl.data.bean.DictionaryItem | |||
import com.xkl.cdl.module.XKLApplication | |||
import mqComsumerV1.Struct | |||
import java.io.File | |||
@@ -13,7 +17,7 @@ import java.io.File | |||
* create 2022/7/7 16:50 | |||
* Describe: | |||
*/ | |||
@Database(entities = [Exam::class,ExamItem::class],version = 1,exportSchema = true) | |||
@Database(entities = [Exam::class,ExamItem::class,DictionaryItem::class], version = 2, exportSchema = true) | |||
abstract class AppDatabase : RoomDatabase() { | |||
abstract fun examDao() : ExamDao | |||
abstract fun examItemDao(): ExamItemDao | |||
@@ -23,8 +27,18 @@ abstract class AppDatabase : RoomDatabase() { | |||
companion object{ | |||
private var DATABASE_NAME :String = "${FileUtil.getSaveDirPath("db")}${File.separator}app.db" | |||
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`))") | |||
} | |||
} | |||
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { | |||
Room.databaseBuilder(XKLApplication.instance(), AppDatabase::class.java, DATABASE_NAME).build() | |||
Room.databaseBuilder(XKLApplication.instance(), AppDatabase::class.java, DATABASE_NAME) | |||
.addMigrations(MIGRATION_1_2).build() | |||
} | |||
@JvmStatic |
@@ -1,5 +1,6 @@ | |||
package com.xkl.cdl.data.manager | |||
import com.suliang.common.AppConfig | |||
import com.suliang.common.util.file.FileUtil | |||
import com.xkl.cdl.data.manager.db.DbControlBase | |||
import java.io.File | |||
@@ -79,7 +80,20 @@ class FilePathManager { | |||
fun getMp4File(subjectId : Int,coursePackId : Long,courseId : Long, fileName:String):File{ | |||
return File(getMp4RootPath(), "$subjectId/$coursePackId/$courseId/$fileName") | |||
} | |||
/** 存储音频地址的父文件 */ | |||
fun getVoiceParent() : File { | |||
return FileUtil.getSaveDirFile(AppConfig.VOICE) | |||
} | |||
/** 词典存储音频地址的父文件 */ | |||
fun getDicVoiceParent() : File { | |||
return File(getVoiceParent(),"dic") | |||
} | |||
/** vocabulary存储音频地址的父文件 */ | |||
fun getVocVoiceParent() : File { | |||
return File(getVoiceParent(),"voc") | |||
} | |||
} | |||
} |
@@ -1,13 +1,17 @@ | |||
package com.xkl.cdl.data.manager.db | |||
import android.widget.Toast | |||
import com.google.common.base.Joiner | |||
import androidx.core.database.getBlobOrNull | |||
import com.suliang.common.AppConfig | |||
import com.suliang.common.util.AppGlobals | |||
import com.suliang.common.util.LogUtil | |||
import com.suliang.common.util.file.FileUtil | |||
import com.suliang.common.util.thread.AppExecutors | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.data.bean.DictionaryItem | |||
import com.xkl.cdl.data.manager.FilePathManager | |||
import net.sqlcipher.database.SQLiteDatabase | |||
import java.io.File | |||
import java.lang.StringBuilder | |||
import java.util.* | |||
@@ -20,7 +24,7 @@ object DictionaryManager { | |||
private var mDataBase : SQLiteDatabase? = null | |||
private var pwd : String = "XKL_XECD_DICT_DATA_KEY" | |||
private const val DATA_DICT = "dict" | |||
private const val DATA_DICT = "dict" | |||
private const val MAX_COUNT = 50 | |||
private fun open() { | |||
@@ -57,18 +61,18 @@ object DictionaryManager { | |||
val titleMap : MutableMap<String, Int> = mutableMapOf() | |||
var sql = "" | |||
//1: wor | |||
sql = "SELECT word FROM $DATA_DICT WHERE word = $keyWord" | |||
sql = "SELECT word FROM $DATA_DICT WHERE word = '$keyWord'" | |||
query(sql, titleMap) | |||
//2: wor% %wor% | |||
sql = "SELECT word FROM $DATA_DICT WHERE word LIKE $keyWord || '%' LIMIT $MAX_COUNT" //完全匹配搜索:以关键字开头的搜索 | |||
sql = "SELECT word FROM $DATA_DICT WHERE word LIKE '$keyWord' || '%' LIMIT $MAX_COUNT" //完全匹配搜索:以关键字开头的搜索 | |||
query(sql, titleMap) | |||
if (titleMap.size < MAX_COUNT) { | |||
sql = "SELECT word FROM $DATA_DICT WHERE word LIKE '%' || $keyWord || '%' LIMIT $MAX_COUNT" //查找任意位置包含keyword的值 | |||
sql = "SELECT word FROM $DATA_DICT WHERE word LIKE '%' || '$keyWord' || '%' LIMIT $MAX_COUNT" //查找任意位置包含keyword的值 | |||
query(sql, titleMap) | |||
if (titleMap.size < MAX_COUNT) { | |||
//3:% w % o % r % | |||
var newKeyWord : String = insertPercent(keyWord) | |||
val length = keyWord.length | |||
val length = newKeyWord.length | |||
for (i in 0 until length - 1) { | |||
val max : Int = MAX_COUNT - titleMap.size | |||
newKeyWord = substringEnd(newKeyWord) | |||
@@ -100,6 +104,7 @@ object DictionaryManager { | |||
* @param hashMap | |||
*/ | |||
private fun query(sql : String, hashMap : MutableMap<String, Int>) { | |||
LogUtil.e(sql) | |||
mDataBase?.rawQuery(sql, null)?.let { | |||
while (it.moveToNext()) { | |||
if (hashMap.size == MAX_COUNT) break //数量15则打断 | |||
@@ -173,6 +178,7 @@ object DictionaryManager { | |||
example = it.getString(13) | |||
reference = it.getString(14) | |||
// setVersion(it.getInt(15)) //version版本 | |||
inHistory = false | |||
} | |||
} | |||
it.close() | |||
@@ -187,9 +193,10 @@ object DictionaryManager { | |||
* @param courseId 课程查询的courseId | |||
* @return | |||
*/ | |||
fun querys(words : List<String>,courseId : Long = 0 ) : List<DictionaryItem> { | |||
fun querys(words : String ,courseId : Long = 0 ) : MutableList<DictionaryItem> { | |||
val list : MutableList<DictionaryItem> = ArrayList<DictionaryItem>() | |||
val sql = "SELECT * FROM " + DATA_DICT + " WHERE word IN (" + Joiner.on(",").join(words) + ")" | |||
// val sql = "SELECT * FROM " + DATA_DICT + " WHERE word IN (" + Joiner.on(",").join(words) + ")" | |||
val sql = "SELECT * FROM $DATA_DICT WHERE word IN ( $words)" | |||
open() | |||
mDataBase?.rawQuery(sql, null)?.let { | |||
while (it.moveToNext()) { | |||
@@ -210,6 +217,7 @@ object DictionaryManager { | |||
phrase = it.getString(12) | |||
example = it.getString(13) | |||
reference = it.getString(14) | |||
inHistory = false | |||
} | |||
list.add(mDictionaryItem) | |||
} | |||
@@ -219,6 +227,49 @@ object DictionaryManager { | |||
//去重 排序 | |||
return list.distinctBy { it.word }.sortedBy { | |||
words.indexOf(it.word) | |||
}.toMutableList() | |||
} | |||
/** | |||
* 查询发音音频,并写入文件,返回地址 | |||
* 查询到内容,直接保存为对应发音文件,如果有一个为空,则保存为另一个,如果都为空,则都不保存 | |||
* @param id Long | |||
* @param soundWay 发音方式 | |||
* @return 对应发音的路劲 | |||
*/ | |||
fun queryAudio(id:Long, soundWay : Int): String?{ | |||
open() | |||
val sql = "SELECT audio_uk,audio_us FROM $DATA_DICT WHERE id = $id" | |||
var result: String? = null | |||
mDataBase?.rawQuery(sql,null)?.let { | |||
while (it.moveToNext()){ | |||
val audioUk = it.getBlobOrNull(0) | |||
val audioUs = it.getBlobOrNull(0) | |||
val parentFile = FilePathManager.getDicVoiceParent() | |||
audioUk?.let { | |||
val writeFile = File(parentFile,"${id}_${AppConstants.SOUND_TYPE_UK}") | |||
FileUtil.writeBytesToFile(writeFile,it) | |||
}?:audioUs?.let { | |||
val writeFile = File(parentFile,"${id}_${AppConstants.SOUND_TYPE_UK}") | |||
FileUtil.writeBytesToFile(writeFile,it) | |||
} | |||
audioUs?.let { | |||
val writeFile = File(parentFile,"${id}_${AppConstants.SOUND_TYPE_US}") | |||
FileUtil.writeBytesToFile(writeFile,it) | |||
}?:audioUk?.let { | |||
val writeFile = File(parentFile,"${id}_${AppConstants.SOUND_TYPE_US}") | |||
FileUtil.writeBytesToFile(writeFile,it) | |||
} | |||
if (audioUk != null || audioUs != null){ | |||
result = File(parentFile,"${id}_$soundWay").path | |||
} | |||
} | |||
it.close() | |||
} | |||
return result | |||
} | |||
} |
@@ -88,7 +88,7 @@ object VocabularyManager { | |||
it.close() | |||
} | |||
//不为空,写入本身,如果为空,用另外的发音方式写入 | |||
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||
val parentPath = FilePathManager.getVocVoiceParent() | |||
val audio_us_file_path = audio_us?.let { | |||
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_US}" | |||
val file = File(parentPath, audioFileNameUS) |
@@ -1,13 +1,16 @@ | |||
package com.xkl.cdl.data.repository | |||
import android.net.IpPrefix | |||
import android.util.LruCache | |||
import androidx.lifecycle.MutableLiveData | |||
import com.suliang.common.AppConfig | |||
import com.suliang.common.extension.diskIo2Main | |||
import com.suliang.common.util.file.FileUtil | |||
import com.xkl.cdl.data.AppConstants | |||
import com.xkl.cdl.data.manager.FilePathManager | |||
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.data.manager.db.VocabularyManager | |||
import io.reactivex.rxjava3.core.Observable | |||
import java.io.File | |||
@@ -42,99 +45,126 @@ object AudioCache { | |||
* 1缓存 2文件 3数据库 | |||
* @return String 文件路径 | |||
*/ | |||
fun get(dbControlBase : DbControlBase, wordId : Long, soundWay : Int) { | |||
fun getLearnVoice(dbControlBase : DbControlBase, wordId : Long, soundWay : Int) { | |||
//父文件路劲 | |||
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||
//先查对应的文件路径 : 项目id_课程包id_课程id_单词id_发音方式 | |||
val defaultKey = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}_$soundWay" | |||
val parentPath = FilePathManager.getVoiceParent() | |||
//先查对应的文件路径 : 项目id_课程包id_课程id_单词id 发音方式在内部拼接 | |||
val fileName = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}" | |||
getVoicePath(parentPath,fileName,"",soundWay) {DBCourseManager.queryAudio(dbControlBase, wordId, soundWay) } | |||
} | |||
/** | |||
* 获取词汇量测试发音音频 | |||
* @param wordId Long | |||
* @param soundWay Int | |||
*/ | |||
fun getVocabularyVoice(wordId : Long, soundWay : Int){ | |||
//父文件路劲 | |||
val parentPath = FilePathManager.getVocVoiceParent() | |||
getVoicePath(parentPath,"$wordId","v",soundWay) { VocabularyManager.queryAudio(wordId, soundWay) } | |||
} | |||
/** | |||
* 获取词典音频 | |||
* @param wordId Long | |||
* @param soundWay Int | |||
*/ | |||
fun getDictionaryVoice(wordId : Long,soundWay : Int){ | |||
//父文件路劲 | |||
val parentPath = FilePathManager.getDicVoiceParent() | |||
getVoicePath(parentPath,"$wordId","d",soundWay) { DictionaryManager.queryAudio(wordId, soundWay) } | |||
} | |||
/** | |||
* 获取对应发音文件,使用audioLiveData发送回去 | |||
* 查询缓存 -> 查询文件 -> 查询数据库 | |||
* @param parentPath File 父文件 | |||
* @param fileName String 文件名称,没有加发音格式,需要评价发音格式,用于存key的时候,需要添加前缀 | |||
* @param prefix String 存key时用的前缀,课程、词汇等的时候,为空, 词汇量测试的时候为 "v" , 词典的时候为"d" | |||
* @param soundWay Int 发音方式,拼接在发音文件后面 | |||
* @param {} -> String | |||
*/ | |||
private fun getVoicePath(parentPath:File,fileName:String, prefix : String, soundWay : Int, queryMethod : ()-> String?){ | |||
val defaultKey = "${prefix}_${fileName}_${soundWay}" | |||
Observable.fromCallable { | |||
//不为空,直接取值 | |||
if (lruCache.get(defaultKey) == null){ | |||
//为空 查文件 | |||
val file = File(parentPath, defaultKey) | |||
when { | |||
file.exists() -> lruCache.put(defaultKey, file.path) //文件存在,则表示以前进行过数据库查询,且进行了保存文件,英美发音本地都有文件 | |||
else -> { //文件不存在,则查询数据库进行查询,返回的为保存的文件地址 | |||
val filePath = DBCourseManager.queryAudio(dbControlBase, wordId, soundWay) | |||
filePath?.also { | |||
when(soundWay){ | |||
AppConstants.SOUND_TYPE_UK -> { | |||
lruCache.put(defaultKey, it) //保存路径 | |||
val audioFileNameUS = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}_${AppConstants.SOUND_TYPE_US}" | |||
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径 | |||
} | |||
AppConstants.SOUND_TYPE_US -> { | |||
lruCache.put(defaultKey, it) //保存路径 | |||
val audioFileNameUS = "${dbControlBase.subjectId}_${dbControlBase.coursePackId}_${dbControlBase.courseId}_${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径 | |||
} | |||
else -> lruCache.put(defaultKey, it) //保存路径 | |||
return@fromCallable lruCache[defaultKey]?:let { | |||
val file = File(parentPath,"${fileName}_$soundWay") //存储的文件 | |||
if (file.exists()) { | |||
lruCache.put(defaultKey,file.path) | |||
}else{ | |||
queryMethod.invoke()?.let { | |||
when(soundWay){ | |||
AppConstants.SOUND_TYPE_US -> { | |||
val audioUkKey = "${prefix}_${fileName}_${AppConstants.SOUND_TYPE_UK}" | |||
val audioUk = File(parentPath,"${fileName}_${AppConstants.SOUND_TYPE_UK}").path | |||
lruCache.put(audioUkKey,audioUk) | |||
} | |||
AppConstants.SOUND_TYPE_UK -> { | |||
val audioUsKey = "${prefix}_${fileName}_${AppConstants.SOUND_TYPE_US}" | |||
val audioUs = File(parentPath,"${fileName}_${AppConstants.SOUND_TYPE_US}").path | |||
lruCache.put(audioUsKey,audioUs) | |||
} | |||
} //如果查询返回路径为空,则说明,没有发音内容 | |||
} | |||
lruCache.put(defaultKey,it) | |||
} | |||
} | |||
lruCache[defaultKey]?:"" | |||
} | |||
return@fromCallable lruCache.get(defaultKey) ?: "" | |||
}.compose(diskIo2Main()).subscribe { | |||
if (this::audioLiveData.isInitialized) | |||
audioLiveData.value = it | |||
audioLiveData.value = it | |||
} | |||
} | |||
/** | |||
* 口语总测获取发音文件 | |||
* @param fileName String 发音文件名称 | |||
* @return String 发音文件路径 | |||
*/ | |||
fun get(fileName : String):String { | |||
fun getSpokenVoice(fileName : String):String { | |||
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||
if (lruCache[fileName] == null){ | |||
lruCache.put(fileName,File(parentPath,fileName).path) | |||
} | |||
return lruCache[fileName] | |||
} | |||
fun getVocabulary(wordId : Long,soundWay : Int){ | |||
//父文件路劲 | |||
val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||
//先查对应的文件路径 : 项目id_课程包id_课程id_单词id_发音方式 | |||
val defaultKey = "${wordId}_$soundWay" | |||
Observable.fromCallable { | |||
//不为空,直接取值 | |||
if (lruCache.get(defaultKey) == null){ | |||
//为空 查文件 | |||
val file = File(parentPath, defaultKey) | |||
when { | |||
file.exists() -> lruCache.put(defaultKey, file.path) //文件存在,则表示以前进行过数据库查询,且进行了保存文件,英美发音本地都有文件 | |||
else -> { //文件不存在,则查询数据库进行查询,返回的为保存的文件地址 | |||
val filePath = VocabularyManager.queryAudio(wordId, soundWay) | |||
filePath?.also { | |||
when(soundWay){ | |||
AppConstants.SOUND_TYPE_UK -> { | |||
lruCache.put(defaultKey, it) //保存路径 | |||
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_US}" | |||
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径 | |||
} | |||
AppConstants.SOUND_TYPE_US -> { | |||
lruCache.put(defaultKey, it) //保存路径 | |||
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径 | |||
} | |||
else -> lruCache.put(defaultKey, it) //保存路径 | |||
/* | |||
Observable.fromCallable { | |||
//不为空,直接取值 | |||
if (lruCache.get(defaultKey) == null){ | |||
//为空 查文件 | |||
val file = File(parentPath, defaultKey) | |||
when { | |||
file.exists() -> lruCache.put(defaultKey, file.path) //文件存在,则表示以前进行过数据库查询,且进行了保存文件,英美发音本地都有文件 | |||
else -> { //文件不存在,则查询数据库进行查询,返回的为保存的文件地址 | |||
val filePath = VocabularyManager.queryAudio(wordId, soundWay) | |||
filePath?.also { | |||
when(soundWay){ | |||
AppConstants.SOUND_TYPE_UK -> { | |||
lruCache.put(defaultKey, it) //保存路径 | |||
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_US}" | |||
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径 | |||
} | |||
} //如果查询返回路径为空,则说明,没有发音内容 | |||
} | |||
AppConstants.SOUND_TYPE_US -> { | |||
lruCache.put(defaultKey, it) //保存路径 | |||
val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_UK}" | |||
lruCache.put(audioFileNameUS, File(parentPath,audioFileNameUS).path) //保存路径 | |||
} | |||
else -> lruCache.put(defaultKey, it) //保存路径 | |||
} | |||
} //如果查询返回路径为空,则说明,没有发音内容 | |||
} | |||
} | |||
return@fromCallable lruCache.get(defaultKey) ?: "" | |||
}.compose(diskIo2Main()).subscribe { | |||
if (this::audioLiveData.isInitialized) | |||
audioLiveData.value = it | |||
} | |||
return@fromCallable lruCache.get(defaultKey) ?: "" | |||
}.compose(diskIo2Main()).subscribe { | |||
if (this::audioLiveData.isInitialized) | |||
audioLiveData.value = it | |||
}*/ | |||
} | |||
} |
@@ -536,11 +536,11 @@ class LearnExamActivity : BaseActivityVM<ActivityLearnExamBinding, LearnExamView | |||
if (view is LottieAnimationView && view.visibility == View.VISIBLE) currentPlayView = view | |||
when{ | |||
//词汇量测试 | |||
vm.intentData.examType == AppConstants.TEST_TYPE_NORMAL -> AudioCache.getVocabulary(it.word_id,vm.defaultSoundWay) | |||
vm.intentData.examType == AppConstants.TEST_TYPE_NORMAL -> AudioCache.getVocabularyVoice(it.word_id, vm.defaultSoundWay) | |||
//口语 | |||
isSpokenTotalTest() -> AudioCache.get(view.tag as String) | |||
isSpokenTotalTest() -> AudioCache.getSpokenVoice(view.tag as String) | |||
//测试 | |||
else -> AudioCache.get(vm.dbBaseControl, it.word_id, vm.defaultSoundWay) | |||
else -> AudioCache.getLearnVoice(vm.dbBaseControl, it.word_id, vm.defaultSoundWay) | |||
} | |||
} | |||
} |
@@ -2,16 +2,13 @@ package com.xkl.cdl.module.learn | |||
import android.annotation.SuppressLint | |||
import android.graphics.Color | |||
import android.opengl.Visibility | |||
import android.os.Bundle | |||
import android.text.SpannableStringBuilder | |||
import android.view.MotionEvent | |||
import android.view.View | |||
import android.view.ViewGroup | |||
import android.widget.TextView | |||
import androidx.constraintlayout.widget.ConstraintLayout | |||
import androidx.core.content.ContextCompat | |||
import androidx.fragment.app.FragmentManager | |||
import androidx.lifecycle.ViewModelProvider | |||
import androidx.recyclerview.widget.GridLayoutManager | |||
import androidx.recyclerview.widget.LinearLayoutManager | |||
@@ -767,7 +764,7 @@ class LearnWordActivity : BaseActivityVM<ActivityLearnWordBinding, LearnWordView | |||
when (vm.learnData.lesson.coursePackType) { | |||
AppConstants.COURSEPACK_TYPE_ENGLISH_WORD, AppConstants.COURSEPACK_TYPE_ENGLISH_SOUNDMARK, AppConstants.COURSEPACK_TYPE_ENGLISH_SPOKEN, AppConstants.COURSEPACK_TYPE_CHINESE_LITERACY, AppConstants.COURSEPACK_TYPE_CHINESE_PINYIN -> { | |||
val valueWord = vm.currentLearnWord.value!! | |||
AudioCache.get(vm.dbControlBase, valueWord.wordId, vm.defaultSoundWay) | |||
AudioCache.getLearnVoice(vm.dbControlBase, valueWord.wordId, vm.defaultSoundWay) | |||
} | |||
} | |||
} |
@@ -1,10 +1,8 @@ | |||
package com.xkl.cdl.module.m_memo | |||
import android.annotation.SuppressLint | |||
import android.app.TimePickerDialog | |||
import android.os.Bundle | |||
import android.view.View | |||
import android.widget.TimePicker | |||
import androidx.lifecycle.ViewModelProvider | |||
import androidx.recyclerview.widget.LinearLayoutManager | |||
import com.haibin.calendarview.Calendar | |||
@@ -28,7 +26,6 @@ import com.xkl.cdl.data.event.LearnEventData | |||
import com.xkl.cdl.data.repository.AudioCache | |||
import com.xkl.cdl.databinding.ActivityMemoListDetailBinding | |||
import com.xkl.cdl.module.learn.LearnWordActivity | |||
import com.xkl.cdl.module.m_center_learn.coursechildren.CourseMainFragment | |||
import java.util.* | |||
import java.util.Calendar.* | |||
@@ -76,7 +73,7 @@ class MemoListDetailActivity : BaseActivityVM<ActivityMemoListDetailBinding, Mem | |||
needShowEmptyView = true | |||
//处理发音事件 | |||
soundListener = { wordId, soundWay -> | |||
AudioCache.get(vm.dbControlBase, wordId, soundWay) | |||
AudioCache.getLearnVoice(vm.dbControlBase, wordId, soundWay) | |||
} | |||
} | |||
} |
@@ -5,18 +5,27 @@ import android.content.Context | |||
import android.content.Intent | |||
import android.os.Bundle | |||
import android.view.View | |||
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.ActivityDictionaryBinding | |||
import com.xkl.cdl.dialog.CommonDialog | |||
import com.xkl.cdl.dialog.CommonDialogBean | |||
import com.xkl.cdl.module.m_service_center.DictionaryViewModel.Companion.STATE_HISTROY | |||
import com.xkl.cdl.module.m_service_center.DictionaryViewModel.Companion.STATE_SEARCH | |||
/** | |||
* author suliang | |||
* create 2022/8/9 14:53 | |||
* Describe: 词典 | |||
* 历史记录: 查询数据库 历史记录点击进入详情并更新记录 | |||
* 词典搜索 :1、输入联想搜索 2、联想结果点击详情显示(显示结果,更新记录) 3、联想结果中文详情点击英文item进入详情 | |||
*/ | |||
class DictionaryActivity : BaseActivityVM<ActivityDictionaryBinding, DictionaryViewModel>() { | |||
companion object { | |||
@@ -36,24 +45,28 @@ class DictionaryActivity : BaseActivityVM<ActivityDictionaryBinding, DictionaryV | |||
override fun initActivity(savedInstanceState : Bundle?) { | |||
vm.courseId = intent.getLongExtra(AppConfig.INTENT_1, 0) | |||
binding.titleBar.onBackClick = {finish()} | |||
initHistoryRecyclerView() | |||
//点击 查看更多 | |||
binding.tvSeeMore.click { | |||
when (vm.detailState) { | |||
STATE_HISTROY -> { | |||
DictionaryViewModel.STATE_HISTROY -> { | |||
//显示页面加1, 列表更新 | |||
vm.histroyShowPage++ | |||
vm.adapter.notifyDataSetChanged() | |||
//根据数量判断,是否还要显示 | |||
binding.tvSeeMore.visibility = if (vm.historyList.size > vm.histroyShowPage * vm.historyPageSize) View.VISIBLE else View.GONE | |||
binding.tvSeeMore.visibility = if (vm.historyList.size > vm.histroyShowPage * vm.pageShowCount) View.VISIBLE else View.GONE | |||
binding.rv.smoothScrollToPosition(vm.histroyShowPage * vm.pageShowCount - 1) | |||
} | |||
STATE_SEARCH -> { | |||
DictionaryViewModel.STATE_SEARCH -> { | |||
//显示页面加1, 列表更新 | |||
vm.searchShowPage++ | |||
vm.adapter.notifyDataSetChanged() | |||
//根据数量判断,是否还要显示 | |||
binding.tvSeeMore.visibility = if (vm.searchDetailList.size > vm.searchShowPage * vm.historyPageSize) View.VISIBLE else View.GONE | |||
binding.tvSeeMore.visibility = if (vm.searchAssociateList.size > vm.searchShowPage * vm.pageShowCount) View.VISIBLE else View.GONE | |||
binding.rv.smoothScrollToPosition(vm.searchShowPage * vm.pageShowCount - 1) | |||
} | |||
} | |||
} | |||
@@ -70,96 +83,116 @@ class DictionaryActivity : BaseActivityVM<ActivityDictionaryBinding, DictionaryV | |||
} | |||
}.show(supportFragmentManager, javaClass.name) | |||
} | |||
} | |||
private fun initHistoryRecyclerView() { | |||
binding.rv.apply { | |||
layoutManager = LinearLayoutManager(this@DictionaryActivity, LinearLayoutManager.VERTICAL, false) | |||
adapter = vm.adapter | |||
} | |||
} | |||
@SuppressLint("NotifyDataSetChanged") | |||
override fun loadData() { | |||
//更新历史记录适配器 | |||
vm.updateHistoryAdapterLiveData.observe(this) { | |||
//监听EditText变化 | |||
// 1 点清空或没有内容时 显示历史记录 | |||
// 2 关键字搜索 显示搜索记录 | |||
// 3 点击历史记录或者点击搜索记录,更新显示详情 | |||
// 4 单独定义一个值,定义为点击了历史记录或搜索记录 | |||
binding.etSearch.addTextChangedListener{ | |||
vm.handler.removeCallbacksAndMessages(null) | |||
when { | |||
//历史记录 | |||
!it -> vm.adapter.apply { | |||
//如果非历史列表记录,则更新列表布局的状态 | |||
if (vm.detailState != STATE_HISTROY) { | |||
vm.detailState = STATE_HISTROY | |||
binding.apply { | |||
tvHistoryTitle.visibility = View.VISIBLE | |||
ivDelete.visibility = View.VISIBLE | |||
} | |||
} | |||
binding.layoutDetail.visibility = if (vm.historyList.isEmpty()) View.GONE else View.VISIBLE | |||
binding.tvSeeMore.visibility = if (vm.historyList.size > vm.histroyShowPage * vm.historyPageSize) View.VISIBLE else View.GONE | |||
//更新值 | |||
needShowEmptyView = false | |||
setData(vm.historyList) | |||
it.isNullOrEmpty() -> { //内容为空,显示为历史记录 | |||
LogUtil.e("EditText输入数据为空: 空数据,显示历史记录") | |||
vm.detailState = DictionaryViewModel.STATE_HISTROY | |||
vm.updateHistoryAdapterLiveData.value = vm.detailState | |||
} | |||
//搜索结果 | |||
else -> vm.adapter.apply { | |||
if (vm.detailState != STATE_SEARCH) { | |||
vm.detailState = STATE_SEARCH | |||
binding.apply { | |||
tvHistoryTitle.visibility = View.GONE | |||
ivDelete.visibility = View.GONE | |||
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()) | |||
} | |||
} | |||
binding.layoutDetail.visibility = View.VISIBLE | |||
binding.tvSeeMore.visibility = if (vm.searchDetailList.size > vm.searchShowPage * vm.historyPageSize) View.VISIBLE else View.GONE | |||
needShowEmptyView = vm.searchDetailList.size == 0 | |||
setData(vm.searchDetailList) | |||
} | |||
} | |||
} | |||
/**搜索监听*/ | |||
vm.etSearchLiveData.observe(this) { | |||
when { | |||
//直接历史记录更新 | |||
it.isNullOrEmpty() -> vm.updateHistoryAdapterLiveData.value = false | |||
//历史记录的详情 | |||
vm.searchItemDetailAble -> { | |||
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) | |||
} | |||
} | |||
//关键字搜索 | |||
else -> vm.searchKeyWord(it) | |||
} | |||
} | |||
//查询历史记录 | |||
vm.loadHistory() | |||
/**发音监听*/ | |||
AudioCache.initAudioLiveData().observe(this){ | |||
it?.let { | |||
MPManager.play(it) | |||
}?: showToast("未找到发音文件") | |||
} | |||
} | |||
/** | |||
* 详情中的状态 | |||
* @param state Int 1历史记录 2搜索详情 | |||
*/ | |||
private fun initLayoutDetailState(state : Int) { | |||
when (state) { | |||
//历史布局 | |||
STATE_HISTROY -> binding.apply { | |||
tvHistoryTitle.visibility = View.VISIBLE | |||
ivDelete.visibility = View.VISIBLE | |||
} | |||
//查询详情 | |||
STATE_SEARCH -> binding.apply { | |||
tvHistoryTitle.visibility = View.GONE | |||
ivDelete.visibility = View.GONE | |||
} | |||
private fun initHistoryRecyclerView() { | |||
binding.rv.apply { | |||
layoutManager = LinearLayoutManager(this@DictionaryActivity, LinearLayoutManager.VERTICAL, false) | |||
adapter = vm.adapter | |||
} | |||
binding.tvSeeMore.visibility = View.GONE | |||
} | |||
@SuppressLint("NotifyDataSetChanged") | |||
override fun loadData() { | |||
//查询历史记录 | |||
vm.loadHistory() | |||
} | |||
} |
@@ -4,6 +4,7 @@ import android.os.Handler | |||
import android.os.Looper | |||
import androidx.lifecycle.MutableLiveData | |||
import com.suliang.common.base.viewmodel.BaseViewModel | |||
import com.suliang.common.extension.io2Main | |||
import com.suliang.common.util.thread.AppExecutors | |||
import com.xkl.cdl.adapter.DictionaryAdapter | |||
import com.xkl.cdl.data.bean.DictionaryItem | |||
@@ -19,43 +20,44 @@ class DictionaryViewModel : BaseViewModel() { | |||
//记录状态 | |||
companion object { | |||
const val STATE_HISTROY = 1 | |||
const val STATE_SEARCH = 2 | |||
const val STATE_HISTROY = 1 //历史记录 | |||
const val STATE_SEARCH = 2 //关键字搜索结果 | |||
const val STATE_DETAIL = 3 //详情展示 | |||
} | |||
//状态 : 默认历史记录 | |||
var detailState = STATE_HISTROY | |||
//适配器 | |||
val adapter = DictionaryAdapter(this) | |||
//输入框监听 | |||
//手动改变editText Value | |||
val etSearchLiveData = MutableLiveData<String>() | |||
//搜索关键子 | |||
var keyWord : String? = null | |||
//状态 : 默认历史记录 | |||
var detailState = STATE_HISTROY | |||
//历史记录集合 | |||
val historyList = mutableListOf<DictionaryItem>() | |||
//搜索结果 可为关键字搜索结果 | |||
var searchAssociateList = mutableListOf<DictionaryItem>() | |||
//详情结果 固定一个item的大小,即显示详情 | |||
var detailItemList: MutableList<DictionaryItem>? = null | |||
//更新详情适配器 false:历史记录 true:联想搜索结果 | |||
val updateHistoryAdapterLiveData = MutableLiveData<Boolean>() | |||
//更新详情适配器 STATE_HISTROY历史记录 STATE_SEARCH 搜索结果 STATE_DETAIL 详情 | |||
val updateHistoryAdapterLiveData = MutableLiveData<Int>() | |||
//一页显示的历史记录个数,最多历史记录显示30个 搜索结果50个 | |||
val pageShowCount = 15 | |||
//历史记录,显示的页数 | |||
var histroyShowPage = 1 | |||
var searchShowPage = 1 | |||
//一页显示的历史记录个数,最多历史记录显示30个 | |||
val historyPageSize = 10 | |||
//搜索结果,做多匹配显示50个 | |||
//是否显示搜索单词的详情 true:显示单条记录的详情 false:搜索显示的为搜索结果的列表详情 | |||
var searchItemDetailAble = false | |||
//搜索结果 多条为联想结果 单条基本为搜索的详情结果 | |||
var searchDetailList = mutableListOf<DictionaryItem>() | |||
val handler : Handler = Handler(Looper.getMainLooper()) | |||
//记录联想结果中为历史记录的位置 | |||
val searchListForHistoryPositionMap = mutableMapOf<Int,Int>() | |||
private val searchListForHistoryPositionMap = mutableMapOf<Int, Int>() | |||
/** | |||
@@ -65,17 +67,19 @@ class DictionaryViewModel : BaseViewModel() { | |||
Observable.fromCallable { | |||
val history = AppDatabase.instance.dictionaryDao().query(courseId) | |||
historyList.addAll(history) | |||
updateHistoryAdapterLiveData.postValue(false) | |||
}.observeOn(Schedulers.from(AppExecutors.io)).subscribe() | |||
if (detailState == STATE_HISTROY) | |||
updateHistoryAdapterLiveData.postValue(detailState) | |||
}.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe() | |||
} | |||
/**清空历史记录*/ | |||
fun clearHistory() { | |||
Observable.fromCallable { | |||
historyList.clear() | |||
updateHistoryAdapterLiveData.postValue(false) | |||
if (detailState == STATE_HISTROY) | |||
updateHistoryAdapterLiveData.postValue(detailState) | |||
AppDatabase.instance.dictionaryDao().clear(courseId) | |||
}.observeOn(Schedulers.from(AppExecutors.io)).subscribe() | |||
}.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe() | |||
} | |||
/** | |||
@@ -83,17 +87,17 @@ class DictionaryViewModel : BaseViewModel() { | |||
* @param keyWord String | |||
*/ | |||
fun searchKeyWord(keyWord : String) { | |||
handler.removeCallbacksAndMessages(null) | |||
this.keyWord = keyWord | |||
handler.postDelayed({ | |||
Observable.fromCallable { | |||
//清空位置对应信息 | |||
searchListForHistoryPositionMap.clear() | |||
//联想查询结果 | |||
searchDetailList = DictionaryManager.queryKeyWordLikeMatchSimple(keyWord) | |||
searchAssociateList = DictionaryManager.queryKeyWordLikeMatchSimple(keyWord) | |||
//搜索记录中获取历史记录位置 需要判断是否有历史记录 | |||
if (historyList.isNotEmpty() && searchDetailList.isNotEmpty()) { | |||
if (historyList.isNotEmpty() && searchAssociateList.isNotEmpty()) { | |||
historyList.forEachIndexed { historyIndex, historyItem -> | |||
searchDetailList.forEachIndexed { searchIndex, searchItem -> | |||
searchAssociateList.forEachIndexed { searchIndex, searchItem -> | |||
if (historyItem.word == searchItem.word) { | |||
searchItem.inHistory = true | |||
searchListForHistoryPositionMap.put(searchIndex, historyIndex) | |||
@@ -101,11 +105,84 @@ class DictionaryViewModel : BaseViewModel() { | |||
} | |||
} | |||
} | |||
//更新搜索结果 | |||
updateHistoryAdapterLiveData.postValue(true) | |||
}.observeOn(Schedulers.from(AppExecutors.io)).subscribe() | |||
}, 300) | |||
//更新搜索结果 : 避免搜索结束后,显示状态已改变,发送出去消息后,造成更新错乱 | |||
if (detailState == STATE_SEARCH) { | |||
updateHistoryAdapterLiveData.postValue(detailState) | |||
} | |||
}.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe() | |||
}, 200) | |||
} | |||
/** | |||
* 查询中文item下的多个英文item集合 | |||
* @param it DictionaryItem | |||
* @return MutableList<DictionaryItem>? | |||
*/ | |||
fun queryChineseForEnglishList(chineseItem : DictionaryItem) { | |||
Observable.fromCallable { | |||
//清空位置对应信息 | |||
searchListForHistoryPositionMap.clear() | |||
//精准查询结果 | |||
return@fromCallable chineseItem.basic_explaination?.let { | |||
val replace = it.replace(";", "\',\'") | |||
val querys = DictionaryManager.querys("\'$replace\'") | |||
//搜索记录中获取历史记录位置 需要判断是否有历史记录 | |||
if (historyList.isNotEmpty() && querys.isNotEmpty()) { | |||
historyList.forEachIndexed { historyIndex, historyItem -> | |||
querys.forEachIndexed { searchIndex, searchItem -> | |||
if (historyItem.word == searchItem.word) { | |||
searchItem.inHistory = true | |||
searchListForHistoryPositionMap.put(searchIndex, historyIndex) | |||
} | |||
} | |||
} | |||
} | |||
querys | |||
} ?: mutableListOf<DictionaryItem>() | |||
}.compose(io2Main()).subscribe { | |||
if (detailState == STATE_DETAIL) | |||
adapter.adapterChinese.setData(it) | |||
} | |||
} | |||
/** | |||
* 添加历史记录中 | |||
* @param item DictionaryItem | |||
* @param position Int 搜索列表中的位置 | |||
*/ | |||
fun insertAndUpdateHistory(item : DictionaryItem, position : Int) { | |||
detailState = STATE_DETAIL | |||
Observable.fromCallable { | |||
item.queryTime = System.currentTimeMillis() | |||
//添加到历史记录第一个 | |||
if (!item.inHistory) { | |||
item.inHistory = true | |||
if (historyList.isNotEmpty() && historyList.size >= 30) { | |||
historyList.removeLast() //移除最后一个 | |||
} | |||
} else if(detailState == STATE_SEARCH){ // 搜索结果的item点击,需要移除历史记录中对应的位置 | |||
searchListForHistoryPositionMap.get(position)?.let { | |||
historyList.removeAt(it) //移除其在历史记录中的位置 | |||
} | |||
}else if (detailState == STATE_HISTROY){ //历史记录的item点击,移除该条历史记录 | |||
historyList.removeAt(position) | |||
} | |||
//添加到历史记录第一个 | |||
historyList.add(0, item) | |||
//重新赋值需要用于刷新的集合 | |||
detailItemList = mutableListOf(item) | |||
//更改inputEditView的值,重新赋值列表集合 | |||
etSearchLiveData.postValue(item.word) | |||
//更新或插入历史搜索记录中 | |||
AppDatabase.instance.dictionaryDao().insert(item) | |||
}.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe() | |||
} | |||
} |
@@ -54,11 +54,13 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() { | |||
//复制词典 和 词汇量测试 | |||
val dictionary = FilePathManager.getDictionaryDbPath() | |||
if (!dictionary.exists()) { | |||
FileUtil.copyAsset("dictionary.db", dictionary) | |||
if (!dictionary.exists() || dictionary.length() != FileUtil.getAssetFileSize("dictionary.db")) { | |||
LogUtil.e("复制dictionary.db") | |||
FileUtil.copyAsset("dictionary.db", dictionary) | |||
} | |||
val vocabulary = FilePathManager.getVocabularyDbPath() | |||
if (!vocabulary.exists()) { | |||
if (!vocabulary.exists() || vocabulary.length() != FileUtil.getAssetFileSize("vocabulary.db")) { | |||
LogUtil.e("复制vocabulary.db") | |||
FileUtil.copyAsset("vocabulary.db", vocabulary) | |||
} | |||
@@ -1,14 +1,15 @@ | |||
package com.xkl.cdl.util | |||
import android.text.Html | |||
import android.text.Spanned | |||
import android.text.Spannable | |||
import android.text.SpannableStringBuilder | |||
import android.text.style.ForegroundColorSpan | |||
import android.widget.TextView | |||
import androidx.annotation.ColorInt | |||
import androidx.annotation.ColorRes | |||
import androidx.core.content.ContextCompat | |||
import com.suliang.common.util.AppGlobals | |||
import com.suliang.common.util.ColorUtil | |||
import com.suliang.common.util.os.ScreenUtil | |||
import java.util.* | |||
import java.util.regex.Pattern | |||
/** | |||
@@ -103,6 +104,6 @@ class ViewUtil { | |||
m.appendTail(buffer) | |||
return buffer.toString() | |||
} | |||
} | |||
} |
@@ -4,9 +4,6 @@ | |||
xmlns:tools="http://schemas.android.com/tools"> | |||
<data> | |||
<variable | |||
name="vm" | |||
type="com.xkl.cdl.module.m_service_center.DictionaryViewModel" /> | |||
</data> | |||
<androidx.constraintlayout.widget.ConstraintLayout | |||
@@ -58,26 +55,31 @@ | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:background="@drawable/shape_rounder_8_stroke_gray1" | |||
android:imeOptions="actionDone" | |||
android:paddingStart="@dimen/global_spacing" | |||
android:paddingEnd="@dimen/global_spacing" | |||
android:drawablePadding="4dp" | |||
android:textSize="@dimen/smallSize" | |||
android:gravity="center_vertical" | |||
android:hint="输入你要搜索的内容…" | |||
android:text="@{vm.etSearchLiveData}"/> | |||
android:singleLine="true" | |||
android:hint="输入你要搜索的内容…"/> | |||
<androidx.constraintlayout.widget.ConstraintLayout | |||
android:id="@+id/layout_detail" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:layout_height="0dp" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/et_search" | |||
app:layout_constraintBottom_toBottomOf="parent" | |||
app:layout_constraintVertical_bias="0" | |||
android:layout_marginTop="12dp" | |||
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"> | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
app:layout_constrainedHeight="true"> | |||
<View | |||
android:id="@+id/v_line" | |||
@@ -115,7 +117,12 @@ | |||
android:id="@+id/rv" | |||
android:layout_width="match_parent" | |||
android:layout_height="wrap_content" | |||
app:layout_constraintTop_toBottomOf="@+id/v_line" /> | |||
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" | |||
@@ -126,6 +133,8 @@ | |||
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" |
@@ -1,47 +1,53 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
android:layout_width="match_parent" | |||
android:layout_height="wrap_content" | |||
<layout xmlns:android="http://schemas.android.com/apk/res/android" | |||
xmlns:app="http://schemas.android.com/apk/res-auto" | |||
xmlns:tools="http://schemas.android.com/tools"> | |||
<TextView | |||
android:id="@+id/tv_word" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:layout_marginTop="16dp" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:text="jaapjaapjaap" | |||
android:textColor="@color/main_text_color" | |||
android:textSize="22dp" | |||
android:textStyle="bold" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintHorizontal_bias="0.0" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toTopOf="parent" /> | |||
<TextView | |||
android:id="@+id/tv_phonetic" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:gravity="center_vertical" | |||
android:textColor="@color/gray_2" | |||
android:textSize="@dimen/smallerSize" | |||
app:layout_constraintEnd_toEndOf="@+id/tv_word" | |||
app:layout_constraintStart_toStartOf="@+id/tv_word" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_word" | |||
tools:text="[gud:bai] " | |||
app:layout_constraintHorizontal_bias="0" | |||
android:layout_marginTop="8dp"/> | |||
<androidx.recyclerview.widget.RecyclerView | |||
android:id="@+id/recyclerView" | |||
<data> | |||
</data> | |||
<androidx.constraintlayout.widget.ConstraintLayout | |||
android:layout_width="match_parent" | |||
android:layout_height="wrap_content" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_phonetic" | |||
/> | |||
android:layout_height="wrap_content"> | |||
<TextView | |||
android:id="@+id/tv_word" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:layout_marginTop="16dp" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:text="jaapjaapjaap" | |||
android:textColor="@color/main_text_color" | |||
android:textSize="22dp" | |||
android:textStyle="bold" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintHorizontal_bias="0.0" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toTopOf="parent" /> | |||
<TextView | |||
android:id="@+id/tv_phonetic" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:gravity="center_vertical" | |||
android:textColor="@color/gray_2" | |||
android:textSize="@dimen/smallerSize" | |||
app:layout_constraintEnd_toEndOf="@+id/tv_word" | |||
app:layout_constraintStart_toStartOf="@+id/tv_word" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_word" | |||
tools:text="[gud:bai] " | |||
app:layout_constraintHorizontal_bias="0" | |||
android:layout_marginTop="8dp" /> | |||
<androidx.recyclerview.widget.RecyclerView | |||
android:id="@+id/recyclerView" | |||
android:layout_width="match_parent" | |||
android:layout_height="wrap_content" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_phonetic" /> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
</layout> |
@@ -1,55 +1,61 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
android:layout_width="match_parent" | |||
android:layout_height="wrap_content" | |||
<layout xmlns:android="http://schemas.android.com/apk/res/android" | |||
xmlns:tools="http://schemas.android.com/tools" | |||
xmlns:app="http://schemas.android.com/apk/res-auto" | |||
android:orientation="horizontal" | |||
android:gravity="center_vertical"> | |||
xmlns:app="http://schemas.android.com/apk/res-auto"> | |||
<TextView | |||
android:id="@+id/tv_word" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:singleLine="true" | |||
tools:text="introduction" | |||
android:textSize="@dimen/normalSize" | |||
android:textColor="@color/main_text_color" | |||
android:layout_marginTop="8dp" | |||
app:layout_constraintTop_toTopOf="parent" | |||
app:layout_constraintStart_toEndOf="@+id/iv_horuns" | |||
android:gravity="center_vertical" | |||
/> | |||
<ImageView | |||
android:id="@+id/iv_horuns" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
tools:src="@drawable/ic_horuns" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:layout_marginEnd="8dp" | |||
app:layout_constraintTop_toTopOf="@+id/tv_word" | |||
app:layout_constraintBottom_toBottomOf="@+id/tv_word" | |||
app:layout_constraintStart_toStartOf="parent" | |||
android:paddingRight="4dp"/> | |||
<TextView | |||
android:id="@+id/tv_value" | |||
android:layout_width="wrap_content" | |||
<data> | |||
</data> | |||
<androidx.constraintlayout.widget.ConstraintLayout | |||
android:layout_width="match_parent" | |||
android:layout_height="wrap_content" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:textSize="@dimen/smallerSize" | |||
android:textColor="@color/gray_2" | |||
tools:text="简介" | |||
app:layout_constrainedWidth="true" | |||
app:layout_constraintHorizontal_bias="0" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_word" | |||
app:layout_constraintBottom_toBottomOf="parent" | |||
android:layout_marginBottom="8dp" | |||
android:layout_marginTop="4dp"/> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
android:orientation="horizontal" | |||
android:gravity="center_vertical"> | |||
<TextView | |||
android:id="@+id/tv_word" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:singleLine="true" | |||
tools:text="introduction" | |||
android:textSize="@dimen/normalSize" | |||
android:textColor="@color/main_text_color" | |||
android:layout_marginTop="8dp" | |||
app:layout_constraintTop_toTopOf="parent" | |||
app:layout_constraintStart_toEndOf="@+id/iv_horuns" | |||
android:gravity="center_vertical" /> | |||
<ImageView | |||
android:id="@+id/iv_horuns" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:src="@drawable/ic_horuns" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:layout_marginEnd="8dp" | |||
app:layout_constraintTop_toTopOf="@+id/tv_word" | |||
app:layout_constraintBottom_toBottomOf="@+id/tv_word" | |||
app:layout_constraintStart_toStartOf="parent" | |||
android:paddingRight="4dp" /> | |||
<TextView | |||
android:id="@+id/tv_value" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:textSize="@dimen/smallerSize" | |||
android:textColor="@color/gray_2" | |||
tools:text="简介" | |||
app:layout_constrainedWidth="true" | |||
app:layout_constraintHorizontal_bias="0" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_word" | |||
app:layout_constraintBottom_toBottomOf="parent" | |||
android:layout_marginBottom="8dp" | |||
android:layout_marginTop="4dp" /> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
</layout> |
@@ -1,94 +1,101 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
android:layout_width="match_parent" | |||
android:layout_height="wrap_content" | |||
<layout xmlns:android="http://schemas.android.com/apk/res/android" | |||
xmlns:app="http://schemas.android.com/apk/res-auto" | |||
xmlns:tools="http://schemas.android.com/tools"> | |||
<TextView | |||
android:id="@+id/tv_word" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:layout_marginTop="16dp" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:text="jaapjaapjaap" | |||
android:textColor="@color/main_text_color" | |||
android:textSize="22dp" | |||
android:textStyle="bold" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintHorizontal_bias="0.0" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toTopOf="parent" /> | |||
<data> | |||
<com.suliang.common.widget.TagLinearLayout | |||
android:id="@+id/layout_phonetic" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
app:item_horizontal_space="16dp" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="@+id/tv_word" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_word" | |||
android:layout_marginTop="8dp" | |||
android:layout_marginEnd="@dimen/global_spacing"> | |||
</data> | |||
<androidx.constraintlayout.widget.ConstraintLayout | |||
android:layout_width="match_parent" | |||
android:layout_height="wrap_content"> | |||
<TextView | |||
android:id="@+id/tv_phonetic_uk" | |||
android:layout_width="wrap_content" | |||
android:id="@+id/tv_word" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:drawableEnd="@drawable/ic_horuns" | |||
android:gravity="center" | |||
android:textColor="@color/gray_2" | |||
android:textSize="@dimen/smallerSize" | |||
tools:text="英 [gud:bai] " /> | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:layout_marginTop="16dp" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:text="jaapjaapjaap" | |||
android:textColor="@color/main_text_color" | |||
android:textSize="22dp" | |||
android:textStyle="bold" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintHorizontal_bias="0.0" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintTop_toTopOf="parent" /> | |||
<TextView | |||
android:id="@+id/tv_phonetic_us" | |||
android:layout_width="wrap_content" | |||
<com.suliang.common.widget.TagLinearLayout | |||
android:id="@+id/layout_phonetic" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:drawableEnd="@drawable/ic_horuns" | |||
android:gravity="center" | |||
android:textColor="@color/gray_2" | |||
android:textSize="@dimen/smallerSize" | |||
tools:text="美 [gud:bai] " /> | |||
app:item_horizontal_space="16dp" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="@+id/tv_word" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_word" | |||
android:layout_marginTop="8dp" | |||
android:layout_marginEnd="@dimen/global_spacing"> | |||
<TextView | |||
android:id="@+id/tv_phonetic_uk" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:drawableEnd="@drawable/ic_horuns" | |||
android:gravity="center" | |||
android:textColor="@color/gray_2" | |||
android:textSize="@dimen/smallerSize" | |||
tools:text="英 [gud:bai] " /> | |||
<TextView | |||
android:id="@+id/tv_phonetic_us" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:drawableEnd="@drawable/ic_horuns" | |||
android:gravity="center" | |||
android:textColor="@color/gray_2" | |||
android:textSize="@dimen/smallerSize" | |||
tools:text="美 [gud:bai] " /> | |||
</com.suliang.common.widget.TagLinearLayout> | |||
</com.suliang.common.widget.TagLinearLayout> | |||
<TextView | |||
android:id="@+id/tv_explain" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:layout_marginTop="10dp" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:textColor="@color/main_text_color" | |||
android:textSize="@dimen/smallSize" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="@+id/tv_word" | |||
app:layout_constraintTop_toBottomOf="@+id/layout_phonetic" | |||
app:layout_constraintHorizontal_bias="0" | |||
android:textStyle="bold" | |||
tools:text="基本释义"/> | |||
<TextView | |||
android:id="@+id/tv_explain" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:layout_marginTop="10dp" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:textColor="@color/main_text_color" | |||
android:textSize="@dimen/smallSize" | |||
app:layout_constraintEnd_toEndOf="parent" | |||
app:layout_constraintStart_toStartOf="@+id/tv_word" | |||
app:layout_constraintTop_toBottomOf="@+id/layout_phonetic" | |||
app:layout_constraintHorizontal_bias="0" | |||
android:textStyle="bold" | |||
tools:text="基本释义" /> | |||
<TextView | |||
android:id="@+id/tv_expand_explain" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:layout_marginTop="2dp" | |||
android:textColor="@color/gray_2" | |||
android:textSize="@dimen/smallSize" | |||
app:layout_constraintEnd_toEndOf="@+id/tv_word" | |||
app:layout_constraintStart_toStartOf="@+id/tv_word" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_explain" | |||
tools:text="扩展释义>"/> | |||
<TextView | |||
android:id="@+id/tv_expand_explain" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
android:layout_marginTop="2dp" | |||
android:textColor="@color/gray_2" | |||
android:textSize="@dimen/smallSize" | |||
app:layout_constraintEnd_toEndOf="@+id/tv_word" | |||
app:layout_constraintStart_toStartOf="@+id/tv_word" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_explain" | |||
tools:text="扩展释义>" /> | |||
<include | |||
android:id="@+id/inc_word_detail" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
layout="@layout/inc_word_detail" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_expand_explain" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintEnd_toEndOf="parent"/> | |||
<include | |||
android:id="@+id/inc_word_detail" | |||
android:layout_width="0dp" | |||
android:layout_height="wrap_content" | |||
layout="@layout/inc_word_detail" | |||
app:layout_constraintTop_toBottomOf="@+id/tv_expand_explain" | |||
app:layout_constraintStart_toStartOf="parent" | |||
app:layout_constraintEnd_toEndOf="parent" /> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
</androidx.constraintlayout.widget.ConstraintLayout> | |||
</layout> |
@@ -1,40 +1,46 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
android:layout_width="match_parent" | |||
android:layout_height="32dp" | |||
xmlns:tools="http://schemas.android.com/tools" | |||
android:orientation="horizontal" | |||
android:gravity="center_vertical"> | |||
<ImageView | |||
android:id="@+id/iv" | |||
android:layout_width="16dp" | |||
android:layout_height="16dp" | |||
tools:src="@drawable/ic_clock" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:layout_marginEnd="8dp"/> | |||
<TextView | |||
android:id="@+id/tv_word" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:singleLine="true" | |||
tools:text="jaap" | |||
android:textSize="@dimen/smallSize" | |||
android:textColor="@color/main_text_color" | |||
/> | |||
<TextView | |||
android:id="@+id/tv_value" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:singleLine="true" | |||
android:ellipsize="end" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:textSize="@dimen/smallSize" | |||
android:textColor="@color/main_text_color" | |||
tools:text="治疗"/> | |||
</LinearLayout> | |||
<layout xmlns:android="http://schemas.android.com/apk/res/android" | |||
xmlns:tools="http://schemas.android.com/tools"> | |||
<data> | |||
</data> | |||
<LinearLayout | |||
android:layout_width="match_parent" | |||
android:layout_height="32dp" | |||
android:orientation="horizontal" | |||
android:gravity="center_vertical"> | |||
<ImageView | |||
android:id="@+id/iv" | |||
android:layout_width="16dp" | |||
android:layout_height="16dp" | |||
tools:src="@drawable/ic_clock" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:layout_marginEnd="8dp" /> | |||
<TextView | |||
android:id="@+id/tv_word" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:singleLine="true" | |||
tools:text="jaap" | |||
android:textSize="@dimen/smallSize" | |||
android:textColor="@color/main_text_color" /> | |||
<TextView | |||
android:id="@+id/tv_value" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:singleLine="true" | |||
android:ellipsize="end" | |||
android:layout_marginEnd="@dimen/global_spacing" | |||
android:layout_marginStart="@dimen/global_spacing" | |||
android:textSize="@dimen/smallSize" | |||
android:textColor="@color/main_text_color" | |||
tools:text="治疗" /> | |||
</LinearLayout> | |||
</layout> |
@@ -43,7 +43,8 @@ object LogUtil { | |||
if (!LogEnable) return | |||
val stackTraceElement = Thread.currentThread().stackTrace[5] | |||
val className = stackTraceElement.className.substringAfterLast(".") | |||
val out = "==> \n${className}_${stackTraceElement.methodName.substringAfterLast(".")}_${stackTraceElement.lineNumber} \n$message" | |||
// val out = "==> \n${className}_${stackTraceElement.methodName.substringAfterLast(".")}_${stackTraceElement.lineNumber} \n$message" | |||
val out = "==> $className \n$message" | |||
when(logType){ | |||
"v" -> Log.v(className,out) | |||
"d" -> Log.d(className,out) |
@@ -1,8 +1,8 @@ | |||
package com.suliang.common.util | |||
import android.text.Html | |||
import android.text.Spanned | |||
import androidx.annotation.ColorInt | |||
import android.text.Spannable | |||
import android.text.SpannableStringBuilder | |||
import android.text.style.ForegroundColorSpan | |||
import java.util.regex.Pattern | |||
/** | |||
@@ -14,9 +14,25 @@ class StringUtil { | |||
companion object { | |||
/** | |||
* 匹配指定字符串,并对匹配的字符串每个字符都进行匹配,进行高亮 | |||
* @param word String | |||
* @param needHighStr String | |||
* @param color Int | |||
*/ | |||
fun highString(word : String, needHighStr : String?, color : Int) : SpannableStringBuilder { | |||
val result = SpannableStringBuilder(word) | |||
if (!needHighStr.isNullOrEmpty()){ | |||
val compile = Pattern.compile("[${needHighStr.lowercase()}]") // 匹配needHighStr中的所有匹配字符 | |||
val matcher = compile.matcher(word.lowercase()) | |||
while (matcher.find()) { | |||
val start = matcher.start() //开始位置 | |||
val end = matcher.end() //结束位置 | |||
result.setSpan(ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) | |||
} | |||
} | |||
return result | |||
} | |||
} | |||
} |
@@ -4,7 +4,6 @@ import android.os.Environment | |||
import com.suliang.common.util.AppGlobals | |||
import com.suliang.common.util.LogUtil | |||
import java.io.* | |||
import java.lang.Exception | |||
import java.text.DecimalFormat | |||
import java.util.zip.GZIPInputStream | |||
import java.util.zip.GZIPOutputStream | |||
@@ -446,6 +445,16 @@ object FileUtil { | |||
} | |||
return result | |||
} | |||
fun getAssetFileSize(assetName:String):Long{ | |||
try { | |||
val open = AppGlobals.application.assets.open(assetName) | |||
return open.available().toLong() | |||
}catch (e:Exception){ | |||
e.printStackTrace() | |||
} | |||
return 0 | |||
} | |||
/* private fun getFileSize(file: File) : Int{ | |||
if (file.exists()){ |