1
0
mirror of https://github.com/fumiama/simple-dict-android.git synced 2026-06-05 16:50:26 +08:00
This commit is contained in:
fumiama
2021-02-18 17:26:28 +08:00
parent c82f715f55
commit 1e0a7b2a4c
15 changed files with 207 additions and 148 deletions

7
.idea/dictionaries/spayi.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="spayi">
<words>
<w>recv</w>
</words>
</dictionary>
</component>

View File

@@ -13,8 +13,9 @@ android {
applicationId "top.fumiama.simpledict"
minSdkVersion 26
targetSdkVersion 30
versionCode 4
versionName '1.2'
versionCode 5
versionName '1.3'
resConfigs "zh", "zh-rCN"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -6,7 +6,7 @@ import java.io.InputStream
import java.io.OutputStream
import java.net.Socket
class Client(val ip: String, val port: Int) {
class Client(private val ip: String, private val port: Int) {
//普通数据交互接口
private var sc: Socket? = null
@@ -15,23 +15,22 @@ class Client(val ip: String, val port: Int) {
private var din: InputStream? = null
//已连接标记
var isConnect = false
private val isConnect get() = sc != null && din != null && dout != null
/**
* 初始化普通交互连接
*/
fun initConnect(){
try {
fun initConnect(depth: Int = 0){
if(depth > 3) Log.d("MyC", "connect server failed after $depth tries")
else try {
sc = Socket(ip, port) //通过socket连接服务器
din = sc?.getInputStream() //获取输入流并转换为StreamReader约定编码格式
dout = sc?.getOutputStream() //获取输出流
sc?.soTimeout = 10000 //设置连接超时限制
if (sc != null && din != null && dout != null) { //判断一下是否都连上避免NullPointException
isConnect = true
Log.d("MyC", "connect server successful")
} else {
Log.d("MyC", "connect server failed,now retry...")
initConnect()
sc?.soTimeout = 2333 //设置连接超时限制
if (isConnect) Log.d("MyC", "connect server successful")
else {
Log.d("MyC", "connect server failed, now retry...")
initConnect(depth + 1)
}
} catch (e: IOException) { //获取输入输出流是可能报IOException的所以必须try-catch
e.printStackTrace()
@@ -45,30 +44,31 @@ class Client(val ip: String, val port: Int) {
fun sendMessage(message: CharSequence?) {
try {
if (isConnect) {
if (dout != null && message != null) { //判断输出流或者消息是否为空为空的话会产生nullpoint错误
dout!!.write(message.toString().toByteArray())
dout!!.flush()
} else Log.d("MyC", "The message to be sent is empty or have no connect")
if (message != null) { //判断输出流或者消息是否为空为空的话会产生nullpoint错误
dout?.write(message.toString().toByteArray())
dout?.flush()
Log.d("MyC", "Send msg: $message")
} else Log.d("MyC", "The message to be sent is empty")
Log.d("MyC", "send message succeed")
} else Log.d("MyC", "no connect to send message")
} else Log.d("MyC", "send message failed: no connect")
} catch (e: IOException) {
Log.d("MyC", "send message to cilent failed")
Log.d("MyC", "send message failed: crash")
e.printStackTrace()
}
}
fun receiveRawMessage() : ByteArray {
fun receiveRawMessage(totalSize: Int = -1, bufferSize: Int = 4096) : ByteArray {
var re = byteArrayOf()
try {
if (isConnect) {
Log.d("MyC", "开始接收服务端信息")
val inMessage = ByteArray(4096) //设置接受缓冲,避免接受数据过长占用过多内存
val inMessage = ByteArray(bufferSize) //设置接受缓冲,避免接受数据过长占用过多内存
var a: Int
do {
a = din?.read(inMessage)?:0 //a存储返回消息的长度
Log.d("MyC", "reply length:$a: ${inMessage.decodeToString()}")
re += inMessage.copyOf(a)
} while (a == 4096)
Log.d("MyC", "reply length:$a: ${re.decodeToString()}")
} while (a == bufferSize || totalSize > re.size)
} else Log.d("MyC", "no connect to receive message")
} catch (e: IOException) {
Log.d("MyC", "receive message failed")
@@ -87,10 +87,12 @@ class Client(val ip: String, val port: Int) {
din?.close()
dout?.close()
sc?.close()
sc = null
din = null
dout = null
} catch (e: IOException) {
e.printStackTrace()
}
isConnect = false
Log.d("MyC", "关闭连接")
}
}

View File

@@ -9,10 +9,12 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
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.textfield.TextInputLayout
import com.lapism.search.internal.SearchLayout
import com.lapism.search.util.SearchUtils
import kotlinx.android.synthetic.main.activity_main.*
@@ -21,6 +23,14 @@ import kotlinx.android.synthetic.main.line_word.view.*
class MainActivity : AppCompatActivity() {
private val dict = SimpleDict(Client("127.0.0.1", 8000), "fumiama")
private var hasLiked = false
private val fetchThread get() = Thread{
dict.fetchDict {
runOnUiThread {
Toast.makeText(this@MainActivity, "刷新成功", Toast.LENGTH_SHORT).show()
ffsw.isRefreshing = false
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -35,8 +45,10 @@ class MainActivity : AppCompatActivity() {
ffsw.apply {
setOnRefreshListener {
ad.refresh()
isRefreshing = false
fetchThread.start()
}
isRefreshing = true
fetchThread.start()
}
}
@@ -107,15 +119,15 @@ class MainActivity : AppCompatActivity() {
}
private fun showDictAlert(key: String, data: String?) {
val like = getSharedPreferences("dict", MODE_PRIVATE)?.contains(key)?:false
val hintAdd = if(data != null && data != "null") "重设" else "添加"
hasLiked = false
AlertDialog.Builder(this@MainActivity)
.setTitle(key)
.setMessage(data)
.setPositiveButton(if(data != "null") "重设" else "添加") { _, _ ->
.setPositiveButton(hintAdd) { _, _ ->
val t = EditText(this@MainActivity)
AlertDialog.Builder(this@MainActivity)
.setTitle("重设$key")
.setTitle("$hintAdd$key")
.setView(t)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (t.text.isNotEmpty()) Thread {
@@ -125,12 +137,8 @@ class MainActivity : AppCompatActivity() {
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
.setNeutralButton(if(like) "取消收藏" else "收藏") { _, _ ->
getSharedPreferences("dict", MODE_PRIVATE)?.edit()?.apply {
if(like) remove(key) else putString(key, data)
hasLiked = true
apply()
}
.setNeutralButton("删除") { _, _ ->
Thread{dict -= key}.start()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
@@ -182,19 +190,38 @@ class MainActivity : AppCompatActivity() {
@SuppressLint("ClickableViewAccessibility", "SetTextI18n")
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)
Thread{
listKeys?.apply {
if (position < size) {
val key = get(position)
val data = getValue(key)
val like = getSharedPreferences("dict", MODE_PRIVATE)?.contains(key) == true
runOnUiThread {
holder.itemView.apply {
ta.text = key
tb.text = data
if(like) vl.setBackgroundResource(R.drawable.ic_like_filled)
setOnClickListener {
showDictAlert(key, data)
}
vl.setOnClickListener {
getSharedPreferences("dict", MODE_PRIVATE)?.edit()?.apply {
if(like) {
remove(key)
it.setBackgroundResource(R.drawable.ic_like)
} else {
putString(key, data)
it.setBackgroundResource(R.drawable.ic_like_filled)
}
hasLiked = true
apply()
}
}
}
}
}
}
}
}.start()
}
override fun getItemCount() = listKeys?.size?:0

View File

@@ -10,21 +10,44 @@ class SimpleDict(private val client: Client, private val pwd: String) { //must
//val size get() = dict.size
private val raw: ByteArray
get() {
initDict()
client.sendMessage("cat")
sleep(2333)
val re = client.receiveRawMessage()
closeDict()
var times = 3
var re: ByteArray
var firstRecv: ByteArray
do {
re = byteArrayOf()
initDict()
sendMessageWithDelay("cat", 2333)
try {
firstRecv = client.receiveRawMessage()
val firstStr = firstRecv.decodeToString()
var length = ""
for ((i, c) in firstStr.withIndex()) {
if(c.isDigit()) length += c
else {
if(i + 1 < firstRecv.size) re = firstRecv.copyOfRange(i, firstRecv.size)
break
}
}
re += client.receiveRawMessage(length.toInt() - re.size)
break
} catch (e: Exception){
e.printStackTrace()
}
closeDict()
} while (times-- > 0)
return re
}
init {
Thread{ fetchDict() }.start()
}
private fun sendMessageWithDelay(msg: CharSequence, delay: Long = 233) = Thread{
client.sendMessage(msg)
sleep(delay)
}.start()
private fun initDict() {
client.initConnect()
client.sendMessage(pwd)
client.receiveRawMessage()
sleep(233)
}
private fun closeDict() {
@@ -45,59 +68,36 @@ class SimpleDict(private val client: Client, private val pwd: String) { //must
//fun filterKeys(predicate: (String) -> Boolean) = dict.filterKeys(predicate)
fun filterValues(predicate: (String?) -> Boolean) = dict.filterValues(predicate)
fun fetchDict() {
fun fetchDict(doOnLoadSuccess: ()->Unit = {
Log.d("MySD", "Fetch dict success")
}) {
val dictBlock = ByteArray(128)
dict = hashMapOf()
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)
}
while (it.read(dictBlock, 0, 128) == 128) analyzeDictBlk(dictBlock)
doOnLoadSuccess()
}
}
/*fun keysWithPattern(pattern: String): MutableSet<String>{
val re = mutableSetOf<String>()
operator fun minusAssign(key: String) {
initDict()
client.sendMessage("lst")
sleep(233)
sendMessageWithDelay("del")
client.receiveMessage()
sendMessageWithDelay(key)
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()
closeDict()
return re
}*/
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)
sendMessageWithDelay("set")
client.receiveMessage()
client.sendMessage(key)
sendMessageWithDelay(key)
client.receiveMessage()
client.sendMessage(value)
sendMessageWithDelay(value)
client.receiveMessage()
closeDict()
dict[key] = value

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportHeight="1024"
android:viewportWidth="1024">
<path
android:fillColor="@android:color/holo_red_light"
android:pathData="M668 117C833 117 939 250 939 428c0 138-125 291-372 462a97 97 0 0 1-110 0C210 718 85 566 85 428 85 250 191 117 356 117c60 0 100 21 156 68C568 138 608 117 668 117z m0 63c-41 0-70 15-117 55-2 2-14 12-18 15a32 32 0 0 1-42 0c-4-3-16-14-18-15-47-40-76-55-117-55C230 180 149 281 149 427 149 538 263 675 494 835a32 32 0 0 0 37 0C761 675 875 538 875 427c0-145-81-246-207-246z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportHeight="1024"
android:viewportWidth="1024">
<path
android:fillColor="@android:color/holo_red_light"
android:pathData="M668 117C833 117 939 250 939 428c0 138-125 291-372 462a97 97 0 0 1-110 0C210 718 85 566 85 428 85 250 191 117 356 117c60 0 100 21 156 68C568 138 608 117 668 117z"/>
</vector>

View File

@@ -1,32 +1,33 @@
<?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"
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
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.CollapsingToolbarLayout
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorSurface"
android:layout_height="wrap_content"
android:fillViewport="true"
app:layout_scrollFlags="scroll">
app:layout_scrollFlags="scroll"
app:toolbarId="@+id/toolbar">
<com.lapism.search.widget.MaterialSearchView
android:id="@+id/ffms"
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.lapism.search.widget.SearchBehavior" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
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>
</androidx.core.widget.NestedScrollView>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@@ -46,6 +47,4 @@
android:layout_height="wrap_content" />
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,31 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="vertical">
android:foreground="?android:attr/selectableItemBackground">
<TextView
android:id="@+id/ta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
<View
android:id="@+id/vl"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="16dp"
android:fontFamily="@font/gotham"
android:text="TextView"
android:textColor="?attr/colorOnSurface"
android:textSize="22sp" />
android:background="@drawable/ic_like"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"/>
<TextView
android:id="@+id/tb"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="TextView" />
</LinearLayout>
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/ta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:fontFamily="@font/gotham"
android:textColor="?attr/colorOnSurface"
android:textSize="22sp" />
<TextView
android:id="@+id/tb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -4,11 +4,11 @@
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<item name="colorOnPrimary">@android:color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<item name="colorOnSecondary">@android:color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->

View File

@@ -1,12 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="colorPrimary">#BA8D08</color>
<color name="colorPrimaryVariant">#FF9800</color>
<color name="colorSecondary">#FF845E</color>

View File

@@ -1,3 +0,0 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
</resources>

View File

@@ -1,12 +1,3 @@
<resources>
<string name="app_name">SimpleDict</string>
<string name="action_settings">Settings</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="hello_first_fragment">Hello first fragment</string>
<string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
</resources>

Binary file not shown.

View File

@@ -10,8 +10,8 @@
{
"type": "SINGLE",
"filters": [],
"versionCode": 4,
"versionName": "1.2",
"versionCode": 5,
"versionName": "1.3",
"outputFile": "app-winrelease.apk"
}
]