1
0
mirror of https://github.com/fumiama/simple-dict-android.git synced 2026-06-05 00:30:24 +08:00
大幅优化,新增众多功能。
This commit is contained in:
源文雨
2023-12-07 23:33:27 +09:00
parent 804a940dd6
commit 6ee2fec108
27 changed files with 1047 additions and 465 deletions

2
.idea/compiler.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
<bytecodeTargetLevel target="17" />
</component>
</project>

3
.idea/gradle.xml generated
View File

@@ -7,14 +7,13 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>

View File

@@ -21,5 +21,15 @@
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
</component>
</project>

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.7.10" />
</component>
</project>

2
.idea/misc.xml generated
View File

@@ -8,7 +8,7 @@
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@@ -5,15 +5,13 @@ plugins {
}
android {
compileSdkVersion 31
buildToolsVersion "30.0.2"
defaultConfig {
compileSdk 34
applicationId "top.fumiama.simpledict"
minSdkVersion 26
targetSdkVersion 31
versionCode 19
versionName '4.0'
targetSdkVersion 34
versionCode 20
versionName '5.0.0'
resConfigs "zh", "zh-rCN"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -39,19 +37,21 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
namespace 'top.fumiama.simpledict'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.5'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'com.lapism:search:2.4.1@aar'
}

View File

@@ -0,0 +1,59 @@
package top.fumiama.simpledict
class ControlBarState(private var pageSize: Int) {
var index = 0
set(value) {
if(total == 0) return
if(value < 0 || value > total) return
var s = value
if(value+pageSize > total) s = total - pageSize
if(s < 0) s = 0
end = s + pageSize
field = s
}
var total: Int = 0
set(value) {
if(value >= 0) field = value
}
var sort = SORT_EDIT_TIME_DOWN
set(value) {
if(value < 0 || value > SORT_LENGTH_DOWN) return
field = value
}
private var end = pageSize
fun formatRange(fmt: String) = fmt.format(index, end)
fun formatSize(fmt: String) = fmt.format(total)
fun getPosition(p: Int): Int {
if(p > 100 || p < 0) return 0
var newIndex = p * total / 100
if(newIndex + pageSize > total) {
newIndex = total - pageSize
if(newIndex < 0) newIndex = 0
}
return newIndex
}
fun getPercentage() = if(total == 0) 0 else 100 * (index+end)/2 / total
fun sort(keys: List<String>): List<String> {
return when(sort) {
SORT_EDIT_TIME_UP -> keys
SORT_EDIT_TIME_DOWN -> keys.reversed()
SORT_ALPHABET_UP -> keys.sorted()
SORT_ALPHABET_DOWN -> keys.sorted().reversed()
SORT_LENGTH_UP -> keys.sortedBy { k -> return@sortedBy k.length }
SORT_LENGTH_DOWN -> keys.sortedBy { k -> return@sortedBy k.length }.reversed()
else -> keys
}
}
companion object {
const val SORT_EDIT_TIME_UP = 0
const val SORT_EDIT_TIME_DOWN = 1
const val SORT_ALPHABET_UP = 2
const val SORT_ALPHABET_DOWN = 3
const val SORT_LENGTH_UP = 4
const val SORT_LENGTH_DOWN = 5
}
}

View File

@@ -0,0 +1,32 @@
package top.fumiama.simpledict
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
class InnerFragment(private val mHandleOnCreateView: HandleOnCreateView) : Fragment() {
var recyclerView: RecyclerView? = null
private val p get() = arguments?.getInt("p", 0)?:0
constructor(): this(handleOnCreateView!!)
interface HandleOnCreateView {
fun onCreateView(p: Int, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
recyclerView = mHandleOnCreateView.onCreateView(p, inflater, container, savedInstanceState) as RecyclerView
return recyclerView!!
}
companion object {
var handleOnCreateView: HandleOnCreateView? = null
}
}

View File

@@ -1,56 +1,58 @@
package top.fumiama.simpledict
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.text.SpannableString
import android.text.Spanned
import android.text.style.StrikethroughSpan
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.view.WindowManager
import android.widget.ImageButton
import android.widget.SeekBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.core.view.children
import androidx.fragment.app.commit
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.lapism.search.internal.SearchLayout
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main.ffsw
import kotlinx.android.synthetic.main.activity_main.view.*
import kotlinx.android.synthetic.main.card_bottom.cbcard
import kotlinx.android.synthetic.main.dialog_input.view.*
import kotlinx.android.synthetic.main.fragment_main.fmvp
import kotlinx.android.synthetic.main.line_bottom.view.*
import kotlinx.android.synthetic.main.line_word.view.*
import kotlinx.android.synthetic.main.line_word.view.tb
import kotlinx.android.synthetic.main.line_word.view.tn
import java.io.FileNotFoundException
import java.lang.Exception
class MainActivity : AppCompatActivity() {
private val visibleThreshold = 16
private var host = "127.0.0.1"
private var port = 80
private var pwd = "demo"
private var spwd: String? = null
private var dict: SimpleDict? = null
private var hasLiked = false
private var cm: ClipboardManager? = null
private var ad: LikeViewHolder.RecyclerViewAdapter? = null
private var lastLikeLine: View? = null
private var end = 0
private var start = 0
private var mViewPagerPosition = 0
private val mControlBarStates = arrayOf(ControlBarState(visibleThreshold+8), ControlBarState(visibleThreshold+8))
private val mVPAdapter get() = fmvp.adapter as MainFragment.PagerAdapter
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val ime = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
getSharedPreferences("remote", MODE_PRIVATE)?.apply {
if(contains("host")) getString("host", host)?.apply { host = this }
if(contains("port")) getInt("port", port).apply { port = this }
@@ -58,13 +60,15 @@ class MainActivity : AppCompatActivity() {
if(contains("spwd")) getString("spwd", spwd)?.apply { spwd = this }
}
dict = SimpleDict(Client(host, port), pwd, externalCacheDir, spwd)
ad = LikeViewHolder(ffr).RecyclerViewAdapter()
cm = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
ffr.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = ad
setOnScrollChangeListener { _, _, scrollY, _, _ ->
this@MainActivity.ffsw.isEnabled = scrollY == 0
if(savedInstanceState == null) {
MainFragment.handleOnViewCreated = HandleOnViewCreated()
InnerFragment.handleOnCreateView = HandleOnCreateView()
supportFragmentManager.commit {
setReorderingAllowed(true)
add(R.id.fffc, MainFragment())
}
}
@@ -81,20 +85,28 @@ class MainActivity : AppCompatActivity() {
}
ffms.apply {
val recyclerView = findViewById<RecyclerView>(R.id.search_recycler_view)
setAdapterLayoutManager(LinearLayoutManager(this@MainActivity))
val recyclerView = findViewById<RecyclerView>(com.lapism.search.R.id.search_recycler_view)
val lm = LinearLayoutManager(this@MainActivity)
setAdapterLayoutManager(lm)
val adapter = SearchViewHolder(recyclerView).RecyclerViewAdapter()
recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val a = lm.findFirstVisibleItemPosition()
val b = lm.findLastVisibleItemPosition()
val total = lm.itemCount
if(a <= 0) adapter.scrollUp(1)
else if(b >= total-1) adapter.scrollDown(1)
}
})
setAdapter(adapter)
navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH
setMicIconImageResource(R.drawable.ic_setting)
val micView = findViewById<ImageButton>(R.id.search_image_view_mic)
val micView = findViewById<ImageButton>(com.lapism.search.R.id.search_image_view_mic)
setClearFocusOnBackPressed(true)
setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener {
override fun onNavigationClick(hasFocus: Boolean) {
if (hasFocus()) {
if(hasLiked) ad?.refresh()
clearFocus()
}
if (hasFocus()) clearFocus()
else requestFocus()
}
})
@@ -116,9 +128,9 @@ class MainActivity : AppCompatActivity() {
if(query.isNotEmpty()) {
val key = query.toString()
val data = dict?.get(key)
showDictAlert(key, data, recyclerView.children.toList().let {
val i = it.map { it.ta.text }.indexOf(key)
if(i >= 0) it[i] else null
showDictAlert(key, data, recyclerView.children.toList().let { children ->
val i = children.map { it.ta.text }.indexOf(key)
if(i >= 0) children[i] else null
})
}
return true
@@ -169,44 +181,79 @@ class MainActivity : AppCompatActivity() {
setOnFocusChangeListener(object : SearchLayout.OnFocusChangeListener {
override fun onFocusChange(hasFocus: Boolean) {
navigationIconSupport = if (hasFocus) SearchLayout.NavigationIconSupport.ARROW
navigationIconSupport = if (hasFocus) {
hideControlCard(true)
SearchLayout.NavigationIconSupport.ARROW
}
else {
micView.postDelayed({ micView.visibility = View.VISIBLE }, 233)
micView.postDelayed({
micView.visibility = View.VISIBLE
showControlCard(true)
}, 233)
SearchLayout.NavigationIconSupport.SEARCH
}
}
})
}
this@MainActivity.ffc.setOnTouchListener { _, e ->
if (e.action == MotionEvent.ACTION_UP && mSearchEditText?.text?.isNotEmpty() == true) {
ime.hideSoftInputFromWindow(window.decorView.windowToken, 0)
var isSeeking = false
cctrl.sb.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(s: SeekBar?, p: Int, isUser: Boolean) {
Log.d("MyMain", "seek to $p")
if(isSeeking) {
val bar = mControlBarStates[mViewPagerPosition]
bar.index = bar.getPosition(p)
updateSize(false)
}
}
override fun onStartTrackingTouch(s: SeekBar?) {
isSeeking = true
Log.d("MyMain", "onStartTrackingTouch")
}
override fun onStopTrackingTouch(s: SeekBar?) {
isSeeking = false
Log.d("MyMain", "onStopTrackingTouch")
s?.progress?.let {
val ad = mVPAdapter.views[mViewPagerPosition]?.recyclerView?.adapter as? ListViewHolder.RecyclerViewAdapter ?: return
ad.setProgress(it)
}
}
})
var isHide = false
cbcard.setOnClickListener {
Log.d("MyMain", "cbcard clicked")
isHide = if (isHide) {
showControlCard()
false
} else {
hideControlCard()
true
}
}
cbcard.setOnLongClickListener {
if(!isHide) AlertDialog.Builder(this)
.setTitle(R.string.alert_select_sort_type)
.setIcon(R.mipmap.ic_launcher)
.setSingleChoiceItems(R.array.sort_type, mControlBarStates[mViewPagerPosition].sort) { d, p ->
mControlBarStates[mViewPagerPosition].sort = p
d.cancel()
val ad = mVPAdapter.views[mViewPagerPosition]?.recyclerView?.adapter as? ListViewHolder.RecyclerViewAdapter ?: return@setSingleChoiceItems
ad.refresh()
}.show()
true
}
}
override fun onBackPressed() {
if(ffms.hasFocus()) {
if(hasLiked) ad?.refresh()
} 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 updateSize() = runOnUiThread {
lastLikeLine?.fftt?.text = "${dict?.size?.toString()?:"0"} syez rjimj"
lastLikeLine?.fftc?.text = "${start}-${end}"
private fun updateSize(updateSeekbar: Boolean = true) = runOnUiThread {
Log.d("MyMain", "update size, updateSeekbar: $updateSeekbar")
val bar = mControlBarStates[mViewPagerPosition]
cctrl?.lbtindex?.text = bar.formatRange(getString(R.string.info_index_meter))
cctrl?.lbttotal?.text = bar.formatSize(getString(R.string.info_words_total))
if (updateSeekbar) cctrl?.sb?.progress = bar.getPercentage()
}
private fun fetchThread(doWhenFinish: (()->Unit)? = null) {
@@ -222,9 +269,8 @@ class MainActivity : AppCompatActivity() {
}) {
runOnUiThread {
ffsw.isRefreshing = false
ad?.capacity = 5
ad?.offset = 0
ad?.refresh()
(mVPAdapter.views[mViewPagerPosition]?.recyclerView?.adapter as? ListViewHolder.RecyclerViewAdapter)?.refresh()
updateSize()
doWhenFinish?.apply { this() }
}
}
@@ -233,7 +279,6 @@ class MainActivity : AppCompatActivity() {
private fun showDictAlert(key: String, data: String?, line: View?) {
val hintAdd = if(data != null && data != "null") "重设" else "添加"
hasLiked = false
AlertDialog.Builder(this@MainActivity)
.setTitle(key)
.setMessage(data)
@@ -250,7 +295,6 @@ class MainActivity : AppCompatActivity() {
val k = key.trim().replace(Regex("[\\uFF00-\\uFF5E]")) { (it.value[0] - 0xFEE0).toString() }
if(dict?.set(k, newText) == true) {
line?.tb?.text = newText
updateSize()
} else runOnUiThread {
Toast.makeText(this, "失败", Toast.LENGTH_SHORT).show()
}
@@ -270,9 +314,6 @@ class MainActivity : AppCompatActivity() {
ta.text = delKey
tn.text = delKey
tb.text = delData
start--
end--
updateSize()
}
else runOnUiThread {
Toast.makeText(this, "失败", Toast.LENGTH_SHORT).show()
@@ -283,58 +324,83 @@ class MainActivity : AppCompatActivity() {
.show()
}
private fun showControlCard(completely: Boolean = false){
cctrl.sb.isEnabled = true
if(completely) {
cbcard.alpha = 0f
cbcard.visibility = View.VISIBLE
ObjectAnimator.ofFloat(cbcard, "alpha", 0f, 0.9f).setDuration(233).start()
return
}
ObjectAnimator.ofFloat(cbcard, "alpha", 0.3f, 0.9f).setDuration(233).start()
ObjectAnimator.ofFloat(cbcard, "translationX", cbcard.width.toFloat() * 0.9f, 0f).setDuration(233).start()
}
private fun hideControlCard(completely: Boolean = false){
cctrl.sb.isEnabled = false
if(completely) {
cbcard.visibility = View.GONE
return
}
ObjectAnimator.ofFloat(cbcard, "alpha", 0.9f, 0.3f).setDuration(233).start()
ObjectAnimator.ofFloat(cbcard, "translationX", 0f, cbcard.width.toFloat() * 0.9f).setDuration(233).start()
}
inner class SearchViewHolder(itemView: View) : ListViewHolder(itemView) {
inner class RecyclerViewAdapter : ListViewHolder.RecyclerViewAdapter() {
inner class RecyclerViewAdapter : ListViewHolder.RecyclerViewAdapter(visibleThreshold) {
override fun getKeys(filterText: CharSequence?) = filterText?.let { filter(it) }
override fun getValue(key: String) = dict?.get(key)
private fun filter(text: CharSequence): List<String> {
val selectSet = dict?.keys?.filter { it.contains(text, true) }?.toSet()?.plus(dict?.filterValues { it?.contains(text, true) ?: false }.let {
val newSet = mutableSetOf<String>()
it?.keys?.forEach {
newSet += it
return dict?.keys?.filter {
it.contains(text, true)
}?.toSet()?.plus(
dict?.filterValues {
it?.contains(text, true) ?: false
}.let {
val newSet = mutableSetOf<String>()
it?.keys?.forEach { k ->
newSet += k
}
newSet
}
newSet
})
return selectSet?.toList()?.let { if (it.size > 50) it.subList(0, 49) else it }?: emptyList()
)?.toList()?: emptyList()
}
}
}
inner class LikeViewHolder(itemView: View) : ListViewHolder(itemView) {
inner class RecyclerViewAdapter: ListViewHolder.RecyclerViewAdapter(true){
var capacity = 5
var offset = 0
override fun loadMore() {
if(offset+5<dict?.latestKeys?.size?:0) {
offset += 5
refresh()
inner class LikeViewHolder(itemView: View, private val onlyLike: Boolean) : ListViewHolder(itemView) {
inner class RecyclerViewAdapter: ListViewHolder.RecyclerViewAdapter(visibleThreshold+8) {
override fun getKeys(filterText: CharSequence?) = (
if(onlyLike) dictPreferences?.all?.keys?.let { keys ->
Log.d("MyMain", "LikeViewHolder getKeys like")
mControlBarStates[1].let { bar ->
bar.total = keys.size
bar.sort(keys.toList())
}
}
}
override fun loadLess() {
if(offset>=5) {
offset -= 5
refresh()
else dict?.latestKeys?.let { keys ->
Log.d("MyMain", "LikeViewHolder getKeys all, set size: ${keys.size}")
mControlBarStates[0].let { bar ->
bar.total = keys.size
bar.sort(keys.toList())
}
}
}
override fun getKeys(filterText: CharSequence?) = getSharedPreferences("dict", MODE_PRIVATE).all.keys.toTypedArray().let{
dict?.let { d ->
end = d.latestKeys.size - offset
start = if(end > capacity) end - capacity else 0
(it + d.latestKeys.copyOfRange(start, end).reversedArray()).toList()
}?: emptyList()
}
override fun getValue(key: String) = dict?.get(key)?:getSharedPreferences("dict", MODE_PRIVATE).getString(key, "null")
)?: emptyList()
override fun getValue(key: String) = dict?.get(key)?:dictPreferences?.getString(key, "null")?:"N/A"
}
}
open inner class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
open inner class RecyclerViewAdapter(private val showLoadMore: Boolean = false) :
val recyclerView: RecyclerView? = itemView as? RecyclerView
open inner class RecyclerViewAdapter(private val renderLinesCount: Int) :
RecyclerView.Adapter<ListViewHolder>() {
private var listKeys: List<String>? = null
private var index = 0
val dictPreferences: SharedPreferences? = getSharedPreferences("dict", MODE_PRIVATE)
var hasRefreshed = false
open fun getKeys(filterText: CharSequence? = null): List<String>? = null
open fun getValue(key: String): String? = null
open fun loadMore() {}
open fun loadLess() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
return ListViewHolder(
LayoutInflater.from(parent.context)
@@ -343,76 +409,189 @@ class MainActivity : AppCompatActivity() {
}
@SuppressLint("ClickableViewAccessibility", "SetTextI18n")
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
Log.d("MyMain", "Bind open at $position")
override fun onBindViewHolder(holder: ListViewHolder, p: Int) {
val position = p + index
Log.d("MyMain", "Bind open at $p($position)")
Thread{
listKeys?.apply {
if (position < size) {
val key = get(position)
val data = getValue(key)
val like = getSharedPreferences("dict", MODE_PRIVATE)?.contains(key) == true
Log.d("MyMain", "Like status of $key is $like")
holder.itemView.apply {
runOnUiThread {
ta.visibility = View.VISIBLE
lwclast.visibility = View.GONE
tn.text = key
ta.text = key
tb.text = data
vl.setBackgroundResource(if(like) R.drawable.ic_like_filled else R.drawable.ic_like)
Log.d("MyMain", "Set like of $key: $like")
setOnClickListener {
showDictAlert(key, data, this)
}
setOnLongClickListener {
cm?.setPrimaryClip(ClipData.newPlainText("SimpleDict", "$key\n$data"))
runOnUiThread {
Toast.makeText(this@MainActivity, "已复制", Toast.LENGTH_SHORT).show()
}
true
}
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()
}
}
}
}
} else if(showLoadMore && position == size) runOnUiThread{
holder.itemView.apply {
lastLikeLine = this
ta.visibility = View.GONE
lwclast.visibility = View.VISIBLE
tn.text = "motkyep..."
tb.text = "加载更多(长按返回上页)..."
updateSize()
if (position >= size) return@Thread
val key = get(position)
val data = getValue(key)
val like = dictPreferences?.contains(key) == true
//Log.d("MyMain", "Like status of $key is $like")
holder.itemView.apply {
runOnUiThread {
ta.visibility = View.VISIBLE
tn.text = key
ta.text = key
tb.text = data
vl.setBackgroundResource(if(like) R.drawable.ic_like_filled else R.drawable.ic_like)
//Log.d("MyMain", "Set like of $key: $like")
setOnClickListener {
loadMore()
showDictAlert(key, data, this)
}
setOnLongClickListener {
loadLess()
return@setOnLongClickListener true
cm?.setPrimaryClip(ClipData.newPlainText("SimpleDict", "$key\n$data"))
runOnUiThread {
Toast.makeText(this@MainActivity, R.string.toast_copied, Toast.LENGTH_SHORT).show()
}
true
}
vl.setOnClickListener {
dictPreferences?.apply {
if(contains(key)) {
edit { remove(key) }
it.setBackgroundResource(R.drawable.ic_like)
Log.d("MyMain", "unliked $key")
} else {
edit { putString(key, data) }
it.setBackgroundResource(R.drawable.ic_like_filled)
Log.d("MyMain", "liked $key")
}
}
}
}
}
}
if(recyclerView?.isComputingLayout == false) {
if(p >= itemCount-1) scrollDown(if(p < renderLinesCount) 4 else 1)
else if(p <= 1) scrollUp(if(p < renderLinesCount) 4 else 1)
}
}.start()
}
override fun getItemCount() = (listKeys?.size?:0) + (if(showLoadMore) 1 else 0)
override fun getItemCount() = (listKeys?.size?:0).let { if(it > renderLinesCount) renderLinesCount else it }
fun refresh(filterText: CharSequence? = null) = Thread{
@SuppressLint("NotifyDataSetChanged")
fun refresh(filterText: CharSequence? = null) {
index = 0
listKeys = getKeys(filterText)
runOnUiThread { notifyDataSetChanged() }
}.start()
notifyDataSetChanged()
hasRefreshed = true
}
@SuppressLint("NotifyDataSetChanged")
fun scrollDown(n: Int) {
if((listKeys?.size ?: 0) <= renderLinesCount) return
val oldIndex = index
val nextIndex = if(oldIndex + n + renderLinesCount > (listKeys?.size ?: 0)) (listKeys?.size ?: 0) - renderLinesCount else oldIndex + n
if (oldIndex == nextIndex) return
if(nextIndex < 0) return
index = nextIndex
if(n >= renderLinesCount) {
runOnUiThread { notifyDataSetChanged() }
return
}
// index next index
// +*************************
// +*************************
// ---remain--- ↑
// ----delete---- → → → → → ↗
val insert = nextIndex - oldIndex
runOnUiThread {
notifyItemRangeInserted(renderLinesCount, insert)
notifyItemRangeRemoved(0, insert)
}
}
@SuppressLint("NotifyDataSetChanged")
fun scrollUp(n: Int) {
if((listKeys?.size ?: 0) <= renderLinesCount) return
val oldIndex = index
val nextIndex = if(oldIndex-n >= 0) oldIndex-n else 0
if(oldIndex == nextIndex) return
index = nextIndex
if(n >= renderLinesCount) {
runOnUiThread { notifyDataSetChanged() }
return
}
val insert = oldIndex - nextIndex
runOnUiThread {
notifyItemRangeInserted(0, insert)
notifyItemRangeRemoved(renderLinesCount, insert)
}
}
fun getPosition() = index
@SuppressLint("NotifyDataSetChanged")
fun setProgress(p: Int) {
if(p > 100 || p < 0) return
var newIndex = p * (listKeys?.size?:0) / 100
if(newIndex + renderLinesCount > (listKeys?.size?:0)) {
newIndex = (listKeys?.size?:0) - renderLinesCount
if(newIndex < 0) newIndex = 0
}
val oldIndex = index
if (oldIndex == newIndex) return
val n = newIndex - oldIndex
if(n >= renderLinesCount || n <= -renderLinesCount) {
index = newIndex
runOnUiThread { notifyDataSetChanged() }
return
}
if(n > 0) scrollDown(n)
else scrollUp(-n)
}
}
}
}
inner class HandleOnViewCreated: MainFragment.HandleOnViewCreated {
override fun onPageSelected(position: Int) {
mViewPagerPosition = position
val ad = mVPAdapter.views[mViewPagerPosition]?.recyclerView?.adapter as? ListViewHolder.RecyclerViewAdapter
if(ad?.hasRefreshed == false) {
ad.refresh()
}
updateSize()
}
override fun onPageScrollStateChanged(state: Int) {
val ad = mVPAdapter.views[mViewPagerPosition]?.recyclerView?.adapter as? ListViewHolder.RecyclerViewAdapter
this@MainActivity.ffsw.isEnabled = state == ViewPager.SCROLL_STATE_IDLE && ad?.getPosition() == 0
Log.d("MyMain", "set ffsw enabled: ${this@MainActivity.ffsw.isEnabled}")
}
}
inner class HandleOnCreateView: InnerFragment.HandleOnCreateView {
override fun onCreateView(
p: Int,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val r = RecyclerView(inflater.context)
r.layoutParams = ViewGroup.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT)
val ad = LikeViewHolder(r, p == 1).RecyclerViewAdapter()
r.apply {
val lm = LinearLayoutManager(this@MainActivity)
layoutManager = lm
adapter = ad
addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val newStart = ad.getPosition()
val bar = mControlBarStates[p]
Log.d("MyMain", "new start: $newStart, index: ${bar.index}, sy: ${recyclerView?.scrollY}")
if (newStart != bar.index) {
bar.index = newStart
updateSize()
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val a = lm.findFirstVisibleItemPosition()
val b = lm.findLastVisibleItemPosition()
Log.d("MyMain", "new scroll state: $newState, a: $a, b: $b")
this@MainActivity.ffsw.isEnabled = newState == 0 && a == 0
val total = lm.itemCount
if(a <= 0) ad.scrollUp(1)
else if(b >= total-1) ad.scrollDown(1)
}
})
}
return r
}
}
}

View File

@@ -0,0 +1,73 @@
package top.fumiama.simpledict
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import kotlinx.android.synthetic.main.fragment_main.fmtab
import kotlinx.android.synthetic.main.fragment_main.fmvp
class MainFragment(private val mHandleOnViewCreated: HandleOnViewCreated, private val mHandleOnCreateView: InnerFragment.HandleOnCreateView): Fragment() {
constructor() : this(handleOnViewCreated!!, InnerFragment.handleOnCreateView!!)
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
interface HandleOnViewCreated {
fun onPageSelected(position: Int)
fun onPageScrollStateChanged(state: Int)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
fmvp.adapter = PagerAdapter(childFragmentManager)
fmvp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) { }
override fun onPageSelected(position: Int) {
Log.d("MyMF", "select page: $position")
mHandleOnViewCreated.onPageSelected(position)
}
override fun onPageScrollStateChanged(state: Int) {
Log.d("MyMF", "scroll state: $state, idle: ${ViewPager.SCROLL_STATE_IDLE}")
mHandleOnViewCreated.onPageScrollStateChanged(state)
}
})
fmtab.setupWithViewPager(fmvp)
}
inner class PagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
var views = arrayOf<InnerFragment?>(null, null)
override fun getCount(): Int = 2
override fun getItem(i: Int): Fragment {
if(views[i] != null) return views[i]!!
val f = InnerFragment(mHandleOnCreateView)
val b = Bundle()
b.putInt("p", i)
f.arguments = b
views[i] = f
return f
}
override fun getPageTitle(position: Int): CharSequence {
return getString(if(position == 0) R.string.tab_all_words else R.string.tab_liked_words)
}
}
companion object {
var handleOnViewCreated: HandleOnViewCreated? = null
}
}

View File

@@ -1,51 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/ffc"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.lapism.search.widget.MaterialSearchView
android:id="@+id/ffms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorTopBar"
app:layout_behavior="com.lapism.search.widget.SearchBehavior"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<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">
android:layout_height="0dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ffms">
<androidx.core.widget.NestedScrollView
android:id="@+id/ffns"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fffc"
android:layout_width="match_parent"
android:layout_height="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>
<com.google.android.material.appbar.AppBarLayout
<include
android:id="@+id/cctrl"
layout="@layout/card_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface">
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
app:layout_scrollFlags="scroll">
<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>
</androidx.core.widget.NestedScrollView>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<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="false">
<androidx.cardview.widget.CardView
android:id="@+id/cbcard"
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:alpha="0.9"
android:clickable="true"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<include
layout="@layout/line_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -20,6 +20,7 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/desc_image_decoration"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/bg_dere" />
@@ -32,9 +33,10 @@
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:text="请输入:服务器地址:端口_口令"
android:text="@string/alert_input_server_info"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:labelFor="@id/diet"
app:layout_constraintBottom_toTopOf="@+id/diet"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -43,15 +45,17 @@
<EditText
android:id="@+id/diet"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:ems="10"
android:hint="@string/alert_input_server_hint"
android:inputType="textPersonName"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/dit"
app:layout_constraintStart_toStartOf="@+id/dit"
app:layout_constraintTop_toBottomOf="@+id/dit" />
app:layout_constraintTop_toBottomOf="@+id/dit"
android:autofillHints="@string/alert_input_server_hint" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.viewpager.widget.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/fmvp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:id="@+id/fmtab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabTextAppearance="@style/TextAppearance.NisiTabText">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tab_all_words" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tab_liked_words" />
</com.google.android.material.tabs.TabLayout>
</androidx.viewpager.widget.ViewPager>

View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:clickable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/lbtindex"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:clickable="false"
android:fontFamily="@font/nisi"
android:text="@string/info_index_meter"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@+id/lbttotal"
app:layout_constraintEnd_toStartOf="@+id/imageView" />
<TextView
android:id="@+id/lbttotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:clickable="false"
android:fontFamily="@font/nisi"
android:text="@string/info_words_total"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/imageView" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:clickable="false"
android:contentDescription="@string/desc_image_decoration"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/bg_dere" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="false"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="16dp"
android:clickable="false"
android:fontFamily="@font/nisi"
android:text="@string/control_card_h1"
android:textColor="?attr/colorOnSurface"
android:textSize="30sp"
android:textStyle="bold" />
<SeekBar
android:id="@+id/sb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:clickable="false"
android:fontFamily="@font/gotham"
android:text="@string/control_card_h2"
android:textColor="?attr/colorOnSurface"
android:textSize="18sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="10dp"
android:clickable="false"
android:text="@string/control_card_h3" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -8,52 +8,7 @@
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/lwclast"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<TextView
android:id="@+id/fftc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:fontFamily="@font/nisi"
android:text="0"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@+id/fftt"
app:layout_constraintEnd_toStartOf="@+id/imageView" />
<TextView
android:id="@+id/fftt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:fontFamily="@font/nisi"
android:text="hv#st"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/imageView" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/bg_dere" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@@ -97,6 +52,7 @@
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:contentDescription="@string/desc_image_like"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FFBB86FC</color>
<color name="colorPrimaryVariant">#FF3700B3</color>
<color name="colorSecondary">#FF03DAC5</color>
<color name="colorSecondaryVariant">#03A9F4</color>
<color name="colorTopBar">#252424</color>
</resources>

View File

@@ -2,15 +2,12 @@
<!-- Base application theme. -->
<style name="Theme.SimpleDict" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariant</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="colorSecondary">@color/colorSecondary</item>
<item name="colorSecondaryVariant">@color/colorSecondaryVariant</item>
<item name="colorOnSecondary">@android:color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -1,10 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="colorPrimary">#BA8D08</color>
<color name="colorPrimaryVariant">#FF9800</color>
<color name="colorSecondary">#FF845E</color>
<color name="colorSecondaryVariant">#FF5722</color>
<color name="colorTopBar">#FFFFFF</color>
</resources>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="sort_type">
<item>修改时间(升)</item>
<item>修改时间(降)</item>
<item>字母顺序(升)</item>
<item>字母顺序(降)</item>
<item>长度(升)</item>
<item>长度(降)</item>
</string-array>
</resources>

View File

@@ -1,3 +1,21 @@
<resources>
<string name="app_name">SimpleDict</string>
<string name="info_index_meter">%1$d-%2$d</string>
<string name="info_words_total">%1$d rjimj</string>
<string name="control_card_h1">posena karakio</string>
<string name="control_card_h2">控制栏</string>
<string name="control_card_h3">点击显隐, 长按指定排序</string>
<string name="desc_image_decoration">装饰图</string>
<string name="desc_image_like">喜欢</string>
<string name="alert_input_server_info">请输入服务器信息</string>
<string name="alert_input_server_hint">服务器地址:端口_口令</string>
<string name="alert_select_sort_type">指定排序</string>
<string name="tab_all_words">zenbi</string>
<string name="tab_liked_words">eujuno</string>
<string name="toast_copied">已复制</string>
</resources>

View File

@@ -10,8 +10,6 @@
<item name="colorSecondaryVariant">@color/colorSecondaryVariant</item>
<item name="colorOnSecondary">@android:color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorSurface</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<!-- Customize your theme here. -->
</style>
@@ -26,4 +24,11 @@
<style name="Theme.SimpleDict.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.SimpleDict.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
<style name="TextAppearance.NisiTabText" parent="TextAppearance.Design.Tab">
<item name="android:textSize">26sp</item>
<item name="textAllCaps">false</item>
<item name="android:fontFamily">@font/nisi</item>
</style>
</resources>

View File

@@ -1,12 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.5.31'
ext.kotlin_version = '1.7.10'
repositories {
google()
jcenter()
mavenCentral()
mavenCentral()
maven { url 'https://maven.google.com' }
maven { url "https://jitpack.io" }
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.android.tools.build:gradle:8.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
@@ -18,6 +22,8 @@ allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
}

Binary file not shown.

View File

@@ -1,6 +1,7 @@
#Tue Feb 16 14:30:52 CST 2021
#Wed Dec 06 18:08:54 JST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip

286
gradlew vendored Normal file → Executable file
View File

@@ -1,78 +1,129 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -89,84 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

173
gradlew.bat vendored
View File

@@ -1,84 +1,89 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega