1
0
mirror of https://github.com/fumiama/simple-dict-android.git synced 2026-06-09 12:30:40 +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"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" /> <bytecodeTargetLevel target="17" />
</component> </component>
</project> </project>

3
.idea/gradle.xml generated
View File

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

View File

@@ -21,5 +21,15 @@
<option name="name" value="Google" /> <option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" /> <option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository> </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> </component>
</project> </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> </map>
</option> </option>
</component> </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" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -5,15 +5,13 @@ plugins {
} }
android { android {
compileSdkVersion 31
buildToolsVersion "30.0.2"
defaultConfig { defaultConfig {
compileSdk 34
applicationId "top.fumiama.simpledict" applicationId "top.fumiama.simpledict"
minSdkVersion 26 minSdkVersion 26
targetSdkVersion 31 targetSdkVersion 34
versionCode 19 versionCode 20
versionName '4.0' versionName '5.0.0'
resConfigs "zh", "zh-rCN" resConfigs "zh", "zh-rCN"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -39,19 +37,21 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '1.8'
} }
namespace 'top.fumiama.simpledict'
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.4.0' implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' implementation 'androidx.navigation:navigation-ui-ktx:2.7.5'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' 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.3' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'com.lapism:search:2.4.1@aar' 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 package top.fumiama.simpledict
import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.text.SpannableString import android.text.SpannableString
import android.text.Spanned import android.text.Spanned
import android.text.style.StrikethroughSpan import android.text.style.StrikethroughSpan
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.WindowManager
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.SeekBar
import android.widget.Toast 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.core.content.edit import androidx.core.content.edit
import androidx.core.view.children import androidx.core.view.children
import androidx.fragment.app.commit
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.lapism.search.internal.SearchLayout import com.lapism.search.internal.SearchLayout
import kotlinx.android.synthetic.main.activity_main.* 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.activity_main.view.*
import kotlinx.android.synthetic.main.card_bottom.cbcard
import kotlinx.android.synthetic.main.dialog_input.view.* 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.*
import kotlinx.android.synthetic.main.line_word.view.tb
import kotlinx.android.synthetic.main.line_word.view.tn
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.lang.Exception
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private val visibleThreshold = 16
private var host = "127.0.0.1" private var host = "127.0.0.1"
private var port = 80 private var port = 80
private var pwd = "demo" private var pwd = "demo"
private var spwd: String? = null private var spwd: String? = null
private var dict: SimpleDict? = null private var dict: SimpleDict? = null
private var hasLiked = false
private var cm: ClipboardManager? = null private var cm: ClipboardManager? = null
private var ad: LikeViewHolder.RecyclerViewAdapter? = null private var mViewPagerPosition = 0
private var lastLikeLine: View? = null private val mControlBarStates = arrayOf(ControlBarState(visibleThreshold+8), ControlBarState(visibleThreshold+8))
private var end = 0 private val mVPAdapter get() = fmvp.adapter as MainFragment.PagerAdapter
private var start = 0
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
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 ime = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
getSharedPreferences("remote", MODE_PRIVATE)?.apply { getSharedPreferences("remote", MODE_PRIVATE)?.apply {
if(contains("host")) getString("host", host)?.apply { host = this } if(contains("host")) getString("host", host)?.apply { host = this }
if(contains("port")) getInt("port", port).apply { port = 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 } if(contains("spwd")) getString("spwd", spwd)?.apply { spwd = this }
} }
dict = SimpleDict(Client(host, port), pwd, externalCacheDir, spwd) dict = SimpleDict(Client(host, port), pwd, externalCacheDir, spwd)
ad = LikeViewHolder(ffr).RecyclerViewAdapter()
cm = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager cm = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
ffr.apply {
layoutManager = LinearLayoutManager(this@MainActivity) if(savedInstanceState == null) {
adapter = ad MainFragment.handleOnViewCreated = HandleOnViewCreated()
setOnScrollChangeListener { _, _, scrollY, _, _ -> InnerFragment.handleOnCreateView = HandleOnCreateView()
this@MainActivity.ffsw.isEnabled = scrollY == 0 supportFragmentManager.commit {
setReorderingAllowed(true)
add(R.id.fffc, MainFragment())
} }
} }
@@ -81,20 +85,28 @@ class MainActivity : AppCompatActivity() {
} }
ffms.apply { ffms.apply {
val recyclerView = findViewById<RecyclerView>(R.id.search_recycler_view) val recyclerView = findViewById<RecyclerView>(com.lapism.search.R.id.search_recycler_view)
setAdapterLayoutManager(LinearLayoutManager(this@MainActivity)) val lm = LinearLayoutManager(this@MainActivity)
setAdapterLayoutManager(lm)
val adapter = SearchViewHolder(recyclerView).RecyclerViewAdapter() 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) setAdapter(adapter)
navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH
setMicIconImageResource(R.drawable.ic_setting) 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) setClearFocusOnBackPressed(true)
setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener { setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener {
override fun onNavigationClick(hasFocus: Boolean) { override fun onNavigationClick(hasFocus: Boolean) {
if (hasFocus()) { if (hasFocus()) clearFocus()
if(hasLiked) ad?.refresh()
clearFocus()
}
else requestFocus() else requestFocus()
} }
}) })
@@ -116,9 +128,9 @@ class MainActivity : AppCompatActivity() {
if(query.isNotEmpty()) { if(query.isNotEmpty()) {
val key = query.toString() val key = query.toString()
val data = dict?.get(key) val data = dict?.get(key)
showDictAlert(key, data, recyclerView.children.toList().let { showDictAlert(key, data, recyclerView.children.toList().let { children ->
val i = it.map { it.ta.text }.indexOf(key) val i = children.map { it.ta.text }.indexOf(key)
if(i >= 0) it[i] else null if(i >= 0) children[i] else null
}) })
} }
return true return true
@@ -169,44 +181,79 @@ class MainActivity : AppCompatActivity() {
setOnFocusChangeListener(object : SearchLayout.OnFocusChangeListener { setOnFocusChangeListener(object : SearchLayout.OnFocusChangeListener {
override fun onFocusChange(hasFocus: Boolean) { override fun onFocusChange(hasFocus: Boolean) {
navigationIconSupport = if (hasFocus) SearchLayout.NavigationIconSupport.ARROW navigationIconSupport = if (hasFocus) {
hideControlCard(true)
SearchLayout.NavigationIconSupport.ARROW
}
else { else {
micView.postDelayed({ micView.visibility = View.VISIBLE }, 233) micView.postDelayed({
micView.visibility = View.VISIBLE
showControlCard(true)
}, 233)
SearchLayout.NavigationIconSupport.SEARCH SearchLayout.NavigationIconSupport.SEARCH
} }
} }
}) })
}
this@MainActivity.ffc.setOnTouchListener { _, e -> var isSeeking = false
if (e.action == MotionEvent.ACTION_UP && mSearchEditText?.text?.isNotEmpty() == true) { cctrl.sb.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
ime.hideSoftInputFromWindow(window.decorView.windowToken, 0) 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 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() { private fun updateSize(updateSeekbar: Boolean = true) = runOnUiThread {
if(ffms.hasFocus()) { Log.d("MyMain", "update size, updateSeekbar: $updateSeekbar")
if(hasLiked) ad?.refresh() val bar = mControlBarStates[mViewPagerPosition]
} else super.onBackPressed() 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()
/*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 fetchThread(doWhenFinish: (()->Unit)? = null) { private fun fetchThread(doWhenFinish: (()->Unit)? = null) {
@@ -222,9 +269,8 @@ class MainActivity : AppCompatActivity() {
}) { }) {
runOnUiThread { runOnUiThread {
ffsw.isRefreshing = false ffsw.isRefreshing = false
ad?.capacity = 5 (mVPAdapter.views[mViewPagerPosition]?.recyclerView?.adapter as? ListViewHolder.RecyclerViewAdapter)?.refresh()
ad?.offset = 0 updateSize()
ad?.refresh()
doWhenFinish?.apply { this() } doWhenFinish?.apply { this() }
} }
} }
@@ -233,7 +279,6 @@ class MainActivity : AppCompatActivity() {
private fun showDictAlert(key: String, data: String?, line: View?) { private fun showDictAlert(key: String, data: String?, line: View?) {
val hintAdd = if(data != null && data != "null") "重设" else "添加" val hintAdd = if(data != null && data != "null") "重设" else "添加"
hasLiked = false
AlertDialog.Builder(this@MainActivity) AlertDialog.Builder(this@MainActivity)
.setTitle(key) .setTitle(key)
.setMessage(data) .setMessage(data)
@@ -250,7 +295,6 @@ class MainActivity : AppCompatActivity() {
val k = key.trim().replace(Regex("[\\uFF00-\\uFF5E]")) { (it.value[0] - 0xFEE0).toString() } val k = key.trim().replace(Regex("[\\uFF00-\\uFF5E]")) { (it.value[0] - 0xFEE0).toString() }
if(dict?.set(k, newText) == true) { if(dict?.set(k, newText) == true) {
line?.tb?.text = newText line?.tb?.text = newText
updateSize()
} else runOnUiThread { } else runOnUiThread {
Toast.makeText(this, "失败", Toast.LENGTH_SHORT).show() Toast.makeText(this, "失败", Toast.LENGTH_SHORT).show()
} }
@@ -270,9 +314,6 @@ class MainActivity : AppCompatActivity() {
ta.text = delKey ta.text = delKey
tn.text = delKey tn.text = delKey
tb.text = delData tb.text = delData
start--
end--
updateSize()
} }
else runOnUiThread { else runOnUiThread {
Toast.makeText(this, "失败", Toast.LENGTH_SHORT).show() Toast.makeText(this, "失败", Toast.LENGTH_SHORT).show()
@@ -283,58 +324,83 @@ class MainActivity : AppCompatActivity() {
.show() .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 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 getKeys(filterText: CharSequence?) = filterText?.let { filter(it) }
override fun getValue(key: String) = dict?.get(key) override fun getValue(key: String) = dict?.get(key)
private fun filter(text: CharSequence): List<String> { 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 { return dict?.keys?.filter {
val newSet = mutableSetOf<String>() it.contains(text, true)
it?.keys?.forEach { }?.toSet()?.plus(
newSet += it dict?.filterValues {
it?.contains(text, true) ?: false
}.let {
val newSet = mutableSetOf<String>()
it?.keys?.forEach { k ->
newSet += k
}
newSet
} }
newSet )?.toList()?: emptyList()
})
return selectSet?.toList()?.let { if (it.size > 50) it.subList(0, 49) else it }?: emptyList()
} }
} }
} }
inner class LikeViewHolder(itemView: View) : ListViewHolder(itemView) { inner class LikeViewHolder(itemView: View, private val onlyLike: Boolean) : ListViewHolder(itemView) {
inner class RecyclerViewAdapter: ListViewHolder.RecyclerViewAdapter(true){ inner class RecyclerViewAdapter: ListViewHolder.RecyclerViewAdapter(visibleThreshold+8) {
var capacity = 5 override fun getKeys(filterText: CharSequence?) = (
var offset = 0 if(onlyLike) dictPreferences?.all?.keys?.let { keys ->
override fun loadMore() { Log.d("MyMain", "LikeViewHolder getKeys like")
if(offset+5<dict?.latestKeys?.size?:0) { mControlBarStates[1].let { bar ->
offset += 5 bar.total = keys.size
refresh() bar.sort(keys.toList())
}
} }
} else dict?.latestKeys?.let { keys ->
override fun loadLess() { Log.d("MyMain", "LikeViewHolder getKeys all, set size: ${keys.size}")
if(offset>=5) { mControlBarStates[0].let { bar ->
offset -= 5 bar.total = keys.size
refresh() bar.sort(keys.toList())
}
} }
} )?: emptyList()
override fun getKeys(filterText: CharSequence?) = getSharedPreferences("dict", MODE_PRIVATE).all.keys.toTypedArray().let{ override fun getValue(key: String) = dict?.get(key)?:dictPreferences?.getString(key, "null")?:"N/A"
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")
} }
} }
open inner class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 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>() { RecyclerView.Adapter<ListViewHolder>() {
private var listKeys: List<String>? = null 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 getKeys(filterText: CharSequence? = null): List<String>? = null
open fun getValue(key: String): String? = null open fun getValue(key: String): String? = null
open fun loadMore() {}
open fun loadLess() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
return ListViewHolder( return ListViewHolder(
LayoutInflater.from(parent.context) LayoutInflater.from(parent.context)
@@ -343,76 +409,189 @@ class MainActivity : AppCompatActivity() {
} }
@SuppressLint("ClickableViewAccessibility", "SetTextI18n") @SuppressLint("ClickableViewAccessibility", "SetTextI18n")
override fun onBindViewHolder(holder: ListViewHolder, position: Int) { override fun onBindViewHolder(holder: ListViewHolder, p: Int) {
Log.d("MyMain", "Bind open at $position") val position = p + index
Log.d("MyMain", "Bind open at $p($position)")
Thread{ Thread{
listKeys?.apply { listKeys?.apply {
if (position < size) { if (position >= size) return@Thread
val key = get(position) val key = get(position)
val data = getValue(key) val data = getValue(key)
val like = getSharedPreferences("dict", MODE_PRIVATE)?.contains(key) == true val like = dictPreferences?.contains(key) == true
Log.d("MyMain", "Like status of $key is $like") //Log.d("MyMain", "Like status of $key is $like")
holder.itemView.apply { holder.itemView.apply {
runOnUiThread { runOnUiThread {
ta.visibility = View.VISIBLE ta.visibility = View.VISIBLE
lwclast.visibility = View.GONE tn.text = key
tn.text = key ta.text = key
ta.text = key tb.text = data
tb.text = data vl.setBackgroundResource(if(like) R.drawable.ic_like_filled else R.drawable.ic_like)
vl.setBackgroundResource(if(like) R.drawable.ic_like_filled else R.drawable.ic_like) //Log.d("MyMain", "Set like of $key: $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()
setOnClickListener { setOnClickListener {
loadMore() showDictAlert(key, data, this)
} }
setOnLongClickListener { setOnLongClickListener {
loadLess() cm?.setPrimaryClip(ClipData.newPlainText("SimpleDict", "$key\n$data"))
return@setOnLongClickListener true 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() }.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) listKeys = getKeys(filterText)
runOnUiThread { notifyDataSetChanged() } notifyDataSetChanged()
}.start() 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"?> <?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" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/ffc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/ffsw" android:id="@+id/ffsw"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> 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 <androidx.fragment.app.FragmentContainerView
android:id="@+id/ffns" android:id="@+id/fffc"
android:layout_width="match_parent" 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> </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_width="match_parent"
android:layout_height="wrap_content" 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 </androidx.constraintlayout.widget.ConstraintLayout>
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>

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 <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/desc_image_decoration"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/bg_dere" /> app:srcCompat="@drawable/bg_dere" />
@@ -32,9 +33,10 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:text="请输入:服务器地址:端口_口令" android:text="@string/alert_input_server_info"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp" android:textSize="16sp"
android:labelFor="@id/diet"
app:layout_constraintBottom_toTopOf="@+id/diet" app:layout_constraintBottom_toTopOf="@+id/diet"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -43,15 +45,17 @@
<EditText <EditText
android:id="@+id/diet" android:id="@+id/diet"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="48dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:ems="10" android:ems="10"
android:hint="@string/alert_input_server_hint"
android:inputType="textPersonName" android:inputType="textPersonName"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/dit" app:layout_constraintEnd_toEndOf="@+id/dit"
app:layout_constraintStart_toStartOf="@+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> </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:focusable="true"
android:foreground="?android:attr/selectableItemBackground"> 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 <LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
@@ -97,6 +52,7 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:foreground="?android:attr/selectableItemBackground" android:foreground="?android:attr/selectableItemBackground"
android:contentDescription="@string/desc_image_like"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="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. --> <!-- Base application theme. -->
<style name="Theme.SimpleDict" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.SimpleDict" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/colorPrimaryVariant</item>
<item name="colorOnPrimary">@android:color/black</item> <item name="colorOnPrimary">@android:color/black</item>
<!-- Secondary brand color. --> <!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item> <item name="colorSecondary">@color/colorSecondary</item>
<item name="colorSecondaryVariant">@color/teal_200</item> <item name="colorSecondaryVariant">@color/colorSecondaryVariant</item>
<item name="colorOnSecondary">@android:color/black</item> <item name="colorOnSecondary">@android:color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style> </style>
</resources> </resources>

View File

@@ -1,10 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <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="colorPrimary">#BA8D08</color>
<color name="colorPrimaryVariant">#FF9800</color> <color name="colorPrimaryVariant">#FF9800</color>
<color name="colorSecondary">#FF845E</color> <color name="colorSecondary">#FF845E</color>
<color name="colorSecondaryVariant">#FF5722</color> <color name="colorSecondaryVariant">#FF5722</color>
<color name="colorTopBar">#FFFFFF</color>
</resources> </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> <resources>
<string name="app_name">SimpleDict</string> <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> </resources>

View File

@@ -10,8 +10,6 @@
<item name="colorSecondaryVariant">@color/colorSecondaryVariant</item> <item name="colorSecondaryVariant">@color/colorSecondaryVariant</item>
<item name="colorOnSecondary">@android:color/black</item> <item name="colorOnSecondary">@android:color/black</item>
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorSurface</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item> <item name="android:windowLightStatusBar">true</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
</style> </style>
@@ -26,4 +24,11 @@
<style name="Theme.SimpleDict.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> <style name="Theme.SimpleDict.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.SimpleDict.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> <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> </resources>

View File

@@ -1,12 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.5.31' ext.kotlin_version = '1.7.10'
repositories { repositories {
google() google()
jcenter() jcenter()
mavenCentral()
mavenCentral()
maven { url 'https://maven.google.com' }
maven { url "https://jitpack.io" }
} }
dependencies { 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" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
@@ -18,6 +22,8 @@ allprojects {
repositories { repositories {
google() google()
jcenter() 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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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 # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" 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. # 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 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." location of your Java installation."
fi fi
else 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. 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 Please set the JAVA_HOME variable in your environment to match the
@@ -89,84 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
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" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=$(save "$@") # * --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 # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 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 JAVACMD=$( cygpath --unix "$JAVACMD" )
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")" # 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 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" "$@" exec "$JAVACMD" "$@"

173
gradlew.bat vendored
View File

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