add language filter in column setting

This commit is contained in:
tateisu 2019-12-13 23:48:38 +09:00
parent bf2882e1c7
commit f6294eba25
14 changed files with 749 additions and 662 deletions

View File

@ -64,6 +64,7 @@
<w>kenglxn</w>
<w>kotlinx</w>
<w>lateinit</w>
<w>latn</w>
<w>lparams</w>
<w>magick</w>
<w>mailto</w>

View File

@ -234,6 +234,12 @@
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
/>
<activity
android:name=".ActLanguageFilter"
android:label="@string/language_filter"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
/>
<activity
android:name=".ActNickname"
android:label="@string/nickname_and_color_and_notification_sound"

View File

@ -0,0 +1,490 @@
package jp.juggler.subwaytooter
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
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
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.ProgressDialogEx
import jp.juggler.util.*
import org.apache.commons.io.IOUtils
import org.jetbrains.anko.textColor
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.*
import kotlin.collections.ArrayList
class ActLanguageFilter : AppCompatActivity(), View.OnClickListener {
private class MyItem(
val code : String,
var allow : Boolean
)
companion object {
internal val log = LogCategory("ActLanguageFilter")
internal const val EXTRA_COLUMN_INDEX = "column_index"
fun open(activity : ActMain, idx : Int, request_code : Int) {
val intent = Intent(activity, ActLanguageFilter::class.java)
intent.putExtra(EXTRA_COLUMN_INDEX, idx)
activity.startActivityForResult(intent, request_code)
}
// 他の設定子画面と重複しない値にすること
private const val REQUEST_CODE_OTHER = 0
private const val REQUEST_CODE_IMPORT = 1
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 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
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
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_list[column_index]
load(column.language_filter ?: JSONObject())
}
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
}
private fun load(src : JSONObject) {
loading_busy = true
try {
languageList.clear()
for(key in src.keys()) {
languageList.add(MyItem(key, src.parseBoolean(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() {
val dst = JSONObject()
for(item in languageList) {
dst.put(item.code, item.allow)
}
column.language_filter = dst
}
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 = getAttributeColor(
this@ActLanguageFilter, 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) {
etLanguage.isEnabled = false
btnPresets.isEnabled = false
btnPresets.setEnabledColor(activity,R.drawable.ic_edit, getAttributeColor(activity,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()
}
}
private fun export() {
val progress = ProgressDialogEx(this)
val data = JSONObject().apply {
for(item in languageList) {
put(item.code, item.allow)
}
}
.toString()
.encodeUTF8()
val task = @SuppressLint("StaticFieldLeak")
object : AsyncTask<Void, String, File?>() {
override fun doInBackground(vararg params : Void) : File? {
try {
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) }
return file
} catch(ex : Throwable) {
log.trace(ex)
showToast(
this@ActLanguageFilter,
ex,
"can't save filter data to temporary file."
)
}
return null
}
override fun onCancelled(result : File?) {
onPostExecute(result)
}
override fun onPostExecute(result : File?) {
progress.dismissSafe()
if(isCancelled || result == null) {
// cancelled.
return
}
try {
val uri = FileProvider.getUriForFile(
this@ActLanguageFilter,
App1.FILE_PROVIDER_AUTHORITY,
result
)
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)
startActivityForResult(intent, REQUEST_CODE_OTHER)
} catch(ex : Throwable) {
log.trace(ex)
showToast(this@ActLanguageFilter, ex, "export failed.")
}
}
}
progress.isIndeterminateEx = true
progress.setCancelable(true)
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)
}
private fun import() {
try {
val intent = intentOpenDocument("*/*")
startActivityForResult(intent, REQUEST_CODE_IMPORT)
} catch(ex : Throwable) {
showToast(this, ex, "import failed.")
}
}
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
if(resultCode == RESULT_OK && data != null && requestCode == REQUEST_CODE_IMPORT) {
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
import2(it)
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun import2(uri : Uri) {
val type = contentResolver.getType(uri)
log.d("import2 type=%s", type)
val progress = ProgressDialogEx(this)
val task = @SuppressLint("StaticFieldLeak")
object : AsyncTask<Void, String, JSONObject?>() {
override fun doInBackground(vararg params : Void) : JSONObject? {
try {
val source = contentResolver.openInputStream(uri)
if(source == null) {
showToast(this@ActLanguageFilter, true, "openInputStream failed.")
return null
}
return source.use { inStream ->
val bao = ByteArrayOutputStream()
IOUtils.copy(inStream, bao)
JSONObject(bao.toByteArray().decodeUTF8())
}
} catch(ex : Throwable) {
log.trace(ex)
showToast(this@ActLanguageFilter, ex, "can't load filter data.")
return null
}
}
override fun onCancelled(result : JSONObject?) {
onPostExecute(result)
}
override fun onPostExecute(result : JSONObject?) {
progress.dismissSafe()
// cancelled.
if(isCancelled || result == null) return
load(result)
}
}
progress.isIndeterminateEx = true
progress.setCancelable(true)
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)
}
}

View File

@ -91,6 +91,7 @@ class ActMain : AppCompatActivity()
const val REQUEST_CODE_COLUMN_COLOR = 6
const val REQUEST_CODE_APP_SETTING = 7
const val REQUEST_CODE_TEXT = 8
const val REQUEST_CODE_LANGUAGE_FILTER = 9
const val COLUMN_WIDTH_MIN_DP = 300
@ -1018,7 +1019,7 @@ class ActMain : AppCompatActivity()
REQUEST_CODE_COLUMN_COLOR -> if(data != null) {
app_state.saveColumnList()
val idx = data.getIntExtra(ActColumnCustomize.EXTRA_COLUMN_INDEX, 0)
if(idx >= 0 && idx < app_state.column_list.size) {
if(idx in app_state.column_list.indices ) {
app_state.column_list[idx].fireColumnColor()
app_state.column_list[idx].fireShowContent(
reason = "ActMain column color changed",
@ -1027,6 +1028,14 @@ class ActMain : AppCompatActivity()
}
updateColumnStrip()
}
REQUEST_CODE_LANGUAGE_FILTER -> if(data != null) {
app_state.saveColumnList()
val idx = data.getIntExtra(ActLanguageFilter.EXTRA_COLUMN_INDEX, 0)
if(idx in app_state.column_list.indices ) {
app_state.column_list[idx].onLanguageFilterChanged()
}
}
}
}

View File

@ -162,6 +162,7 @@ class Column(
private const val KEY_QUICK_FILTER = "quickFilter"
private const val KEY_REGEX_TEXT = "regex_text"
private const val KEY_LANGUAGE_FILTER = "language_filter"
private const val KEY_HEADER_BACKGROUND_COLOR = "header_background_color"
private const val KEY_HEADER_TEXT_COLOR = "header_text_color"
@ -487,6 +488,8 @@ class Column(
internal var hashtag_none : String = ""
internal var hashtag_acct : String = ""
internal var language_filter : JSONObject? = null
// プロフカラムでのアカウント情報
@Volatile
internal var who_account : TootAccountRef? = null
@ -552,6 +555,7 @@ class Column(
|| dont_show_reply
|| dont_show_reaction
|| dont_show_vote
|| (language_filter?.length()?:0) >0
)
@Volatile
@ -705,6 +709,7 @@ class Column(
last_viewing_item_id = EntityId.from(src, KEY_LAST_VIEWING_ITEM)
regex_text = src.parseString(KEY_REGEX_TEXT) ?: ""
language_filter = src.optJSONObject(KEY_LANGUAGE_FILTER)
header_bg_color = src.optInt(KEY_HEADER_BACKGROUND_COLOR)
header_fg_color = src.optInt(KEY_HEADER_TEXT_COLOR)
@ -807,6 +812,9 @@ class Column(
dst.put(KEY_REGEX_TEXT, regex_text)
val ov = language_filter
if( ov != null) dst.put(KEY_LANGUAGE_FILTER,ov)
dst.put(KEY_HEADER_BACKGROUND_COLOR, header_bg_color)
dst.put(KEY_HEADER_TEXT_COLOR, header_fg_color)
dst.put(KEY_COLUMN_BACKGROUND_COLOR, column_bg_color)
@ -1314,6 +1322,10 @@ class Column(
}
fun onLanguageFilterChanged() {
// TODO
}
internal fun addColumnViewHolder(cvh : ColumnViewHolder) {
// 現在のリストにあるなら削除する
@ -1451,28 +1463,37 @@ class Column(
if(isFilteredByAttachment(status)) return true
val reblog = status.reblog
if(dont_show_boost) {
if(status.reblog != null) return true
if(reblog != null) return true
}
if(dont_show_reply) {
if(status.in_reply_to_id != null) return true
if(status.reblog?.in_reply_to_id != null) return true
if(reblog?.in_reply_to_id != null) return true
}
if(dont_show_normal_toot) {
if(status.in_reply_to_id == null && status.reblog == null) return true
if(status.in_reply_to_id == null && reblog == null) return true
}
if(column_regex_filter(status.decoded_content)) return true
if(column_regex_filter(status.reblog?.decoded_content)) return true
if(column_regex_filter(reblog?.decoded_content)) return true
if(column_regex_filter(status.decoded_spoiler_text)) return true
if(column_regex_filter(status.reblog?.decoded_spoiler_text)) return true
if(column_regex_filter(reblog?.decoded_spoiler_text)) return true
val languageFilter = language_filter
if(languageFilter != null ){
val bShow = languageFilter.parseBoolean(status.language ?: reblog?.language ?:TootStatus.LANGUAGE_CODE_UNKNOWN)
?: languageFilter.parseBoolean(TootStatus.LANGUAGE_CODE_DEFAULT)
?: true
if(!bShow) return true
}
if(access_info.isPseudo) {
var r = UserRelation.loadPseudo(access_info.getFullAcct(status.account))
if(r.muting || r.blocking) return true
val reblog = status.reblog
if(reblog != null) {
r = UserRelation.loadPseudo(access_info.getFullAcct(reblog.account))
if(r.muting || r.blocking) return true
@ -1535,7 +1556,16 @@ class Column(
// just update _filtered flag for reversible filter
status.updateKeywordFilteredFlag(access_info, filterTrees)
}
if( status != null){
val languageFilter = language_filter
if(languageFilter != null ){
val bShow = languageFilter.parseBoolean(status.language ?: status.reblog?.language ?:TootStatus.LANGUAGE_CODE_UNKNOWN)
?: languageFilter.parseBoolean(TootStatus.LANGUAGE_CODE_DEFAULT)
?: true
if(!bShow) return true
}
}
if(status?.checkMuted() == true) {
log.d("isFiltered: status muted by in-app muted words.")
return true
@ -3002,6 +3032,7 @@ class Column(
getHeaderNameColor()
)
}
// fun findListIndexByTimelineId(orderId : EntityId) : Int? {
// list_data.forEachIndexed { i, v ->

View File

@ -113,6 +113,7 @@ class ColumnViewHolder(
private lateinit var llRegexFilter : View
private lateinit var btnDeleteNotification : Button
private lateinit var btnColor : Button
private lateinit var btnLanguageFilter : Button
private lateinit var svQuickFilter : HorizontalScrollView
private lateinit var btnQuickFilterAll : Button
@ -309,6 +310,7 @@ class ColumnViewHolder(
btnDeleteNotification.setOnClickListener(this)
btnColor.setOnClickListener(this)
btnLanguageFilter.setOnClickListener(this)
refreshLayout.setOnRefreshListener(this)
refreshLayout.setDistanceToTriggerSync((0.5f + 20f * activity.density).toInt())
@ -586,6 +588,7 @@ class ColumnViewHolder(
vg(cbWithHighlight, bAllowFilter)
vg(etRegexFilter, bAllowFilter)
vg(llRegexFilter, bAllowFilter)
vg(btnLanguageFilter,bAllowFilter)
vg(cbDontShowBoost, column.canFilterBoost())
vg(cbDontShowReply, column.canFilterReply())
@ -1030,6 +1033,11 @@ class ColumnViewHolder(
ActColumnCustomize.open(activity, idx, ActMain.REQUEST_CODE_COLUMN_COLOR)
}
btnLanguageFilter ->{
val idx = activity.app_state.column_list.indexOf(column)
ActLanguageFilter.open(activity, idx, ActMain.REQUEST_CODE_LANGUAGE_FILTER)
}
btnListAdd -> {
val tv = etListName.text.toString().trim { it <= ' ' }
if(tv.isEmpty()) {
@ -1871,6 +1879,12 @@ class ColumnViewHolder(
isAllCaps = false
text = context.getString(R.string.color_and_background)
}.lparams(matchParent, wrapContent)
btnLanguageFilter = button {
isAllCaps = false
text = context.getString(R.string.language_filter)
}.lparams(matchParent, wrapContent)
}
} // end of column setting scroll view

View File

@ -93,7 +93,7 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
val sensitive : Boolean
// The detected language for the status, if detected
private val language : String?
val language : String?
//If not empty, warning text that should be displayed before the actual content
// アプリ内部では空文字列はCWなしとして扱う
@ -485,7 +485,7 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
parseItem(::TootApplication, parser, src.optJSONObject("application"), log)
this.pinned = parser.pinned || src.optBoolean("pinned")
this.muted = src.optBoolean("muted")
this.language = src.parseString("language")
this.language = src.parseString("language")?.notEmpty()
this.decoded_mentions = HTMLDecoder.decodeMentions(
parser.linkHelper,
this.mentions,
@ -815,6 +815,9 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
@Volatile
internal var muted_word : WordTrieTree? = null
const val LANGUAGE_CODE_UNKNOWN="unknown"
const val LANGUAGE_CODE_DEFAULT="default"
val EMPTY_SPANNABLE = SpannableString("")
// OStatus

View File

@ -2,13 +2,11 @@ package jp.juggler.subwaytooter.dialog
import android.annotation.SuppressLint
import android.app.Activity
import androidx.appcompat.app.AlertDialog
import android.view.View
import android.widget.CheckBox
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.util.EmptyCallback
object DlgConfirm {

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/llContent"
android:orientation="vertical">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fadeScrollbars="false"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:background="?attr/colorStatusButtonsPopupBg"
>
<ImageButton
android:id="@+id/btnAdd"
android:src="@drawable/ic_add"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:contentDescription="@string/add"
android:textAllCaps="false"
android:elevation="3dp"
/>
<Button
android:id="@+id/btnSave"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:text="@string/save"
android:textAllCaps="false"
android:elevation="3dp"
/>
<ImageButton
android:id="@+id/btnMore"
android:src="@drawable/ic_more"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/more"
android:elevation="3dp"
/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:text="@string/language_filter_description"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingBottom="6dp"
/>
</LinearLayout>

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:text="@string/language"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
>
<EditText
android:id="@+id/etLanguage"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="text"
android:hint="@string/language_code_hint"
/>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="3dp"
android:src="@drawable/ic_edit"
android:id="@+id/btnPresets"
android:contentDescription="@string/presets"
/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:id="@+id/tvLanguage"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:text="@string/show_hide"
/>
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:orientation="horizontal"
>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/rbShow"
android:text="@string/language_show"
/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:id="@+id/rbHide"
android:text="@string/language_hide"
/>
</RadioGroup>
</LinearLayout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:id="@android:id/text1"
style="?android:attr/spinnerDropDownItemStyle"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:background="@drawable/btn_bg_transparent"
/>

View File

@ -1,649 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:ignore="Autofill">
<!--
<LinearLayout
android:id="@+id/llColumnHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_column_header"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingTop="3dp"
android:paddingEnd="12dp"
android:paddingBottom="3dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:orientation="horizontal"
>
<TextView
android:id="@+id/tvColumnContext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textColor="?attr/colorColumnHeaderAcct"
android:textSize="12sp"
tools:text="tvColumnContext"
/>
<TextView
android:id="@+id/tvColumnStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:gravity="end"
android:textColor="?attr/colorColumnHeaderPageNumber"
android:textSize="12sp"
tools:text="RS"
/>
<TextView
android:id="@+id/tvColumnIndex"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:gravity="end"
android:textColor="?attr/colorColumnHeaderPageNumber"
android:textSize="12sp"
tools:text="col 6/12"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:baselineAligned="false"
>
<ImageView
android:id="@+id/ivColumnIcon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="4dp"
android:importantForAccessibility="no"
android:scaleType="fitCenter"
/>
<TextView
android:id="@+id/tvColumnName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tools:text="tvColumnName"
/>
<ImageButton
android:id="@+id/btnColumnSetting"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="2dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/setting"
android:src="@drawable/ic_tune"
android:padding="8dp"
android:scaleType="fitCenter"
/>
<ImageButton
android:id="@+id/btnColumnReload"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="2dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/reload"
android:src="@drawable/ic_refresh"
android:padding="8dp"
android:scaleType="fitCenter"
/>
<ImageButton
android:id="@+id/btnColumnClose"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="2dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/close_column"
android:src="@drawable/ic_close"
android:padding="8dp"
android:scaleType="fitCenter"
/>
</LinearLayout>
</LinearLayout>
<jp.juggler.subwaytooter.view.MaxHeightScrollView
android:id="@+id/llColumnSetting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadeScrollbars="false"
app:maxHeight="240dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorColumnSettingBackground"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingTop="3dp"
android:paddingEnd="12dp"
android:paddingBottom="3dp"
android:id="@+id/llColumnSettingInside"
>
<LinearLayout
android:id="@+id/llHashtagExtra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@+id/etHashtagExtraAny"
android:text="@string/hashtag_extra_any"
android:textColor="?attr/colorColumnHeaderPageNumber"
/>
<EditText
android:id="@+id/etHashtagExtraAny"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"
android:scrollHorizontally="true"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@+id/etHashtagExtraAll"
android:text="@string/hashtag_extra_all"
android:textColor="?attr/colorColumnHeaderPageNumber"
/>
<EditText
android:id="@+id/etHashtagExtraAll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"
android:scrollHorizontally="true"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@+id/etHashtagExtraNone"
android:text="@string/hashtag_extra_none"
android:textColor="?attr/colorColumnHeaderPageNumber"
/>
<EditText
android:id="@+id/etHashtagExtraNone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"
android:scrollHorizontally="true"
/>
</LinearLayout>
<CheckBox
android:id="@+id/cbDontCloseColumn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_close_column"
/>
<CheckBox
android:id="@+id/cbWithAttachment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/with_attachment"
/>
<CheckBox
android:id="@+id/cbWithHighlight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/with_highlight"
/>
<CheckBox
android:id="@+id/cbDontShowBoost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_show_boost"
/>
<CheckBox
android:id="@+id/cbDontShowFavourite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_show_favourite"
/>
<CheckBox
android:id="@+id/cbDontShowFollow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_show_follow"
/>
<CheckBox
android:id="@+id/cbDontShowReply"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_show_reply"
/>
<CheckBox
android:id="@+id/cbDontShowReaction"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_show_reaction"
/>
<CheckBox
android:id="@+id/cbDontShowVote"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_show_vote"
/>
<CheckBox
android:id="@+id/cbDontShowNormalToot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_show_normal_toot"
/>
<CheckBox
android:id="@+id/cbInstanceLocal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/instance_local"
/>
<CheckBox
android:id="@+id/cbDontStreaming"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_use_streaming_api"
/>
<CheckBox
android:id="@+id/cbDontAutoRefresh"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dont_refresh_on_activity_resume"
/>
<CheckBox
android:id="@+id/cbHideMediaDefault"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hide_media_default"
/>
<CheckBox
android:id="@+id/cbSystemNotificationNotRelated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/system_notification_not_related"
/>
<CheckBox
android:id="@+id/cbEnableSpeech"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enable_speech"
/>
<CheckBox
android:id="@+id/cbOldApi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/use_old_api"
/>
<LinearLayout
android:id="@+id/llRegexFilter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@+id/etRegexFilter"
android:text="@string/regex_filter"
android:textColor="?attr/colorColumnHeaderPageNumber"
/>
<TextView
android:id="@+id/tvRegexFilterError"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:textColor="?attr/colorRegexFilterError"
/>
</LinearLayout>
<EditText
android:id="@+id/etRegexFilter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"
android:scrollHorizontally="true"
/>
<Button
android:id="@+id/btnDeleteNotification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/notification_delete"
android:textAllCaps="false"
/>
<Button
android:id="@+id/btnColor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/color_and_background"
android:textAllCaps="false"
/>
</LinearLayout>
</jp.juggler.subwaytooter.view.MaxHeightScrollView>
<LinearLayout
android:id="@+id/llSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSearchFormBackground"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingTop="3dp"
android:paddingEnd="12dp"
android:paddingBottom="3dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:gravity="center"
android:orientation="horizontal"
>
<EditText
android:id="@+id/etSearch"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:imeOptions="actionSearch"
android:inputType="text"
tools:ignore="LabelFor"
/>
<ImageButton
android:id="@+id/btnSearchClear"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/clear"
android:src="@drawable/ic_close"
android:tint="?attr/colorVectorDrawable"
/>
<ImageButton
android:id="@+id/btnSearch"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/search"
android:src="@drawable/ic_search"
android:tint="?attr/colorVectorDrawable"
/>
</LinearLayout>
<CheckBox
android:id="@+id/cbResolve"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/resolve_non_local_account"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/llListList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSearchFormBackground"
android:baselineAligned="false"
android:gravity="center"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingTop="3dp"
android:paddingEnd="12dp"
android:paddingBottom="3dp"
>
<EditText
android:id="@+id/etListName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/list_create_hint"
android:imeOptions="actionSend"
android:inputType="text"
tools:ignore="LabelFor"
/>
<ImageButton
android:id="@+id/btnListAdd"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/add"
android:src="@drawable/ic_add"
android:tint="?attr/colorVectorDrawable"
/>
</LinearLayout>
<HorizontalScrollView
android:id="@+id/svQuickFilter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="40dp"
android:orientation="horizontal"
>
<Button
android:id="@+id/btnQuickFilterAll"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:background="@drawable/btn_bg_transparent"
android:minWidth="40dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:text="@string/all"
android:textAllCaps="false"
android:stateListAnimator="@null"
/>
<ImageButton
android:id="@+id/btnQuickFilterMention"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/mention2"
/>
<ImageButton
android:id="@+id/btnQuickFilterFavourite"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/favourite"
/>
<ImageButton
android:id="@+id/btnQuickFilterBoost"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/boost"
/>
<ImageButton
android:id="@+id/btnQuickFilterFollow"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/follow"
/>
<ImageButton
android:id="@+id/btnQuickFilterReaction"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/reaction"
/>
<ImageButton
android:id="@+id/btnQuickFilterVote"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:background="@drawable/btn_bg_transparent"
android:contentDescription="@string/vote_polls"
/>
</LinearLayout>
</HorizontalScrollView>
<FrameLayout
android:id="@+id/flColumnBackground"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
>
<ImageView
android:id="@+id/ivColumnBackgroundImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
android:visibility="gone"
/>
<TextView
android:id="@+id/tvLoading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
/>
<com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout
android:id="@+id/swipyRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:srl_direction="both"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fadeScrollbars="false"
android:padding="0dp"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
/>
</com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout>
<FrameLayout
android:id="@+id/llRefreshError"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_margin="6dp"
android:background="@drawable/bg_refresh_error"
>
<ImageView
android:id="@+id/ivRefreshError"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="4dp"
android:scaleType="fitCenter"
android:importantForAccessibility="no"
android:src="@drawable/ic_error"
android:tint="#ff0000"
/>
<TextView
android:id="@+id/tvRefreshError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="32dp"
android:scaleType="center"
android:textColor="#fff"
/>
</FrameLayout>
</FrameLayout>
-->
</LinearLayout>

View File

@ -980,5 +980,19 @@
<string name="fedibird_domain_timeline">(fedibird)ドメインタイムライン</string>
<string name="domain_timeline">ドメインTL</string>
<string name="domain_timeline_of">%1$sのドメインTL</string>
<string name="language_filter">言語フィルタ</string>
<string name="export">エクスポート</string>
<string name="import_">インポート</string>
<string name="language_code_default">(デフォルト)</string>
<string name="language_code_unknown">(不明)</string>
<string name="language_show">表示する</string>
<string name="language_hide">隠す</string>
<string name="language">言語</string>
<string name="show_hide">表示する/隠す</string>
<string name="language_filter_description">フィルタを適用するにはカラムをリロードしてください</string>
<string name="clear_all">全て消去</string>
<string name="presets">プリセット</string>
<string name="custom">カスタム</string>
<string name="language_code_hint">プリセットを選択または言語コードを入力</string>
</resources>

View File

@ -974,4 +974,18 @@
<string name="fedibird_domain_timeline">(fedibird)Domain timeline</string>
<string name="domain_timeline">Domain timeline</string>
<string name="domain_timeline_of">%1$s domain timeline</string>
<string name="language_filter">Language filter</string>
<string name="export">Export</string>
<string name="import_">Import</string>
<string name="language_code_default">(default)</string>
<string name="language_code_unknown">(unknown)</string>
<string name="language_show">Show</string>
<string name="language_hide">Hide</string>
<string name="language">Language</string>
<string name="show_hide">Show/Hide</string>
<string name="language_filter_description">column reload required to apply filter.</string>
<string name="clear_all">Clear all</string>
<string name="presets">Presets</string>
<string name="custom">Custom</string>
<string name="language_code_hint">choose preset or input language code</string>
</resources>