diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 61a9130..b589d56 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 2c7e034..ae388c2 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -7,14 +7,13 @@
-
+
-
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index a5f05cd..4dcbce1 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -21,5 +21,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..b1077fb
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index e2af10a..3015725 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/app/build.gradle b/app/build.gradle
index 7084b23..67b4b8a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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'
}
\ No newline at end of file
diff --git a/app/src/main/java/top/fumiama/simpledict/ControlBarState.kt b/app/src/main/java/top/fumiama/simpledict/ControlBarState.kt
new file mode 100644
index 0000000..5fe70da
--- /dev/null
+++ b/app/src/main/java/top/fumiama/simpledict/ControlBarState.kt
@@ -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): List {
+ 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
+ }
+}
diff --git a/app/src/main/java/top/fumiama/simpledict/InnerFragment.kt b/app/src/main/java/top/fumiama/simpledict/InnerFragment.kt
new file mode 100644
index 0000000..b243bd8
--- /dev/null
+++ b/app/src/main/java/top/fumiama/simpledict/InnerFragment.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/top/fumiama/simpledict/MainActivity.kt b/app/src/main/java/top/fumiama/simpledict/MainActivity.kt
index 704455c..73e1c24 100644
--- a/app/src/main/java/top/fumiama/simpledict/MainActivity.kt
+++ b/app/src/main/java/top/fumiama/simpledict/MainActivity.kt
@@ -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(R.id.search_recycler_view)
- setAdapterLayoutManager(LinearLayoutManager(this@MainActivity))
+ val recyclerView = findViewById(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(R.id.search_image_view_mic)
+ val micView = findViewById(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 {
- val selectSet = dict?.keys?.filter { it.contains(text, true) }?.toSet()?.plus(dict?.filterValues { it?.contains(text, true) ?: false }.let {
- val newSet = mutableSetOf()
- 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()
+ 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
+ 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() {
private var listKeys: List? = null
+ private var index = 0
+ val dictPreferences: SharedPreferences? = getSharedPreferences("dict", MODE_PRIVATE)
+ var hasRefreshed = false
open fun getKeys(filterText: CharSequence? = null): List? = 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)
+ }
}
}
-}
\ No newline at end of file
+
+ 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
+ }
+ }
+}
diff --git a/app/src/main/java/top/fumiama/simpledict/MainFragment.kt b/app/src/main/java/top/fumiama/simpledict/MainFragment.kt
new file mode 100644
index 0000000..6655284
--- /dev/null
+++ b/app/src/main/java/top/fumiama/simpledict/MainFragment.kt
@@ -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(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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index eacb639..1804829 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,51 +1,44 @@
-
+
+
+ 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">
-
+ android:layout_height="match_parent"/>
-
-
-
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/card_bottom.xml b/app/src/main/res/layout/card_bottom.xml
new file mode 100644
index 0000000..fb4a181
--- /dev/null
+++ b/app/src/main/res/layout/card_bottom.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_input.xml b/app/src/main/res/layout/dialog_input.xml
index 4d23acc..9f825c4 100644
--- a/app/src/main/res/layout/dialog_input.xml
+++ b/app/src/main/res/layout/dialog_input.xml
@@ -20,6 +20,7 @@
@@ -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 @@
+ app:layout_constraintTop_toBottomOf="@+id/dit"
+ android:autofillHints="@string/alert_input_server_hint" />
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
new file mode 100644
index 0000000..6f3f654
--- /dev/null
+++ b/app/src/main/res/layout/fragment_main.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/line_bottom.xml b/app/src/main/res/layout/line_bottom.xml
new file mode 100644
index 0000000..c87ecc5
--- /dev/null
+++ b/app/src/main/res/layout/line_bottom.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/line_word.xml b/app/src/main/res/layout/line_word.xml
index 33404aa..98dd75d 100644
--- a/app/src/main/res/layout/line_word.xml
+++ b/app/src/main/res/layout/line_word.xml
@@ -8,52 +8,7 @@
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground">
-
-
-
-
-
-
-
-
-
@@ -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" />
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..de0bbc9
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,8 @@
+
+
+ #FFBB86FC
+ #FF3700B3
+ #FF03DAC5
+ #03A9F4
+ #252424
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 517f8e3..e532eb8 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -2,15 +2,12 @@
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 37fc22d..a9a298f 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,10 +1,8 @@
- #FFBB86FC
- #FF3700B3
- #FF03DAC5
#BA8D08
#FF9800
#FF845E
#FF5722
+ #FFFFFF
\ No newline at end of file
diff --git a/app/src/main/res/values/sort_type.xml b/app/src/main/res/values/sort_type.xml
new file mode 100644
index 0000000..ca21f82
--- /dev/null
+++ b/app/src/main/res/values/sort_type.xml
@@ -0,0 +1,11 @@
+
+
+
+ - 修改时间(升)
+ - 修改时间(降)
+ - 字母顺序(升)
+ - 字母顺序(降)
+ - 长度(升)
+ - 长度(降)
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 32425cb..1db6929 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,21 @@
SimpleDict
+
+ %1$d-%2$d
+ %1$d rjimj
+ posena karakio
+ 控制栏
+ 点击显隐, 长按指定排序
+
+ 装饰图
+ 喜欢
+
+ 请输入服务器信息
+ 服务器地址:端口_口令
+ 指定排序
+
+ zenbi
+ eujuno
+
+ 已复制
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 26925dd..6222c0c 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -10,8 +10,6 @@
- @color/colorSecondaryVariant
- @android:color/black
- - ?attr/colorSurface
- - @android:color/transparent
- true
@@ -26,4 +24,11 @@
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 24c5744..652c379 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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" }
}
}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index f6b961f..7454180 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ab3223b..9802d6d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -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
+
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
index cccdd3d..1b6c787
--- a/gradlew
+++ b/gradlew
@@ -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" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index f955316..ac1b06f 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -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