1
0
mirror of https://github.com/fumiama/simple-dict-android.git synced 2026-06-09 20:40:49 +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 { defaultConfig {
applicationId "top.fumiama.simpledict" applicationId "top.fumiama.simpledict"
minSdkVersion 23 minSdkVersion 26
targetSdkVersion 30 targetSdkVersion 30
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
@@ -62,6 +62,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3'
implementation 'androidx.navigation:navigation-ui-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' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
@@ -93,7 +94,8 @@ andResGuard {
"R.string.ga_trackingId", "R.string.ga_trackingId",
"R.string.firebase_database_url", "R.string.firebase_database_url",
"R.string.google_api_key", "R.string.google_api_key",
"R.string.google_crash_reporting_api_key" "R.string.google_crash_reporting_api_key",
"R.font.*"
] ]
compressFilePattern = [ compressFilePattern = [
"*.png", "*.png",

View File

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

View File

@@ -1,91 +1,79 @@
package top.fumiama.simpledict package top.fumiama.simpledict
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.SharedPreferences import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.speech.RecognizerIntent
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView 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.internal.SearchLayout
import com.lapism.search.util.SearchUtils import com.lapism.search.util.SearchUtils
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.line_word.view.* import kotlinx.android.synthetic.main.line_word.view.*
import java.lang.Thread.sleep
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private var keys = arrayOf<String>() private val dict = SimpleDict(Client("pan.fumiama.top", 43792), "fumiama")
private var datas = arrayOf<String?>() private var hasLiked = false
private val dict = SimpleDict(Client("192.168.98.2", 8000))
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) 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 { ffms.apply {
setAdapterLayoutManager(LinearLayoutManager(this@MainActivity)) 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) setAdapter(adapter)
navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH
setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener { setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener {
override fun onNavigationClick(hasFocus: Boolean) { override fun onNavigationClick(hasFocus: Boolean) {
if (hasFocus()) clearFocus() if (hasFocus()) {
if(hasLiked) ad.refresh()
clearFocus()
}
else requestFocus() else requestFocus()
} }
}) })
setTextHint(android.R.string.search_go) setTextHint(android.R.string.search_go)
setOnQueryTextListener(object : SearchLayout.OnQueryTextListener { 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 { override fun onQueryTextChange(newText: CharSequence): Boolean {
if (newText.isNotEmpty()) key = newText if (newText.isNotEmpty()) adapter.filter(newText)
return true return true
} }
override fun onQueryTextSubmit(query: CharSequence): Boolean { override fun onQueryTextSubmit(query: CharSequence): Boolean {
if(query.isNotEmpty()) Thread{ if(query.isNotEmpty()) {
val data = dict[query] val key = query.toString()
runOnUiThread { val data = dict[key]
showDictAlert(query.toString(), data) showDictAlert(key, data)
} }
}.start()
return true return true
} }
}) })
setOnMicClickListener(object : SearchLayout.OnMicClickListener { setOnMicClickListener(object : SearchLayout.OnMicClickListener {
override fun onMicClick() { override fun onMicClick() {
if (SearchUtils.isVoiceSearchAvailable(this@MainActivity)) { 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() { override fun onBackPressed() {
if(ffms.hasFocus()) ffms.clearFocus() if(ffms.hasFocus()) {
if(hasLiked) (ffr.adapter as ListViewHolder.RecyclerViewAdapter).refresh()
ffms.clearFocus()
}
else super.onBackPressed() 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?) { private fun showDictAlert(key: String, data: String?) {
val like = getSharedPreferences("dict", MODE_PRIVATE)?.contains(key)?:false
hasLiked = false
AlertDialog.Builder(this@MainActivity) AlertDialog.Builder(this@MainActivity)
.setTitle(key) .setTitle(key)
.setMessage(data) .setMessage(data)
@@ -120,55 +125,82 @@ class MainActivity : AppCompatActivity() {
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
.show() .show()
} }
.setNeutralButton("收藏") { _, _ -> .setNeutralButton(if(like) "取消收藏" else "收藏") { _, _ ->
getSharedPreferences("dict", MODE_PRIVATE)?.edit()?.let { getSharedPreferences("dict", MODE_PRIVATE)?.edit()?.apply {
it.putString(key, data) if(like) remove(key) else putString(key, data)
it.apply() hasLiked = true
apply()
} }
} }
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
.show() .show()
} }
inner class ViewData(itemView: View) : RecyclerView.ViewHolder(itemView) { inner class SearchViewHolder(itemView: View) : ListViewHolder(itemView) {
inner class RecyclerViewAdapter : inner class RecyclerViewAdapter : ListViewHolder.RecyclerViewAdapter() {
RecyclerView.Adapter<ViewData>() { override fun getValue(key: String) = dict[key]
var count = 0 fun filter(text: CharSequence) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewData { Thread{
return ViewData( 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) LayoutInflater.from(parent.context)
.inflate(R.layout.line_word, parent, false) .inflate(R.layout.line_word, parent, false)
) )
} }
@SuppressLint("ClickableViewAccessibility", "SetTextI18n") @SuppressLint("ClickableViewAccessibility", "SetTextI18n")
override fun onBindViewHolder(holder: ViewData, position: Int) { override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
Log.d("MyMain", "Bind $position") Log.d("MyMain", "Bind like at $position")
if(position < keys.size) { listKeys?.apply {
holder.itemView.ta.text = keys[position] if (position < size) {
if(position < datas.size) holder.itemView.tb.text = datas[position] val key = get(position)
holder.itemView.setOnClickListener { val data = getValue(key)
showDictAlert(keys[position], if(position < datas.size) datas[position] else "null") 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) { fun refresh() {
dict.pattern = text listKeys = getKeys()
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()}")
}
}
}
runOnUiThread { notifyDataSetChanged() } runOnUiThread { notifyDataSetChanged() }
} }
} }

View File

@@ -1,46 +1,97 @@
package top.fumiama.simpledict package top.fumiama.simpledict
import android.util.Log
import java.lang.Thread.sleep import java.lang.Thread.sleep
class SimpleDict(private val client: Client): HashMap<String, String>() { //must run in thread class SimpleDict(private val client: Client, private val pwd: String) { //must run in thread
var pattern: CharSequence = "a" private var dict = HashMap<String, String?>()
private var isInit = true val keys get() = dict.keys
override val keys: MutableSet<String> val values get() = dict.values
//val size get() = dict.size
private val raw: ByteArray?
get() { get() {
val re = mutableSetOf<String>() initDict()
if(isInit) { client.sendMessage("cat")
isInit = false sleep(233)
return re val re = client.receiveRawMessage()
} else { closeDict()
client.initConnect() return re
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
}
} }
override fun get(key: String): String? { init {
Thread{ fetchDict() }.start()
}
private fun initDict() {
client.initConnect() 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") client.sendMessage("get")
sleep(233) sleep(233)
client.receiveMessage() client.receiveMessage()
client.sendMessage(key) client.sendMessage(key)
val re = client.receiveMessage() val re = client.receiveMessage()
client.sendMessage("quit") closeDict()
client.closeConnect()
return re return re
} }*/
override fun put(key: String, value: String): String? { operator fun get(key: String) = dict[key]
val p = this[key]
client.initConnect() operator fun set(key: String, value: String): String? {
val p = dict[key]
initDict()
client.sendMessage("set") client.sendMessage("set")
sleep(233) sleep(233)
client.receiveMessage() client.receiveMessage()
@@ -48,8 +99,7 @@ class SimpleDict(private val client: Client): HashMap<String, String>() { //mu
client.receiveMessage() client.receiveMessage()
client.sendMessage(value) client.sendMessage(value)
client.receiveMessage() client.receiveMessage()
client.sendMessage("quit") closeDict()
client.closeConnect()
return p return p
} }
} }

Binary file not shown.

View File

@@ -1,13 +1,50 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
tools:context=".MainActivity">
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_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> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

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