1
0
mirror of https://github.com/fumiama/simple-dict-android.git synced 2026-06-14 15:30:42 +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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,51 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/ffc"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.lapism.search.widget.MaterialSearchView
android:id="@+id/ffms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorTopBar"
app:layout_behavior="com.lapism.search.widget.SearchBehavior"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/ffsw"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
android:layout_height="0dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ffms">
<androidx.core.widget.NestedScrollView
android:id="@+id/ffns"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fffc"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/ffr"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.appbar.AppBarLayout
<include
android:id="@+id/cctrl"
layout="@layout/card_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface">
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
app:layout_scrollFlags="scroll">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorSurface"
app:toolbarId="@+id/toolbar">
<com.lapism.search.widget.MaterialSearchView
android:id="@+id/ffms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.lapism.search.widget.SearchBehavior" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</androidx.core.widget.NestedScrollView>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="false">
<androidx.cardview.widget.CardView
android:id="@+id/cbcard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:alpha="0.9"
android:clickable="true"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<include
layout="@layout/line_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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