fix #212, アプリ設定画面のListViewが過剰に更新されるのを防ぐ

This commit is contained in:
tateisu 2023-01-13 06:17:34 +09:00
parent 3df1fde938
commit b12b834d11
3 changed files with 310 additions and 284 deletions

View File

@ -18,15 +18,19 @@ import android.widget.*
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.jrummyapps.android.colorpicker.ColorPickerDialog import com.jrummyapps.android.colorpicker.ColorPickerDialog
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
import jp.juggler.subwaytooter.appsetting.AppDataExporter import jp.juggler.subwaytooter.appsetting.AppDataExporter
import jp.juggler.subwaytooter.appsetting.AppSettingItem import jp.juggler.subwaytooter.appsetting.AppSettingItem
import jp.juggler.subwaytooter.appsetting.SettingType import jp.juggler.subwaytooter.appsetting.SettingType
import jp.juggler.subwaytooter.appsetting.appSettingRoot import jp.juggler.subwaytooter.appsetting.appSettingRoot
import jp.juggler.subwaytooter.databinding.ActAppSettingBinding
import jp.juggler.subwaytooter.databinding.LvSettingItemBinding
import jp.juggler.subwaytooter.dialog.DlgAppPicker import jp.juggler.subwaytooter.dialog.DlgAppPicker
import jp.juggler.subwaytooter.notification.restartAllWorker import jp.juggler.subwaytooter.notification.restartAllWorker
import jp.juggler.subwaytooter.pref.impl.BooleanPref import jp.juggler.subwaytooter.pref.impl.BooleanPref
@ -47,8 +51,10 @@ import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.InputStream import java.io.InputStream
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
import java.lang.ref.WeakReference
import java.text.NumberFormat import java.text.NumberFormat
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
@ -79,9 +85,14 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
lateinit var pref: SharedPreferences lateinit var pref: SharedPreferences
lateinit var handler: Handler lateinit var handler: Handler
private lateinit var lvList: ListView
private lateinit var adapter: MyAdapter val views by lazy {
private lateinit var etSearch: EditText ActAppSettingBinding.inflate(layoutInflater)
}
private val adapter by lazy {
MyAdapter()
}
private val arNoop = ActivityResultHandler(log) { } private val arNoop = ActivityResultHandler(log) { }
@ -108,6 +119,18 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
} }
} }
private var pendingQuery: String? = null
private val procQuery: Runnable = Runnable {
if (pendingQuery != null) load(null, pendingQuery)
}
private val divider = Any()
private var lastSection: AppSettingItem? = null
private var lastQuery: String? = null
private var colorTarget: AppSettingItem? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
backPressed { backPressed {
@ -151,17 +174,15 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
} }
private fun initUi() { private fun initUi() {
setContentView(R.layout.act_app_setting) setContentView(views.root)
App1.initEdgeToEdge(this) App1.initEdgeToEdge(this)
Styler.fixHorizontalPadding0(findViewById(R.id.llContent)) Styler.fixHorizontalPadding0(views.llContent)
lvList = findViewById(R.id.lvList)
adapter = MyAdapter() views.lvList.layoutManager = LinearLayoutManager(this)
lvList.adapter = adapter views.lvList.adapter = adapter
etSearch = findViewById<EditText>(R.id.etSearch).apply { views.etSearch.addTextChangedListener(object : TextWatcher {
addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) { override fun afterTextChanged(p0: Editable?) {
pendingQuery = p0?.toString() pendingQuery = p0?.toString()
this@ActAppSetting.handler.removeCallbacks(procQuery) this@ActAppSetting.handler.removeCallbacks(procQuery)
@ -184,9 +205,8 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
) { ) {
} }
}) })
}
findViewById<View>(R.id.btnSearchReset).setOnClickListener(this@ActAppSetting) views.btnSearchReset.setOnClickListener(this)
} }
private fun removeDefaultPref() { private fun removeDefaultPref() {
@ -215,34 +235,18 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
when (v.id) { when (v.id) {
R.id.btnSearchReset -> { R.id.btnSearchReset -> {
handler.removeCallbacks(procQuery) handler.removeCallbacks(procQuery)
etSearch.setText("") views.etSearch.setText("")
etSearch.hideKeyboard() views.etSearch.hideKeyboard()
load(lastSection, null) load(lastSection, null)
} }
} }
} }
///////////////////////////////////////////////////////////////
private var pendingQuery: String? = null
private val procQuery: Runnable = Runnable {
if (pendingQuery != null) load(null, pendingQuery)
}
///////////////////////////////////////////////////////////////
private val divider = Any()
private val list = ArrayList<Any>()
private var lastSection: AppSettingItem? = null
private var lastQuery: String? = null
private fun load(section: AppSettingItem?, query: String?) { private fun load(section: AppSettingItem?, query: String?) {
list.clear() adapter.items = buildList {
var lastPath: String? = null var lastPath: String? = null
fun addParentPath(item: AppSettingItem) { fun addParentPath(item: AppSettingItem) {
list.add(divider) add(divider)
val pathList = ArrayList<String>() val pathList = ArrayList<String>()
var parent = item.parent var parent = item.parent
@ -253,12 +257,14 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
val path = pathList.joinToString("/") val path = pathList.joinToString("/")
if (path != lastPath) { if (path != lastPath) {
lastPath = path lastPath = path
list.add(path) add(path)
list.add(divider) add(divider)
} }
} }
if (query?.isNotEmpty() == true) { when {
// 検索キーワードあり
query?.isNotBlank() == true -> {
lastQuery = query lastQuery = query
fun scanGroup(level: Int, item: AppSettingItem) { fun scanGroup(level: Int, item: AppSettingItem) {
if (item.caption == 0) return if (item.caption == 0) return
@ -267,7 +273,9 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
if (item.type == SettingType.Group) { if (item.type == SettingType.Group) {
for (child in item.items) { for (child in item.items) {
if (child.caption == 0) continue if (child.caption == 0) continue
if (getString(item.caption).contains(query, ignoreCase = true)) { if (getString(item.caption).contains(query,
ignoreCase = true)
) {
match = true match = true
break break
} }
@ -275,17 +283,15 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
if (match) { if (match) {
// put entire group // put entire group
addParentPath(item) addParentPath(item)
list.add(item) add(item)
for (child in item.items) { addAll(item.items)
list.add(child)
}
} }
return return
} }
if (match) { if (match) {
addParentPath(item) addParentPath(item)
list.add(item) add(item)
} }
} }
for (child in item.items) { for (child in item.items) {
@ -293,31 +299,29 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
} }
} }
scanGroup(0, appSettingRoot) scanGroup(0, appSettingRoot)
if (list.isNotEmpty()) list.add(divider) }
} else if (section == null) {
// show root page // show root page
section == null -> {
val root = appSettingRoot val root = appSettingRoot
lastQuery = null lastQuery = null
lastSection = null lastSection = null
for (child in root.items) { for (child in root.items) {
list.add(divider) add(divider)
list.add(child) add(child)
}
} }
list.add(divider)
} else {
// show section page // show section page
else -> {
lastSection = section lastSection = section
lastQuery = null lastQuery = null
fun scanGroup(level: Int, parent: AppSettingItem?) { fun scanGroup(level: Int, parent: AppSettingItem?) {
parent ?: return parent ?: return
for (item in parent.items) { for (item in parent.items) {
list.add(divider) add(divider)
list.add(item) add(item)
if (item.items.isNotEmpty()) { if (item.items.isNotEmpty()) {
if (item.type == SettingType.Group) { if (item.type == SettingType.Group) {
for (child in item.items) { addAll(item.items)
list.add(child)
}
} else { } else {
scanGroup(level + 1, item) scanGroup(level + 1, item)
} }
@ -325,41 +329,11 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
} }
} }
scanGroup(0, section.cast()) scanGroup(0, section.cast())
if (list.isNotEmpty()) list.add(divider)
} }
adapter.notifyDataSetChanged()
lvList.setSelectionFromTop(0, 0)
} }
if (isNotEmpty()) add(divider)
inner class MyAdapter : BaseAdapter() {
override fun getCount(): Int = list.size
override fun getItemId(position: Int): Long = 0
override fun getItem(position: Int): Any = list[position]
override fun getViewTypeCount(): Int = SettingType.values().maxByOrNull { it.id }!!.id + 1
override fun getItemViewType(position: Int): Int =
when (val item = list[position]) {
is AppSettingItem -> item.type.id
is String -> SettingType.Path.id
divider -> SettingType.Divider.id
else -> error("can't generate view for type $item")
}
// true if the item at the specified position is not a separator.
// (A separator is a non-selectable, non-clickable item).
override fun areAllItemsEnabled(): Boolean = false
override fun isEnabled(position: Int): Boolean = list[position] is AppSettingItem
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View =
when (val item = list[position]) {
is AppSettingItem ->
getViewSettingItem(item, convertView, parent)
is String -> getViewPath(item, convertView)
divider -> getViewDivider(convertView)
else -> error("can't generate view for type $item")
} }
views.lvList.scrollToPosition(0)
} }
private fun dip(dp: Float): Int = private fun dip(dp: Float): Int =
@ -367,62 +341,6 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
private fun dip(dp: Int): Int = dip(dp.toFloat()) private fun dip(dp: Int): Int = dip(dp.toFloat())
private fun getViewDivider(convertView: View?): View =
convertView ?: FrameLayout(this@ActAppSetting).apply {
layoutParams = AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT,
AbsListView.LayoutParams.WRAP_CONTENT
)
addView(View(this@ActAppSetting).apply {
layoutParams = FrameLayout.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT,
dip(1)
).apply {
val marginX = 0
val marginY = dip(6)
setMargins(marginX, marginY, marginX, marginY)
}
setBackgroundColor(context.attrColor(R.attr.colorSettingDivider))
})
}
private fun getViewPath(path: String, convertView: View?): View {
val tv: MyTextView =
convertView.cast() ?: MyTextView(this@ActAppSetting).apply {
layoutParams = AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT,
AbsListView.LayoutParams.WRAP_CONTENT
)
val padX = 0
val padY = dip(3)
setTypeface(typeface, Typeface.BOLD)
setPaddingRelative(padX, padY, padX, padY)
}
tv.text = path
return tv
}
private fun getViewSettingItem(
item: AppSettingItem,
convertView: View?,
parent: ViewGroup?,
): View {
val view: View
val holder: ViewHolderSettingItem
if (convertView != null) {
view = convertView
holder = convertView.tag.cast()!!
} else {
view = layoutInflater.inflate(R.layout.lv_setting_item, parent, false)
holder = ViewHolderSettingItem(view)
view.tag = holder
}
holder.bind(item)
return view
}
private var colorTarget: AppSettingItem? = null
override fun onDialogDismissed(dialogId: Int) { override fun onDialogDismissed(dialogId: Int) {
} }
@ -438,40 +356,146 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
colorTarget.changed(this) colorTarget.changed(this)
} }
inner class ViewHolderSettingItem(viewRoot: View) : inner class MyAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var items: List<Any> = emptyList()
set(newItems) {
val oldItems = field
field = newItems
DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize() = oldItems.size
override fun getNewListSize() = newItems.size
override fun areItemsTheSame(
oldItemPosition: Int,
newItemPosition: Int,
) = oldItems.elementAtOrNull(oldItemPosition) == newItems.elementAtOrNull(
newItemPosition)
override fun areContentsTheSame(
oldItemPosition: Int,
newItemPosition: Int,
) = oldItems.elementAtOrNull(oldItemPosition) == newItems.elementAtOrNull(
newItemPosition)
}, true).dispatchUpdatesTo(this)
}
private val settingHolderList =
ConcurrentHashMap<AppSettingItem, WeakReference<VhSettingItem>>()
override fun getItemCount() = items.size
override fun getItemViewType(position: Int) =
when (val item = items.elementAtOrNull(position)) {
divider -> SettingType.Divider.id
is String -> SettingType.Path.id
is AppSettingItem -> item.type.id
else -> error("can't generate view for type $item")
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
when (SettingType.map[viewType]) {
SettingType.Divider -> VhDivider()
SettingType.Path -> VhPath(parent)
else -> VhSettingItem(this@ActAppSetting, parent)
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
when (val item = items.elementAtOrNull(position)) {
divider -> viewHolder.cast<VhDivider>()
is String -> viewHolder.cast<VhPath>()?.bind(item)
is AppSettingItem -> if (viewHolder is VhSettingItem) {
viewHolder.bind(item)
// 古い紐付けを削除
settingHolderList.entries.filter {
when (it.value.get()) {
null, viewHolder -> true
else -> false
}
}.forEach { settingHolderList.remove(it.key) }
// 新しい紐付けを覚える
settingHolderList[item] = WeakReference(viewHolder)
}
}
}
fun findVhSetting(item: AppSettingItem) = settingHolderList[item]?.get()
}
private inner class VhDivider(
viewRoot: FrameLayout = FrameLayout(this@ActAppSetting).apply {
layoutParams = RecyclerView.LayoutParams(
RecyclerView.LayoutParams.MATCH_PARENT,
RecyclerView.LayoutParams.WRAP_CONTENT
)
addView(View(this@ActAppSetting).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
dip(1)
).apply {
val marginX = 0
val marginY = dip(6)
setMargins(marginX, marginY, marginX, marginY)
}
setBackgroundColor(context.attrColor(R.attr.colorSettingDivider))
})
},
) : RecyclerView.ViewHolder(viewRoot)
private inner class VhPath(
val parent: ViewGroup,
val viewRoot: MyTextView = MyTextView(this@ActAppSetting).apply {
layoutParams = RecyclerView.LayoutParams(
RecyclerView.LayoutParams.MATCH_PARENT,
RecyclerView.LayoutParams.WRAP_CONTENT
)
val padX = 0
val padY = dip(3)
setTypeface(typeface, Typeface.BOLD)
setPaddingRelative(padX, padY, padX, padY)
},
) : RecyclerView.ViewHolder(viewRoot) {
fun bind(path: String) {
viewRoot.text = path
}
}
// not private
class VhSettingItem(
val activity: ActAppSetting,
parent: ViewGroup,
private val views: LvSettingItemBinding = LvSettingItemBinding
.inflate(activity.layoutInflater, parent, false),
) : RecyclerView.ViewHolder(views.root),
TextWatcher, TextWatcher,
AdapterView.OnItemSelectedListener, AdapterView.OnItemSelectedListener,
CompoundButton.OnCheckedChangeListener { CompoundButton.OnCheckedChangeListener {
private val tvCaption: TextView = viewRoot.findViewById(R.id.tvCaption) private val btnAction = views.btnAction
private val btnAction: Button = viewRoot.findViewById(R.id.btnAction)
private val checkBox: CheckBox = viewRoot.findViewById<CheckBox>(R.id.checkBox) private val checkBox = views.checkBox
.also { it.setOnCheckedChangeListener(this) } .also { it.setOnCheckedChangeListener(this) }
private val swSwitch: SwitchCompat = viewRoot.findViewById<SwitchCompat>(R.id.swSwitch) private val swSwitch = views.swSwitch
.also { it.setOnCheckedChangeListener(this) } .also { it.setOnCheckedChangeListener(this) }
val llExtra: LinearLayout = viewRoot.findViewById(R.id.llExtra) val llExtra = views.llExtra
val textView1: TextView = viewRoot.findViewById(R.id.textView1) val textView1 = views.textView1
private val llButtonBar: LinearLayout = viewRoot.findViewById(R.id.llButtonBar) private val llButtonBar = views.llButtonBar
private val vColor: View = viewRoot.findViewById(R.id.vColor) private val vColor = views.vColor
private val btnEdit: Button = viewRoot.findViewById(R.id.btnEdit) private val btnEdit = views.btnEdit
private val btnReset: Button = viewRoot.findViewById(R.id.btnReset) private val btnReset = views.btnReset
private val spSpinner: Spinner = viewRoot.findViewById<Spinner>(R.id.spSpinner) private val spSpinner = views.spSpinner
.also { it.onItemSelectedListener = this } .also { it.onItemSelectedListener = this }
private val etEditText: EditText = viewRoot.findViewById<EditText>(R.id.etEditText) private val etEditText = views.etEditText
.also { it.addTextChangedListener(this) } .also { it.addTextChangedListener(this) }
private val tvDesc: TextView = viewRoot.findViewById(R.id.tvDesc) private val tvDesc = views.tvDesc
private val tvError: TextView = viewRoot.findViewById(R.id.tvError) private val tvError = views.tvError
val activity: ActAppSetting private val pref = activity.pref
get() = this@ActAppSetting
var item: AppSettingItem? = null var item: AppSettingItem? = null
@ -482,7 +506,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
try { try {
this.item = item this.item = item
tvCaption.vg(false) views.tvCaption.vg(false)
btnAction.vg(false) btnAction.vg(false)
checkBox.vg(false) checkBox.vg(false)
swSwitch.vg(false) swSwitch.vg(false)
@ -495,11 +519,11 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
tvDesc.vg(false) tvDesc.vg(false)
tvError.vg(false) tvError.vg(false)
val name = if (item.caption == 0) "" else getString(item.caption) val name = if (item.caption == 0) "" else activity.getString(item.caption)
if (item.desc != 0) { if (item.desc != 0) {
tvDesc.vg(true) tvDesc.vg(true)
tvDesc.text = getString(item.desc) tvDesc.text = activity.getString(item.desc)
if (item.descClickSet) { if (item.descClickSet) {
tvDesc.background = ContextCompat.getDrawable( tvDesc.background = ContextCompat.getDrawable(
activity, activity,
@ -520,7 +544,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
btnAction.text = name btnAction.text = name
btnAction.isEnabledAlpha = item.enabled btnAction.isEnabledAlpha = item.enabled
btnAction.setOnClickListener { btnAction.setOnClickListener {
load(item.cast()!!, null) activity.load(item.cast()!!, null)
} }
} }
@ -539,7 +563,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
checkBox.vg(false) // skip animation checkBox.vg(false) // skip animation
checkBox.text = name checkBox.text = name
checkBox.isEnabledAlpha = item.enabled checkBox.isEnabledAlpha = item.enabled
checkBox.isChecked = bp(pref) checkBox.isChecked = bp(activity.pref)
checkBox.vg(true) checkBox.vg(true)
} }
@ -548,9 +572,9 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
item.pref.cast() ?: error("$name has no boolean pref") item.pref.cast() ?: error("$name has no boolean pref")
showCaption(name) showCaption(name)
swSwitch.vg(false) // skip animation swSwitch.vg(false) // skip animation
setSwitchColor(swSwitch) activity.setSwitchColor(swSwitch)
swSwitch.isEnabledAlpha = item.enabled swSwitch.isEnabledAlpha = item.enabled
swSwitch.isChecked = bp(pref) swSwitch.isChecked = bp(activity.pref)
swSwitch.vg(true) swSwitch.vg(true)
} }
@ -561,7 +585,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
SettingType.Sample -> { SettingType.Sample -> {
llExtra.vg(true) llExtra.vg(true)
llExtra.removeAllViews() llExtra.removeAllViews()
layoutInflater.inflate(item.sampleLayoutId, llExtra, true) activity.layoutInflater.inflate(item.sampleLayoutId, llExtra, true)
item.sampleUpdate(activity, llExtra) item.sampleUpdate(activity, llExtra)
} }
@ -570,12 +594,12 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
showCaption(name) showCaption(name)
llButtonBar.vg(true) llButtonBar.vg(true)
vColor.vg(true) vColor.vg(true)
vColor.setBackgroundColor(ip(pref)) vColor.setBackgroundColor(ip(activity.pref))
btnEdit.isEnabledAlpha = item.enabled btnEdit.isEnabledAlpha = item.enabled
btnReset.isEnabledAlpha = item.enabled btnReset.isEnabledAlpha = item.enabled
btnEdit.setOnClickListener { btnEdit.setOnClickListener {
colorTarget = item activity.colorTarget = item
val color = ip(pref) val color = ip(activity.pref)
val builder = ColorPickerDialog.newBuilder() val builder = ColorPickerDialog.newBuilder()
.setDialogType(ColorPickerDialog.TYPE_CUSTOM) .setDialogType(ColorPickerDialog.TYPE_CUSTOM)
.setAllowPresets(true) .setAllowPresets(true)
@ -585,7 +609,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
builder.show(activity) builder.show(activity)
} }
btnReset.setOnClickListener { btnReset.setOnClickListener {
pref.edit().remove(ip).apply() activity.pref.edit().remove(ip).apply()
showColor() showColor()
item.changed.invoke(activity) item.changed.invoke(activity)
} }
@ -602,11 +626,12 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
// 整数型の設定のSpinnerは全て選択肢を単純に覚える // 整数型の設定のSpinnerは全て選択肢を単純に覚える
val argsInt = item.spinnerArgs val argsInt = item.spinnerArgs
if (argsInt != null) { if (argsInt != null) {
initSpinner(spSpinner, argsInt.map { getString(it) }) activity.initSpinner(spSpinner,
argsInt.map { activity.getString(it) })
} else { } else {
initSpinner(spSpinner, item.spinnerArgsProc(activity)) activity.initSpinner(spSpinner, item.spinnerArgsProc(activity))
} }
spSpinner.setSelection(pi.invoke(pref)) spSpinner.setSelection(pi.invoke(activity.pref))
} else { } else {
item.spinnerInitializer.invoke(activity, spSpinner) item.spinnerInitializer.invoke(activity, spSpinner)
} }
@ -615,18 +640,13 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
SettingType.EditText -> { SettingType.EditText -> {
showCaption(name) showCaption(name)
etEditText.vg(true) etEditText.vg(true)
?: error("EditText must have preference.") ?: error("EditText must have preference.")
etEditText.inputType = item.inputType etEditText.inputType = item.inputType
val text = when (val pi = item.pref) { val text = when (val pi = item.pref) {
is FloatPref -> { is FloatPref ->
item.fromFloat.invoke(activity, pi(pref)) item.fromFloat.invoke(activity, pi(activity.pref))
} is StringPref ->
pi(activity.pref)
is StringPref -> {
pi(pref)
}
else -> error("EditText han incorrect pref $pi") else -> error("EditText han incorrect pref $pi")
} }
etEditText.setText(text) etEditText.setText(text)
@ -662,8 +682,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
private fun showCaption(caption: String) { private fun showCaption(caption: String) {
if (caption.isNotEmpty()) { if (caption.isNotEmpty()) {
tvCaption.vg(true) views.tvCaption.vg(true)?.text = caption
tvCaption.text = caption
updateCaption() updateCaption()
} }
} }
@ -672,16 +691,16 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
val item = item ?: return val item = item ?: return
val key = item.pref?.key ?: return val key = item.pref?.key ?: return
val sample: TextView = tvCaption val sample = views.tvCaption
var defaultExtra = defaultLineSpacingExtra[key] var defaultExtra = activity.defaultLineSpacingExtra[key]
if (defaultExtra == null) { if (defaultExtra == null) {
defaultExtra = sample.lineSpacingExtra defaultExtra = sample.lineSpacingExtra
defaultLineSpacingExtra[key] = defaultExtra activity.defaultLineSpacingExtra[key] = defaultExtra
} }
var defaultMultiplier = defaultLineSpacingMultiplier[key] var defaultMultiplier = activity.defaultLineSpacingMultiplier[key]
if (defaultMultiplier == null) { if (defaultMultiplier == null) {
defaultMultiplier = sample.lineSpacingMultiplier defaultMultiplier = sample.lineSpacingMultiplier
defaultLineSpacingMultiplier[key] = defaultMultiplier activity.defaultLineSpacingMultiplier[key] = defaultMultiplier
} }
val size = item.captionFontSize.invoke(activity) val size = item.captionFontSize.invoke(activity)
@ -705,7 +724,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
fun showColor() { fun showColor() {
val item = item ?: return val item = item ?: return
val ip = item.pref.cast<IntPref>() ?: return val ip = item.pref.cast<IntPref>() ?: return
val c = ip(pref) val c = ip(activity.pref)
vColor.setBackgroundColor(c) vColor.setBackgroundColor(c)
} }
@ -724,15 +743,15 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
when (val pi = item.pref) { when (val pi = item.pref) {
is StringPref -> { is StringPref -> {
pref.edit().put(pi, sv).apply() activity.pref.edit().put(pi, sv).apply()
} }
is FloatPref -> { is FloatPref -> {
val fv = item.toFloat.invoke(activity, sv) val fv = item.toFloat.invoke(activity, sv)
if (fv.isFinite()) { if (fv.isFinite()) {
pref.edit().put(pi, fv).apply() activity.pref.edit().put(pi, fv).apply()
} else { } else {
pref.edit().remove(pi.key).apply() activity.pref.edit().remove(pi.key).apply()
} }
} }
@ -756,7 +775,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
if (bindingBusy) return if (bindingBusy) return
val item = item ?: return val item = item ?: return
when (val pi = item.pref) { when (val pi = item.pref) {
is IntPref -> pref.edit().put(pi, spSpinner.selectedItemPosition).apply() is IntPref -> activity.pref.edit().put(pi, spSpinner.selectedItemPosition).apply()
else -> item.spinnerOnSelected.invoke(activity, spSpinner, position) else -> item.spinnerOnSelected.invoke(activity, spSpinner, position)
} }
item.changed.invoke(activity) item.changed.invoke(activity)
@ -865,13 +884,9 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
finish() finish()
} }
fun findItemViewHolder(item: AppSettingItem?): ViewHolderSettingItem? { fun findItemViewHolder(item: AppSettingItem?): VhSettingItem? {
if (item != null) { if (item != null) {
for (i in 0 until lvList.childCount) { adapter.findVhSetting(item)
val view = lvList.getChildAt(i)
val holder: ViewHolderSettingItem? = view?.tag?.cast()
if (holder?.item == item) return holder
}
} }
return null return null
} }
@ -884,8 +899,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
} }
// リスト内部のSwitchCompat全ての色を更新する // リスト内部のSwitchCompat全ての色を更新する
fun setSwitchColor() = fun setSwitchColor() = setSwitchColor(views.lvList)
setSwitchColor(lvList)
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
@ -914,8 +928,8 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
return Float.NaN return Float.NaN
} }
private val defaultLineSpacingExtra = HashMap<String, Float>() val defaultLineSpacingExtra = HashMap<String, Float>()
private val defaultLineSpacingMultiplier = HashMap<String, Float>() val defaultLineSpacingMultiplier = HashMap<String, Float>()
private fun handleFontResult(item: AppSettingItem?, data: Intent, fileName: String) { private fun handleFontResult(item: AppSettingItem?, data: Intent, fileName: String) {
item ?: error("handleFontResult : setting item is null") item ?: error("handleFontResult : setting item is null")

View File

@ -18,6 +18,7 @@ import jp.juggler.subwaytooter.util.CustomShareTarget
import jp.juggler.subwaytooter.util.openBrowser import jp.juggler.subwaytooter.util.openBrowser
import jp.juggler.util.* import jp.juggler.util.*
import org.jetbrains.anko.backgroundDrawable import org.jetbrains.anko.backgroundDrawable
import java.util.concurrent.atomic.AtomicInteger
enum class SettingType(val id: Int) { enum class SettingType(val id: Int) {
Path(0), Path(0),
@ -33,6 +34,12 @@ enum class SettingType(val id: Int) {
TextWithSelector(10), TextWithSelector(10),
CheckBox(11), CheckBox(11),
Section(12) Section(12)
;
companion object {
val map = values().associateBy { it.id }
}
} }
class AppSettingItem( class AppSettingItem(
@ -41,6 +48,7 @@ class AppSettingItem(
@StringRes val caption: Int, @StringRes val caption: Int,
val pref: BasePref<*>? = null, val pref: BasePref<*>? = null,
) { ) {
val id = idSeed.incrementAndGet()
@StringRes @StringRes
var desc: Int = 0 var desc: Int = 0
@ -191,7 +199,11 @@ class AppSettingItem(
for (item in items) item.scan(block) for (item in items) item.scan(block)
} }
override fun hashCode() = id
override fun equals(other: Any?) = (other as? AppSettingItem)?.id == this.id
companion object { companion object {
var idSeed = AtomicInteger(0)
var SAMPLE_CCD_HEADER: AppSettingItem? = null var SAMPLE_CCD_HEADER: AppSettingItem? = null
var SAMPLE_CCD_BODY: AppSettingItem? = null var SAMPLE_CCD_BODY: AppSettingItem? = null
@ -467,7 +479,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
spinner(PrefI.ipMediaBackground, R.string.background_pattern) { spinner(PrefI.ipMediaBackground, R.string.background_pattern) {
MediaBackgroundDrawable.Kind.values() MediaBackgroundDrawable.Kind.values()
.filter{it.isMediaBackground} .filter { it.isMediaBackground }
.map { it.name } .map { it.name }
} }

View File

@ -39,7 +39,7 @@
</LinearLayout> </LinearLayout>
<ListView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/lvList" android:id="@+id/lvList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"