1
0
mirror of https://github.com/fumiama/simple-dict-android.git synced 2026-06-05 08:40:25 +08:00
This commit is contained in:
fumiama
2021-02-17 21:29:31 +08:00
parent cacf34544f
commit 85ee5eb574
13 changed files with 250 additions and 126 deletions

View File

@@ -11,7 +11,7 @@ android {
defaultConfig {
applicationId "top.fumiama.simpledict"
minSdkVersion 23
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"
@@ -62,6 +62,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
@@ -93,7 +94,8 @@ andResGuard {
"R.string.ga_trackingId",
"R.string.firebase_database_url",
"R.string.google_api_key",
"R.string.google_crash_reporting_api_key"
"R.string.google_crash_reporting_api_key",
"R.font.*"
]
compressFilePattern = [
"*.png",

View File

@@ -5,7 +5,6 @@ import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.net.Socket
import java.nio.charset.Charset
class Client(val ip: String, val port: Int) {
//普通数据交互接口
@@ -58,24 +57,27 @@ class Client(val ip: String, val port: Int) {
}
}
fun receiveMessage(): String? {
var message: String? = ""
try {
fun receiveRawMessage()= try {
if (isConnect) {
Log.d("MyC", "开始接收服务端信息")
val inMessage = ByteArray(1024) //设置接受缓冲,避免接受数据过长占用过多内存
val a = din?.read(inMessage) //a存储返回消息的长度
if (a == null || a <= -1) return null
Log.d("MyC", "reply length:$a")
message = inMessage.copyOf(a).decodeToString()
Log.d("MyC", message)
} else Log.d("MyC", "no connect to receive message")
if (a == null || a <= -1) null
else {
Log.d("MyC", "reply length:$a")
inMessage.copyOf(a)
}
} else {
Log.d("MyC", "no connect to receive message")
null
}
} catch (e: IOException) {
Log.d("MyC", "receive message failed")
e.printStackTrace()
null
}
return message
}
fun receiveMessage() = receiveRawMessage()?.decodeToString()
/**
* 关闭连接

View File

@@ -1,91 +1,79 @@
package top.fumiama.simpledict
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.content.Intent
import android.os.Bundle
import android.speech.RecognizerIntent
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.lapism.search.internal.SearchLayout
import com.lapism.search.util.SearchUtils
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.line_word.view.*
import java.lang.Thread.sleep
class MainActivity : AppCompatActivity() {
private var keys = arrayOf<String>()
private var datas = arrayOf<String?>()
private val dict = SimpleDict(Client("192.168.98.2", 8000))
private val dict = SimpleDict(Client("pan.fumiama.top", 43792), "fumiama")
private var hasLiked = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val ad = LikeViewHolder(ffr).RecyclerViewAdapter()
ffr.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = ad
setOnScrollChangeListener { _, _, scrollY, _, _ ->
ffsw.isEnabled = scrollY == 0
}
ffsw.apply {
setOnRefreshListener {
ad.refresh()
isRefreshing = false
}
}
}
ffms.apply {
setAdapterLayoutManager(LinearLayoutManager(this@MainActivity))
val adapter = ViewData(findViewById(R.id.search_recycler_view)).RecyclerViewAdapter()
val adapter = SearchViewHolder(findViewById(R.id.search_recycler_view)).RecyclerViewAdapter()
setAdapter(adapter)
navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH
setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener {
override fun onNavigationClick(hasFocus: Boolean) {
if (hasFocus()) clearFocus()
if (hasFocus()) {
if(hasLiked) ad.refresh()
clearFocus()
}
else requestFocus()
}
})
setTextHint(android.R.string.search_go)
setOnQueryTextListener(object : SearchLayout.OnQueryTextListener {
val sysTime get() = System.currentTimeMillis() / 1000
var lastVisitTime = sysTime
val isLast get() = sysTime - lastVisitTime > 1
var hasLoad = true
var key: CharSequence = ""
set(value) {
field = value
lastVisitTime = sysTime
hasLoad = false
}
init {
Thread {
while (true) {
sleep(1)
if (isLast && !hasLoad) {
adapter.filter(key)
hasLoad = true
}
}
}.start()
}
override fun onQueryTextChange(newText: CharSequence): Boolean {
if (newText.isNotEmpty()) key = newText
if (newText.isNotEmpty()) adapter.filter(newText)
return true
}
override fun onQueryTextSubmit(query: CharSequence): Boolean {
if(query.isNotEmpty()) Thread{
val data = dict[query]
runOnUiThread {
showDictAlert(query.toString(), data)
}
}.start()
if(query.isNotEmpty()) {
val key = query.toString()
val data = dict[key]
showDictAlert(key, data)
}
return true
}
})
setOnMicClickListener(object : SearchLayout.OnMicClickListener {
override fun onMicClick() {
if (SearchUtils.isVoiceSearchAvailable(this@MainActivity)) {
SearchUtils.setVoiceSearch(this@MainActivity, "speak")
SearchUtils.setVoiceSearch(this@MainActivity, "please speak")
}
}
})
@@ -99,11 +87,28 @@ class MainActivity : AppCompatActivity() {
}
override fun onBackPressed() {
if(ffms.hasFocus()) ffms.clearFocus()
if(ffms.hasFocus()) {
if(hasLiked) (ffr.adapter as ListViewHolder.RecyclerViewAdapter).refresh()
ffms.clearFocus()
}
else super.onBackPressed()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when(requestCode) {
SearchUtils.SPEECH_REQUEST_CODE -> data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.let {
if(it.isNotEmpty()) {
ffms.requestFocus()
ffms.mSearchEditText?.setText(it[0])
}
}
}
}
private fun showDictAlert(key: String, data: String?) {
val like = getSharedPreferences("dict", MODE_PRIVATE)?.contains(key)?:false
hasLiked = false
AlertDialog.Builder(this@MainActivity)
.setTitle(key)
.setMessage(data)
@@ -120,55 +125,82 @@ class MainActivity : AppCompatActivity() {
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
.setNeutralButton("收藏") { _, _ ->
getSharedPreferences("dict", MODE_PRIVATE)?.edit()?.let {
it.putString(key, data)
it.apply()
.setNeutralButton(if(like) "取消收藏" else "收藏") { _, _ ->
getSharedPreferences("dict", MODE_PRIVATE)?.edit()?.apply {
if(like) remove(key) else putString(key, data)
hasLiked = true
apply()
}
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
inner class ViewData(itemView: View) : RecyclerView.ViewHolder(itemView) {
inner class RecyclerViewAdapter :
RecyclerView.Adapter<ViewData>() {
var count = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewData {
return ViewData(
inner class SearchViewHolder(itemView: View) : ListViewHolder(itemView) {
inner class RecyclerViewAdapter : ListViewHolder.RecyclerViewAdapter() {
override fun getValue(key: String) = dict[key]
fun filter(text: CharSequence) {
Thread{
val selectSet = dict.keys.filter { it.contains(text, true) }.toSet() +
dict.filterValues { it?.contains(text, true)?:false }.let {
val newSet = mutableSetOf<String>()
it.keys.forEach {
newSet += it
}
newSet
}
listKeys = selectSet.toList()
listKeys?.forEach {
Log.d("MyMain", "Select key: $it")
}
runOnUiThread { notifyDataSetChanged() }
}.start()
}
}
}
inner class LikeViewHolder(itemView: View) : ListViewHolder(itemView) {
inner class RecyclerViewAdapter: ListViewHolder.RecyclerViewAdapter(){
override fun getKeys() = getSharedPreferences("dict", MODE_PRIVATE).all.keys.toList()
override fun getValue(key: String) = getSharedPreferences("dict", MODE_PRIVATE).getString(key, "null")
}
}
open inner class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
open inner class RecyclerViewAdapter :
RecyclerView.Adapter<ListViewHolder>() {
var listKeys = getKeys()
open fun getKeys(): List<String>? = null
open fun getValue(key: String): String? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
return ListViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.line_word, parent, false)
)
}
@SuppressLint("ClickableViewAccessibility", "SetTextI18n")
override fun onBindViewHolder(holder: ViewData, position: Int) {
Log.d("MyMain", "Bind $position")
if(position < keys.size) {
holder.itemView.ta.text = keys[position]
if(position < datas.size) holder.itemView.tb.text = datas[position]
holder.itemView.setOnClickListener {
showDictAlert(keys[position], if(position < datas.size) datas[position] else "null")
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
Log.d("MyMain", "Bind like at $position")
listKeys?.apply {
if (position < size) {
val key = get(position)
val data = getValue(key)
holder.itemView.apply {
ta.text = key
tb.text = data
setOnClickListener {
showDictAlert(key, data)
}
}
}
}
}
override fun getItemCount() = count
override fun getItemCount() = listKeys?.size?:0
fun filter(text: CharSequence) {
dict.pattern = text
dict.keys.let {
count = it.size
if (count > 0) {
keys = arrayOf()
datas = arrayOf()
it.forEach {
keys += it
datas += dict[it]
Log.d("MyMain", "Get key: $it is ${datas.last()}")
}
}
}
fun refresh() {
listKeys = getKeys()
runOnUiThread { notifyDataSetChanged() }
}
}

View File

@@ -1,46 +1,97 @@
package top.fumiama.simpledict
import android.util.Log
import java.lang.Thread.sleep
class SimpleDict(private val client: Client): HashMap<String, String>() { //must run in thread
var pattern: CharSequence = "a"
private var isInit = true
override val keys: MutableSet<String>
class SimpleDict(private val client: Client, private val pwd: String) { //must run in thread
private var dict = HashMap<String, String?>()
val keys get() = dict.keys
val values get() = dict.values
//val size get() = dict.size
private val raw: ByteArray?
get() {
val re = mutableSetOf<String>()
if(isInit) {
isInit = false
return re
} else {
client.initConnect()
client.sendMessage("lst")
sleep(233)
client.receiveMessage()
client.sendMessage(pattern)
client.receiveMessage()?.substringBeforeLast('\n')?.split('\n')?.forEach {
re.add(it)
}
client.sendMessage("quit")
client.closeConnect()
return re
}
initDict()
client.sendMessage("cat")
sleep(233)
val re = client.receiveRawMessage()
closeDict()
return re
}
override fun get(key: String): String? {
init {
Thread{ fetchDict() }.start()
}
private fun initDict() {
client.initConnect()
client.sendMessage(pwd)
}
private fun closeDict() {
client.sendMessage("quit")
client.closeConnect()
}
private fun analyzeDictBlk(dictBlock: ByteArray) {
Log.d("MySD", "Read block: ${dictBlock.decodeToString()}")
val keyLen = dictBlock[63].toInt().let { if (it > 63) 63 else it }
val dataEnd = 64 + dictBlock[127].toInt().let { if (it > 63) 63 else it }
val key = dictBlock.copyOf(keyLen).decodeToString()
val data = if (dataEnd > 64) dictBlock.copyOfRange(64, dataEnd).decodeToString() else null
dict[key] = data
Log.d("MySD", "Fetch $key=$data")
}
//fun filterKeys(predicate: (String) -> Boolean) = dict.filterKeys(predicate)
fun filterValues(predicate: (String?) -> Boolean) = dict.filterValues(predicate)
fun fetchDict() {
val dictBlock = ByteArray(128)
raw?.inputStream()?.let {
var c = '1'
while (!it.read().toChar().isDigit()) Log.d("MySD", "Skip banner.")
while (c.isDigit()) {
c = it.read().toChar()
Log.d("MySD", "Skip digit $c.")
}
dictBlock[0] = c.toByte()
if(it.read(dictBlock, 1, 127) == 127) {
analyzeDictBlk(dictBlock)
while (it.read(dictBlock, 0, 128) == 128) analyzeDictBlk(dictBlock)
}
}
}
/*fun keysWithPattern(pattern: String): MutableSet<String>{
val re = mutableSetOf<String>()
initDict()
client.sendMessage("lst")
sleep(233)
client.receiveMessage()
client.sendMessage(pattern)
client.receiveMessage()?.substringBeforeLast('\n')?.split('\n')?.forEach {
re.add(it)
}
closeDict()
return re
}
fun getDirectly(key: String): String? {
initDict()
client.sendMessage("get")
sleep(233)
client.receiveMessage()
client.sendMessage(key)
val re = client.receiveMessage()
client.sendMessage("quit")
client.closeConnect()
closeDict()
return re
}
}*/
override fun put(key: String, value: String): String? {
val p = this[key]
client.initConnect()
operator fun get(key: String) = dict[key]
operator fun set(key: String, value: String): String? {
val p = dict[key]
initDict()
client.sendMessage("set")
sleep(233)
client.receiveMessage()
@@ -48,8 +99,7 @@ class SimpleDict(private val client: Client): HashMap<String, String>() { //mu
client.receiveMessage()
client.sendMessage(value)
client.receiveMessage()
client.sendMessage("quit")
client.closeConnect()
closeDict()
return p
}
}

Binary file not shown.

View File

@@ -1,13 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
<com.lapism.search.widget.MaterialSearchView
android:id="@+id/ffms"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorSurface"
app:toolbarId="@+id/toolbar">
<com.lapism.search.widget.MaterialSearchView
android:id="@+id/ffms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.lapism.search.widget.SearchBehavior" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/ffsw"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.core.widget.NestedScrollView
android:id="@+id/ffns"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/ffr"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -15,9 +15,10 @@
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:fontFamily="@font/gotham"
android:text="TextView"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textSize="18sp" />
android:textColor="?attr/colorOnSurface"
android:textSize="22sp" />
<TextView
android:id="@+id/tb"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB