| <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_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_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.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_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_chinese.xml" value="0.23632218844984804" /> | ||||
| <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_history_english.xml" value="0.23632218844984804" /> | <entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/item_dic_history_english.xml" value="0.23632218844984804" /> |
| versionName androidConfig.version_name | versionName androidConfig.version_name | ||||
| multiDexEnabled true //解决64k 分包限制 | multiDexEnabled true //解决64k 分包限制 | ||||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | 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") | |||||
| } | } | ||||
| } | } | ||||
| package com.xkl.cdl.adapter | package com.xkl.cdl.adapter | ||||
| import android.view.View | |||||
| import android.view.ViewGroup | 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.BaseAdapterViewHolder | ||||
| import com.suliang.common.base.adapter.BaseRVAdapterVM | import com.suliang.common.base.adapter.BaseRVAdapterVM | ||||
| import com.suliang.common.databinding.ItemEmptyBinding | 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.R | ||||
| import com.xkl.cdl.data.AppConstants | |||||
| import com.xkl.cdl.data.bean.DictionaryItem | 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 | import com.xkl.cdl.module.m_service_center.DictionaryViewModel | ||||
| /** | /** | ||||
| * create 2022/8/4 10:41 | * create 2022/8/4 10:41 | ||||
| * Describe: | * 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 { | 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) { | override fun onBindVH(holder : BaseAdapterViewHolder, position : Int) { | ||||
| val itemViewType = getItemViewType(position) | val itemViewType = getItemViewType(position) | ||||
| when(itemViewType){ | |||||
| when (itemViewType) { | |||||
| //英文 | |||||
| 1 -> { | 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 -> { | 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 -> { | 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) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } |
| */ | */ | ||||
| open class DictionaryBean() { | open class DictionaryBean() { | ||||
| var id : Long = 0 | var id : Long = 0 | ||||
| var word : String? = null | |||||
| var word : String = "" | |||||
| var form : Int = 0 // 1 英文 2中文 | var form : Int = 0 // 1 英文 2中文 | ||||
| var pre_index : String? = null //前缀 | var pre_index : String? = null //前缀 | ||||
| var phonetic_uk : String? = null | var phonetic_uk : String? = null |
| //查询时间 | //查询时间 | ||||
| var queryTime: Long = 0 | var queryTime: Long = 0 | ||||
| //词典联想结果判断其是否在历史记录中,如果在历史记录中,则进行时间重排序 | |||||
| //词典联想结果判断其是否在历史记录中,如果在历史记录中,则进行时间重排序, | |||||
| //历史记录表中查询,默认是true | |||||
| //词典查询时,需要设置为false | |||||
| @Ignore | @Ignore | ||||
| var inHistory : Boolean = false | |||||
| var inHistory : Boolean = true | |||||
| } | } |
| import androidx.room.Database | import androidx.room.Database | ||||
| import androidx.room.Room | import androidx.room.Room | ||||
| import androidx.room.RoomDatabase | import androidx.room.RoomDatabase | ||||
| import androidx.room.migration.Migration | |||||
| import androidx.sqlite.db.SupportSQLiteDatabase | |||||
| import com.suliang.common.util.file.FileUtil | 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 com.xkl.cdl.module.XKLApplication | ||||
| import mqComsumerV1.Struct | import mqComsumerV1.Struct | ||||
| import java.io.File | import java.io.File | ||||
| * create 2022/7/7 16:50 | * create 2022/7/7 16:50 | ||||
| * Describe: | * 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 class AppDatabase : RoomDatabase() { | ||||
| abstract fun examDao() : ExamDao | abstract fun examDao() : ExamDao | ||||
| abstract fun examItemDao(): ExamItemDao | abstract fun examItemDao(): ExamItemDao | ||||
| companion object{ | companion object{ | ||||
| private var DATABASE_NAME :String = "${FileUtil.getSaveDirPath("db")}${File.separator}app.db" | 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) { | 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 | @JvmStatic |
| package com.xkl.cdl.data.manager | package com.xkl.cdl.data.manager | ||||
| import com.suliang.common.AppConfig | |||||
| import com.suliang.common.util.file.FileUtil | import com.suliang.common.util.file.FileUtil | ||||
| import com.xkl.cdl.data.manager.db.DbControlBase | import com.xkl.cdl.data.manager.db.DbControlBase | ||||
| import java.io.File | import java.io.File | ||||
| fun getMp4File(subjectId : Int,coursePackId : Long,courseId : Long, fileName:String):File{ | fun getMp4File(subjectId : Int,coursePackId : Long,courseId : Long, fileName:String):File{ | ||||
| return File(getMp4RootPath(), "$subjectId/$coursePackId/$courseId/$fileName") | 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") | |||||
| } | |||||
| } | } | ||||
| } | } |
| package com.xkl.cdl.data.manager.db | package com.xkl.cdl.data.manager.db | ||||
| import android.widget.Toast | 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.AppGlobals | ||||
| import com.suliang.common.util.LogUtil | import com.suliang.common.util.LogUtil | ||||
| import com.suliang.common.util.file.FileUtil | |||||
| import com.suliang.common.util.thread.AppExecutors | 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.bean.DictionaryItem | ||||
| import com.xkl.cdl.data.manager.FilePathManager | import com.xkl.cdl.data.manager.FilePathManager | ||||
| import net.sqlcipher.database.SQLiteDatabase | import net.sqlcipher.database.SQLiteDatabase | ||||
| import java.io.File | |||||
| import java.lang.StringBuilder | import java.lang.StringBuilder | ||||
| import java.util.* | import java.util.* | ||||
| private var mDataBase : SQLiteDatabase? = null | private var mDataBase : SQLiteDatabase? = null | ||||
| private var pwd : String = "XKL_XECD_DICT_DATA_KEY" | 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 const val MAX_COUNT = 50 | ||||
| private fun open() { | private fun open() { | ||||
| val titleMap : MutableMap<String, Int> = mutableMapOf() | val titleMap : MutableMap<String, Int> = mutableMapOf() | ||||
| var sql = "" | var sql = "" | ||||
| //1: wor | //1: wor | ||||
| sql = "SELECT word FROM $DATA_DICT WHERE word = $keyWord" | |||||
| sql = "SELECT word FROM $DATA_DICT WHERE word = '$keyWord'" | |||||
| query(sql, titleMap) | query(sql, titleMap) | ||||
| //2: wor% %wor% | //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) | query(sql, titleMap) | ||||
| if (titleMap.size < MAX_COUNT) { | 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) | query(sql, titleMap) | ||||
| if (titleMap.size < MAX_COUNT) { | if (titleMap.size < MAX_COUNT) { | ||||
| //3:% w % o % r % | //3:% w % o % r % | ||||
| var newKeyWord : String = insertPercent(keyWord) | var newKeyWord : String = insertPercent(keyWord) | ||||
| val length = keyWord.length | |||||
| val length = newKeyWord.length | |||||
| for (i in 0 until length - 1) { | for (i in 0 until length - 1) { | ||||
| val max : Int = MAX_COUNT - titleMap.size | val max : Int = MAX_COUNT - titleMap.size | ||||
| newKeyWord = substringEnd(newKeyWord) | newKeyWord = substringEnd(newKeyWord) | ||||
| * @param hashMap | * @param hashMap | ||||
| */ | */ | ||||
| private fun query(sql : String, hashMap : MutableMap<String, Int>) { | private fun query(sql : String, hashMap : MutableMap<String, Int>) { | ||||
| LogUtil.e(sql) | |||||
| mDataBase?.rawQuery(sql, null)?.let { | mDataBase?.rawQuery(sql, null)?.let { | ||||
| while (it.moveToNext()) { | while (it.moveToNext()) { | ||||
| if (hashMap.size == MAX_COUNT) break //数量15则打断 | if (hashMap.size == MAX_COUNT) break //数量15则打断 | ||||
| example = it.getString(13) | example = it.getString(13) | ||||
| reference = it.getString(14) | reference = it.getString(14) | ||||
| // setVersion(it.getInt(15)) //version版本 | // setVersion(it.getInt(15)) //version版本 | ||||
| inHistory = false | |||||
| } | } | ||||
| } | } | ||||
| it.close() | it.close() | ||||
| * @param courseId 课程查询的courseId | * @param courseId 课程查询的courseId | ||||
| * @return | * @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 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() | open() | ||||
| mDataBase?.rawQuery(sql, null)?.let { | mDataBase?.rawQuery(sql, null)?.let { | ||||
| while (it.moveToNext()) { | while (it.moveToNext()) { | ||||
| phrase = it.getString(12) | phrase = it.getString(12) | ||||
| example = it.getString(13) | example = it.getString(13) | ||||
| reference = it.getString(14) | reference = it.getString(14) | ||||
| inHistory = false | |||||
| } | } | ||||
| list.add(mDictionaryItem) | list.add(mDictionaryItem) | ||||
| } | } | ||||
| //去重 排序 | //去重 排序 | ||||
| return list.distinctBy { it.word }.sortedBy { | return list.distinctBy { it.word }.sortedBy { | ||||
| words.indexOf(it.word) | 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 | |||||
| } | } | ||||
| } | } |
| it.close() | it.close() | ||||
| } | } | ||||
| //不为空,写入本身,如果为空,用另外的发音方式写入 | //不为空,写入本身,如果为空,用另外的发音方式写入 | ||||
| val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | |||||
| val parentPath = FilePathManager.getVocVoiceParent() | |||||
| val audio_us_file_path = audio_us?.let { | val audio_us_file_path = audio_us?.let { | ||||
| val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_US}" | val audioFileNameUS = "${wordId}_${AppConstants.SOUND_TYPE_US}" | ||||
| val file = File(parentPath, audioFileNameUS) | val file = File(parentPath, audioFileNameUS) |
| package com.xkl.cdl.data.repository | package com.xkl.cdl.data.repository | ||||
| import android.net.IpPrefix | |||||
| import android.util.LruCache | import android.util.LruCache | ||||
| import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||
| import com.suliang.common.AppConfig | import com.suliang.common.AppConfig | ||||
| import com.suliang.common.extension.diskIo2Main | import com.suliang.common.extension.diskIo2Main | ||||
| import com.suliang.common.util.file.FileUtil | import com.suliang.common.util.file.FileUtil | ||||
| import com.xkl.cdl.data.AppConstants | 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.DBCourseManager | ||||
| import com.xkl.cdl.data.manager.db.DbControlBase | 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 com.xkl.cdl.data.manager.db.VocabularyManager | ||||
| import io.reactivex.rxjava3.core.Observable | import io.reactivex.rxjava3.core.Observable | ||||
| import java.io.File | import java.io.File | ||||
| * 1缓存 2文件 3数据库 | * 1缓存 2文件 3数据库 | ||||
| * @return String 文件路径 | * @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 { | 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 { | }.compose(diskIo2Main()).subscribe { | ||||
| if (this::audioLiveData.isInitialized) | if (this::audioLiveData.isInitialized) | ||||
| audioLiveData.value = it | |||||
| audioLiveData.value = it | |||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * 口语总测获取发音文件 | * 口语总测获取发音文件 | ||||
| * @param fileName String 发音文件名称 | * @param fileName String 发音文件名称 | ||||
| * @return String 发音文件路径 | * @return String 发音文件路径 | ||||
| */ | */ | ||||
| fun get(fileName : String):String { | |||||
| fun getSpokenVoice(fileName : String):String { | |||||
| val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | val parentPath = FileUtil.getSaveDirPath(AppConfig.VOICE) | ||||
| if (lruCache[fileName] == null){ | if (lruCache[fileName] == null){ | ||||
| lruCache.put(fileName,File(parentPath,fileName).path) | lruCache.put(fileName,File(parentPath,fileName).path) | ||||
| } | } | ||||
| return lruCache[fileName] | 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 | |||||
| }*/ | |||||
| } | |||||
| } | } |
| if (view is LottieAnimationView && view.visibility == View.VISIBLE) currentPlayView = view | if (view is LottieAnimationView && view.visibility == View.VISIBLE) currentPlayView = view | ||||
| when{ | 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) | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||
| import android.graphics.Color | import android.graphics.Color | ||||
| import android.opengl.Visibility | |||||
| import android.os.Bundle | import android.os.Bundle | ||||
| import android.text.SpannableStringBuilder | import android.text.SpannableStringBuilder | ||||
| import android.view.MotionEvent | import android.view.MotionEvent | ||||
| import android.view.View | import android.view.View | ||||
| import android.view.ViewGroup | |||||
| import android.widget.TextView | import android.widget.TextView | ||||
| import androidx.constraintlayout.widget.ConstraintLayout | import androidx.constraintlayout.widget.ConstraintLayout | ||||
| import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||
| import androidx.fragment.app.FragmentManager | |||||
| import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
| import androidx.recyclerview.widget.GridLayoutManager | import androidx.recyclerview.widget.GridLayoutManager | ||||
| import androidx.recyclerview.widget.LinearLayoutManager | import androidx.recyclerview.widget.LinearLayoutManager | ||||
| when (vm.learnData.lesson.coursePackType) { | 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 -> { | 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!! | val valueWord = vm.currentLearnWord.value!! | ||||
| AudioCache.get(vm.dbControlBase, valueWord.wordId, vm.defaultSoundWay) | |||||
| AudioCache.getLearnVoice(vm.dbControlBase, valueWord.wordId, vm.defaultSoundWay) | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| package com.xkl.cdl.module.m_memo | package com.xkl.cdl.module.m_memo | ||||
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||
| import android.app.TimePickerDialog | |||||
| import android.os.Bundle | import android.os.Bundle | ||||
| import android.view.View | import android.view.View | ||||
| import android.widget.TimePicker | |||||
| import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
| import androidx.recyclerview.widget.LinearLayoutManager | import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import com.haibin.calendarview.Calendar | import com.haibin.calendarview.Calendar | ||||
| import com.xkl.cdl.data.repository.AudioCache | import com.xkl.cdl.data.repository.AudioCache | ||||
| import com.xkl.cdl.databinding.ActivityMemoListDetailBinding | import com.xkl.cdl.databinding.ActivityMemoListDetailBinding | ||||
| import com.xkl.cdl.module.learn.LearnWordActivity | import com.xkl.cdl.module.learn.LearnWordActivity | ||||
| import com.xkl.cdl.module.m_center_learn.coursechildren.CourseMainFragment | |||||
| import java.util.* | import java.util.* | ||||
| import java.util.Calendar.* | import java.util.Calendar.* | ||||
| needShowEmptyView = true | needShowEmptyView = true | ||||
| //处理发音事件 | //处理发音事件 | ||||
| soundListener = { wordId, soundWay -> | soundListener = { wordId, soundWay -> | ||||
| AudioCache.get(vm.dbControlBase, wordId, soundWay) | |||||
| AudioCache.getLearnVoice(vm.dbControlBase, wordId, soundWay) | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| import android.content.Intent | import android.content.Intent | ||||
| import android.os.Bundle | import android.os.Bundle | ||||
| import android.view.View | import android.view.View | ||||
| import androidx.core.widget.addTextChangedListener | |||||
| import androidx.lifecycle.ViewModelProvider | import androidx.lifecycle.ViewModelProvider | ||||
| import androidx.recyclerview.widget.LinearLayoutManager | import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import com.suliang.common.AppConfig | import com.suliang.common.AppConfig | ||||
| import com.suliang.common.base.activity.BaseActivityVM | import com.suliang.common.base.activity.BaseActivityVM | ||||
| import com.suliang.common.extension.click | 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.R | ||||
| import com.xkl.cdl.data.repository.AudioCache | |||||
| import com.xkl.cdl.databinding.ActivityDictionaryBinding | import com.xkl.cdl.databinding.ActivityDictionaryBinding | ||||
| import com.xkl.cdl.dialog.CommonDialog | import com.xkl.cdl.dialog.CommonDialog | ||||
| import com.xkl.cdl.dialog.CommonDialogBean | 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>() { | class DictionaryActivity : BaseActivityVM<ActivityDictionaryBinding, DictionaryViewModel>() { | ||||
| companion object { | companion object { | ||||
| override fun initActivity(savedInstanceState : Bundle?) { | override fun initActivity(savedInstanceState : Bundle?) { | ||||
| vm.courseId = intent.getLongExtra(AppConfig.INTENT_1, 0) | vm.courseId = intent.getLongExtra(AppConfig.INTENT_1, 0) | ||||
| binding.titleBar.onBackClick = {finish()} | |||||
| initHistoryRecyclerView() | initHistoryRecyclerView() | ||||
| //点击 查看更多 | //点击 查看更多 | ||||
| binding.tvSeeMore.click { | binding.tvSeeMore.click { | ||||
| when (vm.detailState) { | when (vm.detailState) { | ||||
| STATE_HISTROY -> { | |||||
| DictionaryViewModel.STATE_HISTROY -> { | |||||
| //显示页面加1, 列表更新 | //显示页面加1, 列表更新 | ||||
| vm.histroyShowPage++ | vm.histroyShowPage++ | ||||
| vm.adapter.notifyDataSetChanged() | 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, 列表更新 | //显示页面加1, 列表更新 | ||||
| vm.searchShowPage++ | vm.searchShowPage++ | ||||
| vm.adapter.notifyDataSetChanged() | 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) | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| }.show(supportFragmentManager, javaClass.name) | }.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 { | 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() | |||||
| } | |||||
| } | } |
| import android.os.Looper | import android.os.Looper | ||||
| import androidx.lifecycle.MutableLiveData | import androidx.lifecycle.MutableLiveData | ||||
| import com.suliang.common.base.viewmodel.BaseViewModel | import com.suliang.common.base.viewmodel.BaseViewModel | ||||
| import com.suliang.common.extension.io2Main | |||||
| import com.suliang.common.util.thread.AppExecutors | import com.suliang.common.util.thread.AppExecutors | ||||
| import com.xkl.cdl.adapter.DictionaryAdapter | import com.xkl.cdl.adapter.DictionaryAdapter | ||||
| import com.xkl.cdl.data.bean.DictionaryItem | import com.xkl.cdl.data.bean.DictionaryItem | ||||
| //记录状态 | //记录状态 | ||||
| companion object { | 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) | val adapter = DictionaryAdapter(this) | ||||
| //输入框监听 | |||||
| //手动改变editText Value | |||||
| val etSearchLiveData = MutableLiveData<String>() | val etSearchLiveData = MutableLiveData<String>() | ||||
| //搜索关键子 | |||||
| var keyWord : String? = null | |||||
| //状态 : 默认历史记录 | |||||
| var detailState = STATE_HISTROY | |||||
| //历史记录集合 | //历史记录集合 | ||||
| val historyList = mutableListOf<DictionaryItem>() | 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 histroyShowPage = 1 | ||||
| var searchShowPage = 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 handler : Handler = Handler(Looper.getMainLooper()) | ||||
| //记录联想结果中为历史记录的位置 | //记录联想结果中为历史记录的位置 | ||||
| val searchListForHistoryPositionMap = mutableMapOf<Int,Int>() | |||||
| private val searchListForHistoryPositionMap = mutableMapOf<Int, Int>() | |||||
| /** | /** | ||||
| Observable.fromCallable { | Observable.fromCallable { | ||||
| val history = AppDatabase.instance.dictionaryDao().query(courseId) | val history = AppDatabase.instance.dictionaryDao().query(courseId) | ||||
| historyList.addAll(history) | 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() { | fun clearHistory() { | ||||
| Observable.fromCallable { | Observable.fromCallable { | ||||
| historyList.clear() | historyList.clear() | ||||
| updateHistoryAdapterLiveData.postValue(false) | |||||
| if (detailState == STATE_HISTROY) | |||||
| updateHistoryAdapterLiveData.postValue(detailState) | |||||
| AppDatabase.instance.dictionaryDao().clear(courseId) | AppDatabase.instance.dictionaryDao().clear(courseId) | ||||
| }.observeOn(Schedulers.from(AppExecutors.io)).subscribe() | |||||
| }.subscribeOn(Schedulers.from(AppExecutors.io)).subscribe() | |||||
| } | } | ||||
| /** | /** | ||||
| * @param keyWord String | * @param keyWord String | ||||
| */ | */ | ||||
| fun searchKeyWord(keyWord : String) { | fun searchKeyWord(keyWord : String) { | ||||
| handler.removeCallbacksAndMessages(null) | |||||
| this.keyWord = keyWord | |||||
| handler.postDelayed({ | handler.postDelayed({ | ||||
| Observable.fromCallable { | Observable.fromCallable { | ||||
| //清空位置对应信息 | //清空位置对应信息 | ||||
| searchListForHistoryPositionMap.clear() | searchListForHistoryPositionMap.clear() | ||||
| //联想查询结果 | //联想查询结果 | ||||
| searchDetailList = DictionaryManager.queryKeyWordLikeMatchSimple(keyWord) | |||||
| searchAssociateList = DictionaryManager.queryKeyWordLikeMatchSimple(keyWord) | |||||
| //搜索记录中获取历史记录位置 需要判断是否有历史记录 | //搜索记录中获取历史记录位置 需要判断是否有历史记录 | ||||
| if (historyList.isNotEmpty() && searchDetailList.isNotEmpty()) { | |||||
| if (historyList.isNotEmpty() && searchAssociateList.isNotEmpty()) { | |||||
| historyList.forEachIndexed { historyIndex, historyItem -> | historyList.forEachIndexed { historyIndex, historyItem -> | ||||
| searchDetailList.forEachIndexed { searchIndex, searchItem -> | |||||
| searchAssociateList.forEachIndexed { searchIndex, searchItem -> | |||||
| if (historyItem.word == searchItem.word) { | if (historyItem.word == searchItem.word) { | ||||
| searchItem.inHistory = true | searchItem.inHistory = true | ||||
| searchListForHistoryPositionMap.put(searchIndex, historyIndex) | searchListForHistoryPositionMap.put(searchIndex, historyIndex) | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| //更新搜索结果 | |||||
| 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() | |||||
| } | |||||
| } | } |
| //复制词典 和 词汇量测试 | //复制词典 和 词汇量测试 | ||||
| val dictionary = FilePathManager.getDictionaryDbPath() | 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() | 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) | FileUtil.copyAsset("vocabulary.db", vocabulary) | ||||
| } | } | ||||
| package com.xkl.cdl.util | 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 android.widget.TextView | ||||
| import androidx.annotation.ColorInt | |||||
| import androidx.annotation.ColorRes | import androidx.annotation.ColorRes | ||||
| import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||
| import com.suliang.common.util.AppGlobals | import com.suliang.common.util.AppGlobals | ||||
| import com.suliang.common.util.ColorUtil | import com.suliang.common.util.ColorUtil | ||||
| import com.suliang.common.util.os.ScreenUtil | import com.suliang.common.util.os.ScreenUtil | ||||
| import java.util.* | |||||
| import java.util.regex.Pattern | import java.util.regex.Pattern | ||||
| /** | /** | ||||
| m.appendTail(buffer) | m.appendTail(buffer) | ||||
| return buffer.toString() | return buffer.toString() | ||||
| } | } | ||||
| } | } | ||||
| } | } |
| xmlns:tools="http://schemas.android.com/tools"> | xmlns:tools="http://schemas.android.com/tools"> | ||||
| <data> | <data> | ||||
| <variable | |||||
| name="vm" | |||||
| type="com.xkl.cdl.module.m_service_center.DictionaryViewModel" /> | |||||
| </data> | </data> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout | <androidx.constraintlayout.widget.ConstraintLayout | ||||
| android:layout_marginStart="@dimen/global_spacing" | android:layout_marginStart="@dimen/global_spacing" | ||||
| android:layout_marginEnd="@dimen/global_spacing" | android:layout_marginEnd="@dimen/global_spacing" | ||||
| android:background="@drawable/shape_rounder_8_stroke_gray1" | android:background="@drawable/shape_rounder_8_stroke_gray1" | ||||
| android:imeOptions="actionDone" | |||||
| android:paddingStart="@dimen/global_spacing" | android:paddingStart="@dimen/global_spacing" | ||||
| android:paddingEnd="@dimen/global_spacing" | android:paddingEnd="@dimen/global_spacing" | ||||
| android:drawablePadding="4dp" | android:drawablePadding="4dp" | ||||
| android:textSize="@dimen/smallSize" | android:textSize="@dimen/smallSize" | ||||
| android:gravity="center_vertical" | android:gravity="center_vertical" | ||||
| android:hint="输入你要搜索的内容…" | |||||
| android:text="@{vm.etSearchLiveData}"/> | |||||
| android:singleLine="true" | |||||
| android:hint="输入你要搜索的内容…"/> | |||||
| <androidx.constraintlayout.widget.ConstraintLayout | <androidx.constraintlayout.widget.ConstraintLayout | ||||
| android:id="@+id/layout_detail" | android:id="@+id/layout_detail" | ||||
| android:layout_width="0dp" | android:layout_width="0dp" | ||||
| android:layout_height="wrap_content" | |||||
| android:layout_height="0dp" | |||||
| app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
| app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
| app:layout_constraintTop_toBottomOf="@+id/et_search" | app:layout_constraintTop_toBottomOf="@+id/et_search" | ||||
| app:layout_constraintBottom_toBottomOf="parent" | |||||
| app:layout_constraintVertical_bias="0" | |||||
| android:layout_marginTop="12dp" | android:layout_marginTop="12dp" | ||||
| android:layout_marginBottom="34dp" | |||||
| android:background="@drawable/shape_rounder_8_solid_green1" | android:background="@drawable/shape_rounder_8_solid_green1" | ||||
| android:backgroundTint="@color/gray_3" | android:backgroundTint="@color/gray_3" | ||||
| android:layout_marginStart="@dimen/global_spacing" | android:layout_marginStart="@dimen/global_spacing" | ||||
| android:layout_marginEnd="@dimen/global_spacing"> | |||||
| android:layout_marginEnd="@dimen/global_spacing" | |||||
| app:layout_constrainedHeight="true"> | |||||
| <View | <View | ||||
| android:id="@+id/v_line" | android:id="@+id/v_line" | ||||
| android:id="@+id/rv" | android:id="@+id/rv" | ||||
| android:layout_width="match_parent" | android:layout_width="match_parent" | ||||
| android:layout_height="wrap_content" | 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 | <TextView | ||||
| android:id="@+id/tv_see_more" | android:id="@+id/tv_see_more" | ||||
| app:layout_constraintTop_toBottomOf="@+id/rv" | app:layout_constraintTop_toBottomOf="@+id/rv" | ||||
| app:layout_constraintStart_toStartOf="parent" | app:layout_constraintStart_toStartOf="parent" | ||||
| app:layout_constraintEnd_toEndOf="parent" | app:layout_constraintEnd_toEndOf="parent" | ||||
| app:layout_constraintBottom_toBottomOf="parent" | |||||
| app:layout_constraintVertical_bias="0" | |||||
| android:text="查看更多" | android:text="查看更多" | ||||
| android:drawableEnd="@drawable/ic_down" | android:drawableEnd="@drawable/ic_down" | ||||
| android:drawableTint="@color/gray_2" | android:drawableTint="@color/gray_2" |
| <?xml version="1.0" encoding="utf-8"?> | <?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:app="http://schemas.android.com/apk/res-auto" | ||||
| xmlns:tools="http://schemas.android.com/tools"> | 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_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> |
| <?xml version="1.0" encoding="utf-8"?> | <?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: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_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> |
| <?xml version="1.0" encoding="utf-8"?> | <?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:app="http://schemas.android.com/apk/res-auto" | ||||
| xmlns:tools="http://schemas.android.com/tools"> | 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 | <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: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: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> |
| <?xml version="1.0" encoding="utf-8"?> | <?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> |
| if (!LogEnable) return | if (!LogEnable) return | ||||
| val stackTraceElement = Thread.currentThread().stackTrace[5] | val stackTraceElement = Thread.currentThread().stackTrace[5] | ||||
| val className = stackTraceElement.className.substringAfterLast(".") | 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){ | when(logType){ | ||||
| "v" -> Log.v(className,out) | "v" -> Log.v(className,out) | ||||
| "d" -> Log.d(className,out) | "d" -> Log.d(className,out) |
| package com.suliang.common.util | 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 | import java.util.regex.Pattern | ||||
| /** | /** | ||||
| companion object { | 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 | |||||
| } | |||||
| } | } | ||||
| } | } |
| import com.suliang.common.util.AppGlobals | import com.suliang.common.util.AppGlobals | ||||
| import com.suliang.common.util.LogUtil | import com.suliang.common.util.LogUtil | ||||
| import java.io.* | import java.io.* | ||||
| import java.lang.Exception | |||||
| import java.text.DecimalFormat | import java.text.DecimalFormat | ||||
| import java.util.zip.GZIPInputStream | import java.util.zip.GZIPInputStream | ||||
| import java.util.zip.GZIPOutputStream | import java.util.zip.GZIPOutputStream | ||||
| } | } | ||||
| return result | 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{ | /* private fun getFileSize(file: File) : Int{ | ||||
| if (file.exists()){ | if (file.exists()){ |