2019-12-13 15:48:38 +01:00
|
|
|
package jp.juggler.subwaytooter
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.content.Intent
|
|
|
|
import android.net.Uri
|
|
|
|
import android.os.Bundle
|
2020-01-31 18:37:39 +01:00
|
|
|
import android.os.PersistableBundle
|
2019-12-13 15:48:38 +01:00
|
|
|
import android.os.Process
|
|
|
|
import android.text.Editable
|
|
|
|
import android.text.TextWatcher
|
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewGroup
|
|
|
|
import android.widget.*
|
|
|
|
import androidx.appcompat.app.AlertDialog
|
2021-05-27 04:15:59 +02:00
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
2019-12-13 15:48:38 +01:00
|
|
|
import androidx.core.content.FileProvider
|
|
|
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
|
|
|
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
|
|
|
import jp.juggler.util.*
|
|
|
|
import org.apache.commons.io.IOUtils
|
|
|
|
import org.jetbrains.anko.textColor
|
|
|
|
import java.io.ByteArrayOutputStream
|
|
|
|
import java.io.File
|
|
|
|
import java.io.FileOutputStream
|
|
|
|
import java.util.*
|
|
|
|
import kotlin.collections.ArrayList
|
|
|
|
|
2021-05-27 04:15:59 +02:00
|
|
|
class ActLanguageFilter : AppCompatActivity(), View.OnClickListener {
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
private class MyItem(
|
|
|
|
val code: String,
|
|
|
|
var allow: Boolean
|
|
|
|
)
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
internal val log = LogCategory("ActLanguageFilter")
|
|
|
|
|
|
|
|
internal const val EXTRA_COLUMN_INDEX = "column_index"
|
|
|
|
private const val STATE_LANGUAGE_LIST = "language_list"
|
|
|
|
|
|
|
|
fun createIntent(activity: ActMain, idx: Int) =
|
|
|
|
Intent(activity, ActLanguageFilter::class.java).apply {
|
|
|
|
putExtra(EXTRA_COLUMN_INDEX, idx)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private val languageComparator = Comparator<MyItem> { a, b ->
|
|
|
|
when {
|
|
|
|
a.code == TootStatus.LANGUAGE_CODE_DEFAULT -> -1
|
|
|
|
b.code == TootStatus.LANGUAGE_CODE_DEFAULT -> 1
|
|
|
|
a.code == TootStatus.LANGUAGE_CODE_UNKNOWN -> -1
|
|
|
|
b.code == TootStatus.LANGUAGE_CODE_UNKNOWN -> 1
|
|
|
|
else -> a.code.compareTo(b.code)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun equalsLanguageList(a: JsonObject?, b: JsonObject?): Boolean {
|
|
|
|
fun JsonObject.encodeToString(): String {
|
|
|
|
val clone = this.toString().decodeJsonObject()
|
|
|
|
if (!clone.contains(TootStatus.LANGUAGE_CODE_DEFAULT)) {
|
|
|
|
clone[TootStatus.LANGUAGE_CODE_DEFAULT] = true
|
|
|
|
}
|
|
|
|
return clone.keys.sorted().joinToString(",") { "$it=${this[it]}" }
|
|
|
|
}
|
|
|
|
|
|
|
|
val a_sign = (a ?: JsonObject()).encodeToString()
|
|
|
|
val b_sign = (b ?: JsonObject()).encodeToString()
|
|
|
|
return a_sign == b_sign
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private val languageNameMap by lazy {
|
|
|
|
HashMap<String, String>().apply {
|
|
|
|
|
|
|
|
// from https://github.com/google/cld3/blob/master/src/task_context_params.cc#L43
|
|
|
|
val languageNamesCld3 = arrayOf(
|
|
|
|
"eo", "co", "eu", "ta", "de", "mt", "ps", "te", "su", "uz", "zh-Latn", "ne",
|
|
|
|
"nl", "sw", "sq", "hmn", "ja", "no", "mn", "so", "ko", "kk", "sl", "ig",
|
|
|
|
"mr", "th", "zu", "ml", "hr", "bs", "lo", "sd", "cy", "hy", "uk", "pt",
|
|
|
|
"lv", "iw", "cs", "vi", "jv", "be", "km", "mk", "tr", "fy", "am", "zh",
|
|
|
|
"da", "sv", "fi", "ht", "af", "la", "id", "fil", "sm", "ca", "el", "ka",
|
|
|
|
"sr", "it", "sk", "ru", "ru-Latn", "bg", "ny", "fa", "haw", "gl", "et",
|
|
|
|
"ms", "gd", "bg-Latn", "ha", "is", "ur", "mi", "hi", "bn", "hi-Latn", "fr",
|
|
|
|
"yi", "hu", "xh", "my", "tg", "ro", "ar", "lb", "el-Latn", "st", "ceb",
|
|
|
|
"kn", "az", "si", "ky", "mg", "en", "gu", "es", "pl", "ja-Latn", "ga", "lt",
|
|
|
|
"sn", "yo", "pa", "ku"
|
|
|
|
)
|
|
|
|
|
|
|
|
for (src1 in languageNamesCld3) {
|
|
|
|
val src2 = src1.replace("-Latn", "")
|
|
|
|
val isLatn = src2 != src1
|
|
|
|
val locale = Locale(src2)
|
|
|
|
log.w("languageNameMap $src1 ${locale.language} ${locale.country} ${locale.displayName}")
|
|
|
|
put(
|
|
|
|
src1, if (isLatn) {
|
|
|
|
"${locale.displayName}(Latn)"
|
|
|
|
} else {
|
|
|
|
locale.displayName
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
put(TootStatus.LANGUAGE_CODE_DEFAULT, getString(R.string.language_code_default))
|
|
|
|
put(TootStatus.LANGUAGE_CODE_UNKNOWN, getString(R.string.language_code_unknown))
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getDesc(item: MyItem): String {
|
|
|
|
val code = item.code
|
|
|
|
return languageNameMap[code] ?: getString(R.string.custom)
|
|
|
|
}
|
|
|
|
|
|
|
|
private var column_index: Int = 0
|
|
|
|
internal lateinit var column: Column
|
|
|
|
internal lateinit var app_state: AppState
|
|
|
|
internal var density: Float = 0f
|
|
|
|
|
|
|
|
private lateinit var listView: ListView
|
|
|
|
private lateinit var adapter: MyAdapter
|
|
|
|
private val languageList = ArrayList<MyItem>()
|
|
|
|
private var loading_busy: Boolean = false
|
|
|
|
|
|
|
|
private val arExport = activityResultHandler { }
|
|
|
|
|
|
|
|
private val arImport = activityResultHandler { ar ->
|
|
|
|
val data = ar?.data
|
|
|
|
if (data != null && ar.resultCode == RESULT_OK) {
|
|
|
|
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
|
|
|
|
import2(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
arExport.register(this, log)
|
|
|
|
arImport.register(this, log)
|
|
|
|
|
|
|
|
App1.setActivityTheme(this)
|
|
|
|
initUI()
|
|
|
|
|
|
|
|
app_state = App1.getAppState(this)
|
|
|
|
density = app_state.density
|
|
|
|
column_index = intent.getIntExtra(EXTRA_COLUMN_INDEX, 0)
|
|
|
|
column = app_state.column(column_index)!!
|
|
|
|
|
|
|
|
if (savedInstanceState != null) {
|
|
|
|
try {
|
|
|
|
val sv = savedInstanceState.getString(STATE_LANGUAGE_LIST, null)
|
|
|
|
if (sv != null) {
|
|
|
|
val list = sv.decodeJsonObject()
|
|
|
|
load(list)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} catch (ex: Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
load(column.language_filter)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
|
|
|
|
super.onSaveInstanceState(outState, outPersistentState)
|
|
|
|
outState.putString(STATE_LANGUAGE_LIST, encodeLanguageList().toString())
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onBackPressed() {
|
|
|
|
if (!equalsLanguageList(column.language_filter, encodeLanguageList())) {
|
|
|
|
AlertDialog.Builder(this)
|
|
|
|
.setMessage(R.string.language_filter_quit_waring)
|
|
|
|
.setPositiveButton(R.string.ok) { _, _ -> finish() }
|
|
|
|
.setNegativeButton(R.string.cancel, null)
|
|
|
|
.show()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
super.onBackPressed()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun initUI() {
|
|
|
|
setContentView(R.layout.act_language_filter)
|
|
|
|
App1.initEdgeToEdge(this)
|
|
|
|
Styler.fixHorizontalPadding(findViewById(R.id.llContent))
|
|
|
|
|
|
|
|
for (id in intArrayOf(
|
|
|
|
R.id.btnAdd,
|
|
|
|
R.id.btnSave,
|
|
|
|
R.id.btnMore
|
|
|
|
)) {
|
|
|
|
findViewById<View>(id)?.setOnClickListener(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
listView = findViewById(R.id.listView)
|
|
|
|
adapter = MyAdapter()
|
|
|
|
listView.adapter = adapter
|
|
|
|
listView.onItemClickListener = adapter
|
|
|
|
}
|
|
|
|
|
|
|
|
// UIのデータをJsonObjectにエンコード
|
|
|
|
private fun encodeLanguageList() = jsonObject {
|
|
|
|
for (item in languageList) {
|
|
|
|
put(item.code, item.allow)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun load(src: JsonObject?) {
|
|
|
|
loading_busy = true
|
|
|
|
try {
|
|
|
|
languageList.clear()
|
|
|
|
|
|
|
|
if (src != null) {
|
|
|
|
for (key in src.keys) {
|
|
|
|
languageList.add(MyItem(key, src.boolean(key) ?: true))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (null == languageList.find { it.code == TootStatus.LANGUAGE_CODE_DEFAULT }) {
|
|
|
|
languageList.add(MyItem(TootStatus.LANGUAGE_CODE_DEFAULT, true))
|
|
|
|
}
|
|
|
|
|
|
|
|
languageList.sortWith(languageComparator)
|
|
|
|
|
|
|
|
adapter.notifyDataSetChanged()
|
|
|
|
} finally {
|
|
|
|
loading_busy = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun save() {
|
|
|
|
column.language_filter = encodeLanguageList()
|
|
|
|
}
|
|
|
|
|
|
|
|
private inner class MyAdapter : BaseAdapter(), AdapterView.OnItemClickListener {
|
|
|
|
|
|
|
|
override fun getCount(): Int = languageList.size
|
|
|
|
override fun getItemId(idx: Int): Long = 0L
|
|
|
|
override fun getItem(idx: Int): Any = languageList[idx]
|
|
|
|
|
|
|
|
override fun getView(idx: Int, viewArg: View?, parent: ViewGroup?): View {
|
|
|
|
val tv = (viewArg ?: layoutInflater.inflate(
|
|
|
|
R.layout.lv_language_filter,
|
|
|
|
parent,
|
|
|
|
false
|
|
|
|
)) as TextView
|
|
|
|
val item = languageList[idx]
|
|
|
|
tv.text = String.format(
|
|
|
|
"%s %s : %s",
|
|
|
|
item.code,
|
|
|
|
getDesc(item),
|
|
|
|
getString(if (item.allow) R.string.language_show else R.string.language_hide)
|
|
|
|
)
|
|
|
|
tv.textColor = attrColor(
|
|
|
|
when (item.allow) {
|
|
|
|
true -> R.attr.colorContentText
|
|
|
|
false -> R.attr.colorRegexFilterError
|
|
|
|
}
|
|
|
|
)
|
|
|
|
return tv
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onItemClick(parent: AdapterView<*>?, viewArg: View?, idx: Int, id: Long) {
|
|
|
|
if (idx in languageList.indices) edit(languageList[idx])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onClick(v: View) {
|
|
|
|
when (v.id) {
|
|
|
|
R.id.btnSave -> {
|
|
|
|
save()
|
|
|
|
val data = Intent()
|
|
|
|
data.putExtra(EXTRA_COLUMN_INDEX, column_index)
|
|
|
|
setResult(RESULT_OK, data)
|
|
|
|
finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
R.id.btnAdd -> edit(null)
|
|
|
|
|
|
|
|
R.id.btnMore -> {
|
|
|
|
ActionsDialog()
|
|
|
|
.addAction(getString(R.string.clear_all)) {
|
|
|
|
languageList.clear()
|
|
|
|
languageList.add(MyItem(TootStatus.LANGUAGE_CODE_DEFAULT, true))
|
|
|
|
adapter.notifyDataSetChanged()
|
|
|
|
}
|
|
|
|
.addAction(getString(R.string.export)) { export() }
|
|
|
|
.addAction(getString(R.string.import_)) { import() }
|
|
|
|
.show(this)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun edit(myItem: MyItem?) =
|
|
|
|
DlgLanguageFilter.open(this, myItem, object : DlgLanguageFilter.Callback {
|
|
|
|
override fun onOK(code: String, allow: Boolean) {
|
|
|
|
val it = languageList.iterator()
|
|
|
|
while (it.hasNext()) {
|
|
|
|
val item = it.next()
|
|
|
|
if (item.code == code) {
|
|
|
|
item.allow = allow
|
|
|
|
adapter.notifyDataSetChanged()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
languageList.add(MyItem(code, allow))
|
|
|
|
languageList.sortWith(languageComparator)
|
|
|
|
adapter.notifyDataSetChanged()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDelete(code: String) {
|
|
|
|
val it = languageList.iterator()
|
|
|
|
while (it.hasNext()) {
|
|
|
|
val item = it.next()
|
|
|
|
if (item.code == code) it.remove()
|
|
|
|
}
|
|
|
|
adapter.notifyDataSetChanged()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
private object DlgLanguageFilter {
|
|
|
|
|
|
|
|
interface Callback {
|
|
|
|
|
|
|
|
fun onOK(code: String, allow: Boolean)
|
|
|
|
fun onDelete(code: String)
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressLint("InflateParams")
|
|
|
|
fun open(activity: ActLanguageFilter, item: MyItem?, callback: Callback) {
|
|
|
|
|
|
|
|
val view = activity.layoutInflater.inflate(R.layout.dlg_language_filter, null, false)
|
|
|
|
|
|
|
|
val etLanguage: EditText = view.findViewById(R.id.etLanguage)
|
|
|
|
val btnPresets: ImageButton = view.findViewById(R.id.btnPresets)
|
|
|
|
val tvLanguage: TextView = view.findViewById(R.id.tvLanguage)
|
|
|
|
|
|
|
|
val rbShow: RadioButton = view.findViewById(R.id.rbShow)
|
|
|
|
val rbHide: RadioButton = view.findViewById(R.id.rbHide)
|
|
|
|
|
|
|
|
when (item?.allow ?: true) {
|
|
|
|
true -> rbShow.isChecked = true
|
|
|
|
else -> rbHide.isChecked = true
|
|
|
|
}
|
|
|
|
|
|
|
|
fun updateDesc() {
|
|
|
|
val code = etLanguage.text.toString().trim()
|
|
|
|
val desc = activity.languageNameMap[code] ?: activity.getString(R.string.custom)
|
|
|
|
tvLanguage.text = desc
|
|
|
|
}
|
|
|
|
|
|
|
|
val languageList =
|
|
|
|
activity.languageNameMap.map { MyItem(it.key, true) }.sortedWith(languageComparator)
|
|
|
|
btnPresets.setOnClickListener {
|
|
|
|
val ad = ActionsDialog()
|
|
|
|
for (a in languageList) {
|
|
|
|
ad.addAction("${a.code} ${activity.getDesc(a)}") {
|
|
|
|
etLanguage.setText(a.code)
|
|
|
|
updateDesc()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ad.show(activity, activity.getString(R.string.presets))
|
|
|
|
}
|
|
|
|
|
|
|
|
etLanguage.setText(item?.code ?: "")
|
|
|
|
updateDesc()
|
|
|
|
|
|
|
|
etLanguage.addTextChangedListener(object : TextWatcher {
|
|
|
|
override fun afterTextChanged(p0: Editable?) {
|
|
|
|
updateDesc()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
if (item != null) {
|
2021-05-22 11:07:23 +02:00
|
|
|
etLanguage.isEnabledAlpha = false
|
2021-05-22 00:03:16 +02:00
|
|
|
btnPresets.setEnabledColor(
|
|
|
|
activity,
|
|
|
|
R.drawable.ic_edit,
|
|
|
|
activity.attrColor(R.attr.colorVectorDrawable),
|
|
|
|
false
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
val builder = AlertDialog.Builder(activity)
|
|
|
|
.setView(view)
|
|
|
|
.setCancelable(true)
|
|
|
|
.setNegativeButton(R.string.cancel, null)
|
|
|
|
.setPositiveButton(R.string.ok) { _, _ ->
|
|
|
|
callback.onOK(etLanguage.text.toString().trim(), rbShow.isChecked)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item != null && item.code != TootStatus.LANGUAGE_CODE_DEFAULT) {
|
|
|
|
builder.setNeutralButton(R.string.delete) { _, _ ->
|
|
|
|
callback.onDelete(etLanguage.text.toString().trim())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
builder.show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("BlockingMethodInNonBlockingContext")
|
2021-05-27 04:15:59 +02:00
|
|
|
private fun export() {
|
|
|
|
launchMain {
|
|
|
|
runWithProgress(
|
|
|
|
"export language filter",
|
|
|
|
doInBackground = {
|
|
|
|
val data = JsonObject().apply {
|
|
|
|
for (item in languageList) {
|
|
|
|
put(item.code, item.allow)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.toString()
|
|
|
|
.encodeUTF8()
|
|
|
|
|
|
|
|
val cache_dir = cacheDir
|
|
|
|
cache_dir.mkdir()
|
|
|
|
|
|
|
|
val file = File(
|
|
|
|
cache_dir,
|
|
|
|
"SubwayTooter-language-filter.${Process.myPid()}.${Process.myTid()}.json"
|
|
|
|
)
|
|
|
|
FileOutputStream(file).use {
|
|
|
|
it.write(data)
|
|
|
|
}
|
|
|
|
file
|
|
|
|
},
|
|
|
|
afterProc = {
|
|
|
|
val uri = FileProvider.getUriForFile(
|
|
|
|
this@ActLanguageFilter,
|
|
|
|
App1.FILE_PROVIDER_AUTHORITY,
|
|
|
|
it
|
|
|
|
)
|
|
|
|
val intent = Intent(Intent.ACTION_SEND)
|
|
|
|
intent.type = contentResolver.getType(uri)
|
|
|
|
intent.putExtra(Intent.EXTRA_SUBJECT, "SubwayTooter language filter data")
|
|
|
|
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
|
|
|
|
|
|
|
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
|
|
|
|
|
|
arExport.launch(intent)
|
2021-05-22 00:03:16 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2021-05-27 04:15:59 +02:00
|
|
|
}
|
2021-05-22 00:03:16 +02:00
|
|
|
|
|
|
|
private fun import() {
|
2021-05-27 04:15:59 +02:00
|
|
|
arImport.launch(intentOpenDocument("*/*"))
|
2021-05-22 00:03:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Suppress("BlockingMethodInNonBlockingContext")
|
2021-05-27 04:15:59 +02:00
|
|
|
private fun import2(uri: Uri) {
|
|
|
|
launchMain {
|
|
|
|
runWithProgress(
|
|
|
|
"import language filter",
|
|
|
|
doInBackground = {
|
|
|
|
log.d("import2 type=${contentResolver.getType(uri)}")
|
|
|
|
val source = contentResolver.openInputStream(uri)
|
|
|
|
if (source == null) {
|
|
|
|
showToast(true, "openInputStream failed.")
|
|
|
|
null
|
|
|
|
} else {
|
|
|
|
source.use { inStream ->
|
|
|
|
val bao = ByteArrayOutputStream()
|
|
|
|
IOUtils.copy(inStream, bao)
|
|
|
|
bao.toByteArray().decodeUTF8().decodeJsonObject()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
afterProc = { if (it != null) load(it) }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2019-12-13 15:48:38 +01:00
|
|
|
}
|