アプリ設定に公開範囲別のトゥート背景色を追加
This commit is contained in:
parent
4ae72558b0
commit
b7f5f67520
|
@ -72,6 +72,11 @@ class ActAppSetting : AppCompatActivity()
|
|||
internal const val COLOR_DIALOG_ID_FOOTER_TAB_INDICATOR = 5
|
||||
internal const val COLOR_DIALOG_ID_LIST_DIVIDER = 6
|
||||
|
||||
internal const val COLOR_DIALOG_ID_TOOT_BG_UNLISTED = 7
|
||||
internal const val COLOR_DIALOG_ID_TOOT_BG_FOLLOWER = 8
|
||||
internal const val COLOR_DIALOG_ID_TOOT_BG_DIRECT_USER = 9
|
||||
internal const val COLOR_DIALOG_ID_TOOT_BG_DIRECT_ME = 10
|
||||
|
||||
internal const val REQUEST_CODE_TIMELINE_FONT = 1
|
||||
internal const val REQUEST_CODE_TIMELINE_FONT_BOLD = 2
|
||||
internal const val REQUEST_CODE_APP_DATA_EXPORT = 3
|
||||
|
@ -113,6 +118,10 @@ class ActAppSetting : AppCompatActivity()
|
|||
private var footer_tab_divider_color : Int = 0
|
||||
private var footer_tab_indicator_color : Int = 0
|
||||
private var list_divider_color : Int = 0
|
||||
private var toot_color_unlisted : Int = 0
|
||||
private var toot_color_follower : Int = 0
|
||||
private var toot_color_direct_user : Int = 0
|
||||
private var toot_color_direct_me : Int = 0
|
||||
|
||||
private lateinit var ivFooterToot : ImageView
|
||||
private lateinit var ivFooterMenu : ImageView
|
||||
|
@ -267,6 +276,16 @@ class ActAppSetting : AppCompatActivity()
|
|||
findViewById<View>(R.id.btnListDividerColorEdit).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnListDividerColorReset).setOnClickListener(this)
|
||||
|
||||
findViewById<View>(R.id.btnBackgroundColorUnlistedEdit).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnBackgroundColorUnlistedReset).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnBackgroundColorFollowerEdit).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnBackgroundColorFollowerReset).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnBackgroundColorDirectWithUserEdit).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnBackgroundColorDirectWithUserReset).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnBackgroundColorDirectNoUserEdit).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnBackgroundColorDirectNoUserReset).setOnClickListener(this)
|
||||
|
||||
|
||||
findViewById<View>(R.id.btnTimelineFontEdit).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnTimelineFontReset).setOnClickListener(this)
|
||||
findViewById<View>(R.id.btnTimelineFontBoldEdit).setOnClickListener(this)
|
||||
|
@ -391,6 +410,10 @@ class ActAppSetting : AppCompatActivity()
|
|||
footer_tab_divider_color = Pref.ipFooterTabDividerColor(pref)
|
||||
footer_tab_indicator_color = Pref.ipFooterTabIndicatorColor(pref)
|
||||
list_divider_color = Pref.ipListDividerColor(pref)
|
||||
toot_color_unlisted = Pref.ipTootColorUnlisted(pref)
|
||||
toot_color_follower = Pref.ipTootColorFollower(pref)
|
||||
toot_color_direct_user = Pref.ipTootColorDirectUser(pref)
|
||||
toot_color_direct_me = Pref.ipTootColorDirectMe(pref)
|
||||
|
||||
etColumnWidth.setText(Pref.spColumnWidth(pref))
|
||||
etMediaThumbHeight.setText(Pref.spMediaThumbHeight(pref))
|
||||
|
@ -498,6 +521,11 @@ class ActAppSetting : AppCompatActivity()
|
|||
.put(Pref.ipFooterTabDividerColor, footer_tab_divider_color)
|
||||
.put(Pref.ipFooterTabIndicatorColor, footer_tab_indicator_color)
|
||||
.put(Pref.ipListDividerColor, list_divider_color)
|
||||
|
||||
.put(Pref.ipTootColorUnlisted, toot_color_unlisted)
|
||||
.put(Pref.ipTootColorFollower, toot_color_follower)
|
||||
.put(Pref.ipTootColorDirectUser, toot_color_direct_user)
|
||||
.put(Pref.ipTootColorDirectMe, toot_color_direct_me)
|
||||
|
||||
.apply()
|
||||
|
||||
|
@ -592,10 +620,50 @@ class ActAppSetting : AppCompatActivity()
|
|||
true
|
||||
)
|
||||
|
||||
R.id.btnBackgroundColorUnlistedEdit -> openColorPicker(
|
||||
COLOR_DIALOG_ID_TOOT_BG_UNLISTED,
|
||||
toot_color_unlisted,
|
||||
true
|
||||
)
|
||||
|
||||
R.id.btnBackgroundColorFollowerEdit -> openColorPicker(
|
||||
COLOR_DIALOG_ID_TOOT_BG_FOLLOWER,
|
||||
toot_color_follower,
|
||||
true
|
||||
)
|
||||
|
||||
R.id.btnBackgroundColorDirectWithUserEdit -> openColorPicker(
|
||||
COLOR_DIALOG_ID_TOOT_BG_DIRECT_USER,
|
||||
toot_color_direct_user,
|
||||
true
|
||||
)
|
||||
|
||||
R.id.btnBackgroundColorDirectNoUserEdit -> openColorPicker(
|
||||
COLOR_DIALOG_ID_TOOT_BG_DIRECT_ME,
|
||||
toot_color_direct_me,
|
||||
true
|
||||
)
|
||||
|
||||
R.id.btnListDividerColorReset -> {
|
||||
list_divider_color = 0
|
||||
saveUIToData()
|
||||
}
|
||||
R.id.btnBackgroundColorUnlistedReset -> {
|
||||
toot_color_unlisted = 0
|
||||
saveUIToData()
|
||||
}
|
||||
R.id.btnBackgroundColorFollowerReset -> {
|
||||
toot_color_follower = 0
|
||||
saveUIToData()
|
||||
}
|
||||
R.id.btnBackgroundColorDirectWithUserReset -> {
|
||||
toot_color_direct_user = 0
|
||||
saveUIToData()
|
||||
}
|
||||
R.id.btnBackgroundColorDirectNoUserReset -> {
|
||||
toot_color_direct_me = 0
|
||||
saveUIToData()
|
||||
}
|
||||
|
||||
R.id.btnTimelineFontReset -> {
|
||||
timeline_font = ""
|
||||
|
@ -723,6 +791,24 @@ class ActAppSetting : AppCompatActivity()
|
|||
list_divider_color = if(colorSelected == 0) 0x01000000 else colorSelected
|
||||
saveUIToData()
|
||||
}
|
||||
|
||||
COLOR_DIALOG_ID_TOOT_BG_UNLISTED -> {
|
||||
toot_color_unlisted = if(colorSelected == 0) 0x01000000 else colorSelected
|
||||
saveUIToData()
|
||||
}
|
||||
COLOR_DIALOG_ID_TOOT_BG_FOLLOWER -> {
|
||||
toot_color_follower = if(colorSelected == 0) 0x01000000 else colorSelected
|
||||
saveUIToData()
|
||||
}
|
||||
COLOR_DIALOG_ID_TOOT_BG_DIRECT_USER -> {
|
||||
toot_color_direct_user= if(colorSelected == 0) 0x01000000 else colorSelected
|
||||
saveUIToData()
|
||||
}
|
||||
COLOR_DIALOG_ID_TOOT_BG_DIRECT_ME -> {
|
||||
toot_color_direct_me = if(colorSelected == 0) 0x01000000 else colorSelected
|
||||
saveUIToData()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -461,6 +461,10 @@ class ActMain : AppCompatActivity()
|
|||
|
||||
// カラーカスタマイズを読み直す
|
||||
ListDivider.color = Pref.ipListDividerColor(pref)
|
||||
ItemViewHolder.toot_color_unlisted = Pref.ipTootColorUnlisted(pref)
|
||||
ItemViewHolder.toot_color_follower = Pref.ipTootColorFollower(pref)
|
||||
ItemViewHolder.toot_color_direct_user = Pref.ipTootColorDirectUser(pref)
|
||||
ItemViewHolder.toot_color_direct_me = Pref.ipTootColorDirectMe(pref)
|
||||
|
||||
// アカウント設定から戻ってきたら、カラムを消す必要があるかもしれない
|
||||
run {
|
||||
|
|
|
@ -43,6 +43,10 @@ internal class ItemViewHolder(
|
|||
|
||||
companion object {
|
||||
private val log = LogCategory("ItemViewHolder")
|
||||
var toot_color_unlisted : Int =0
|
||||
var toot_color_follower : Int =0
|
||||
var toot_color_direct_user : Int =0
|
||||
var toot_color_direct_me : Int =0
|
||||
}
|
||||
|
||||
val viewRoot : View
|
||||
|
@ -757,6 +761,17 @@ internal class ItemViewHolder(
|
|||
R.attr.colorImageButtonAccent
|
||||
) and 0xffffff) or 0x20000000
|
||||
)
|
||||
}else{
|
||||
val c = when(status.getBackgroundColorType(access_info)){
|
||||
TootVisibility.UnlistedHome-> toot_color_unlisted
|
||||
TootVisibility.PrivateFollowers-> toot_color_follower
|
||||
TootVisibility.DirectSpecified-> toot_color_direct_user
|
||||
TootVisibility.DirectPrivate-> toot_color_direct_me
|
||||
else->0
|
||||
}
|
||||
if(c!=0){
|
||||
this.viewRoot.setBackgroundColor(c)
|
||||
}
|
||||
}
|
||||
|
||||
showStatusTime(activity, tvTime, who = status.account, status = status)
|
||||
|
|
|
@ -343,7 +343,12 @@ object Pref {
|
|||
val ipLastColumnPos = IntPref("last_column_pos", - 1)
|
||||
val ipBoostButtonJustify = IntPref("ipBoostButtonJustify", 0) // 0=左,1=中央,2=右
|
||||
|
||||
// val ipTrendTagCountShowing = IntPref("TrendTagCountShowing", 0)
|
||||
val ipTootColorUnlisted = IntPref("ipTootColorUnlisted", 0)
|
||||
val ipTootColorFollower = IntPref("ipTootColorFollower", 0)
|
||||
val ipTootColorDirectUser = IntPref("ipTootColorDirectUser", 0)
|
||||
val ipTootColorDirectMe = IntPref("ipTootColorDirectMe", 0)
|
||||
|
||||
// val ipTrendTagCountShowing = IntPref("TrendTagCountShowing", 0)
|
||||
// const val TTCS_WEEKLY = 0
|
||||
// const val TTCS_DAILY = 1
|
||||
|
||||
|
|
|
@ -500,6 +500,32 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
|
|||
|
||||
val filtered : Boolean
|
||||
get() = _filtered || reblog?._filtered == true
|
||||
|
||||
|
||||
|
||||
private fun hasReceipt(access_info:SavedAccount):TootVisibility{
|
||||
val reply = this.reply
|
||||
if( reply != null && ! access_info.isMe(reply.account)) {
|
||||
return TootVisibility.DirectSpecified
|
||||
}
|
||||
val in_reply_to_account_id = this.in_reply_to_account_id
|
||||
if( in_reply_to_account_id != null && in_reply_to_account_id != access_info.loginAccount?.id) {
|
||||
return TootVisibility.DirectSpecified
|
||||
}
|
||||
mentions?.forEach{
|
||||
if(!access_info.isMe(it.acct))
|
||||
return@hasReceipt TootVisibility.DirectSpecified
|
||||
}
|
||||
return TootVisibility.DirectPrivate
|
||||
}
|
||||
|
||||
fun getBackgroundColorType(access_info:SavedAccount) =
|
||||
when(visibility){
|
||||
TootVisibility.DirectPrivate,
|
||||
TootVisibility.DirectSpecified -> hasReceipt(access_info)
|
||||
else-> visibility
|
||||
}
|
||||
|
||||
|
||||
fun updateFiltered(muted_words : WordTrieTree?) {
|
||||
_filtered = checkFiltered(muted_words)
|
||||
|
|
|
@ -31,15 +31,14 @@ private fun String.safeSubstring(count : Int, offset : Int = 0) = when {
|
|||
else -> this.substring(offset, length)
|
||||
}
|
||||
|
||||
// 配列中の要素をラムダ式で変換して、戻り値が非nullならそこで処理を打ち切る
|
||||
private inline fun <T, V> Array<out T>.firstNonNull(predicate : (T) -> V?) : V? {
|
||||
for(element in this) return predicate(element) ?: continue
|
||||
return null
|
||||
}
|
||||
|
||||
object MisskeySyntaxHighlighter {
|
||||
|
||||
private val symbolMap = SparseBooleanArray().apply {
|
||||
for(c in "=+-*/%~^&|><!?") {
|
||||
this.put(c.toInt(), true)
|
||||
}
|
||||
}
|
||||
|
||||
// 識別子に対して既存の名前と一致するか調べるようになったので、もはやソートの必要はない
|
||||
private val keywords = HashSet<String>().apply {
|
||||
|
||||
val _keywords = arrayOf(
|
||||
|
@ -134,20 +133,100 @@ object MisskeySyntaxHighlighter {
|
|||
addAll(_keywords.map { k -> k[0].toUpperCase() + k.substring(1) })
|
||||
|
||||
add("NaN")
|
||||
|
||||
// 識別子に対して既存の名前と一致するか調べるようになったので、もはやソートの必要はない
|
||||
}
|
||||
|
||||
private val symbolMap = SparseBooleanArray().apply {
|
||||
for(c in "=+-*/%~^&|><!?") {
|
||||
this.put(c.toInt(), true)
|
||||
}
|
||||
}
|
||||
|
||||
private val stringStart = SparseBooleanArray().apply {
|
||||
for(c in "\"'`") {
|
||||
this.put(c.toInt(), true)
|
||||
}
|
||||
}
|
||||
|
||||
private class Token(
|
||||
var length : Int,
|
||||
var color : Int = 0,
|
||||
val length : Int,
|
||||
val color : Int = 0,
|
||||
val italic : Boolean = false,
|
||||
val comment : Boolean = false
|
||||
)
|
||||
|
||||
private class Env(
|
||||
var source : String,
|
||||
var pos : Int,
|
||||
var remain : String
|
||||
)
|
||||
private class Env(val source : String) {
|
||||
|
||||
// 出力先
|
||||
val sb = SpannableStringBuilder(source)
|
||||
|
||||
// 残り部分
|
||||
var remain : String = source
|
||||
private set
|
||||
|
||||
// スキャン位置
|
||||
var pos : Int = 0
|
||||
set(value) {
|
||||
field = value
|
||||
remain = source.substring(value)
|
||||
}
|
||||
|
||||
fun push(start : Int, token : Token) {
|
||||
val end = start + token.length
|
||||
|
||||
if(token.comment) {
|
||||
sb.setSpan(
|
||||
ForegroundColorSpan(Color.BLACK or 0x808000)
|
||||
, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
} else {
|
||||
var c = token.color
|
||||
if(c != 0) {
|
||||
if(c < 0x1000000) {
|
||||
c = c or Color.BLACK
|
||||
}
|
||||
sb.setSpan(
|
||||
ForegroundColorSpan(c)
|
||||
, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
if(token.italic) {
|
||||
sb.setSpan(
|
||||
CalligraphyTypefaceSpan(Typeface.defaultFromStyle(Typeface.ITALIC))
|
||||
, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun parse() : SpannableStringBuilder {
|
||||
|
||||
var lastEnd = 0
|
||||
fun closeTextToken(textEnd : Int) {
|
||||
val length = textEnd - lastEnd
|
||||
if(length > 0) {
|
||||
push(lastEnd, Token(length = length))
|
||||
lastEnd = textEnd
|
||||
}
|
||||
}
|
||||
|
||||
while(remain.isNotEmpty()) {
|
||||
val token = elements.firstNonNull { this.it() }
|
||||
if(token == null) {
|
||||
++ pos
|
||||
} else {
|
||||
closeTextToken(pos)
|
||||
push(pos, token)
|
||||
this.pos += token.length
|
||||
lastEnd = pos
|
||||
}
|
||||
}
|
||||
closeTextToken(pos)
|
||||
|
||||
return sb
|
||||
}
|
||||
}
|
||||
|
||||
private val reLineComment = Pattern.compile("""\A//.*""")
|
||||
private val reBlockComment = Pattern.compile("""\A/\*.*?\*/""", Pattern.DOTALL)
|
||||
|
@ -156,39 +235,39 @@ object MisskeySyntaxHighlighter {
|
|||
private val reKeyword =
|
||||
Pattern.compile("""\A([A-Z_-][A-Z0-9_-]*)([ \t]*\()?""", Pattern.CASE_INSENSITIVE)
|
||||
|
||||
private val elements = arrayOf(
|
||||
private val elements = arrayOf<Env.() -> Token?>(
|
||||
|
||||
// comment
|
||||
{ env : Env ->
|
||||
val match = reLineComment.matcher(env.remain)
|
||||
{
|
||||
val match = reLineComment.matcher(remain)
|
||||
when {
|
||||
match.find() -> Token(length = match.end(), comment = true)
|
||||
else -> null
|
||||
! match.find() -> null
|
||||
else -> Token(length = match.end(), comment = true)
|
||||
}
|
||||
},
|
||||
|
||||
// block comment
|
||||
{ env : Env ->
|
||||
val match = reBlockComment.matcher(env.remain)
|
||||
{
|
||||
val match = reBlockComment.matcher(remain)
|
||||
when {
|
||||
match.find() -> Token(length = match.end(), comment = true)
|
||||
else -> null
|
||||
! match.find() -> null
|
||||
else -> Token(length = match.end(), comment = true)
|
||||
}
|
||||
},
|
||||
|
||||
// string
|
||||
{ env : Env ->
|
||||
val beginChar = env.remain[0]
|
||||
if(beginChar != '"' && beginChar != '`') return@arrayOf null
|
||||
{
|
||||
val beginChar = remain[0]
|
||||
if(!stringStart[beginChar.toInt()]) return@arrayOf null
|
||||
var len = 1
|
||||
while(len < env.remain.length) {
|
||||
val char = env.remain[len ++]
|
||||
while(len < remain.length) {
|
||||
val char = remain[len ++]
|
||||
if(char == beginChar) {
|
||||
break // end
|
||||
} else if(char == '\n' || len >= env.remain.length) {
|
||||
} else if(char == '\n' || len >= remain.length) {
|
||||
len = 0 // not string literal
|
||||
break
|
||||
} else if(char == '\\' && len < env.remain.length) {
|
||||
} else if(char == '\\' && len < remain.length) {
|
||||
++ len // \" では閉じないようにする
|
||||
}
|
||||
}
|
||||
|
@ -199,27 +278,27 @@ object MisskeySyntaxHighlighter {
|
|||
},
|
||||
|
||||
// regexp
|
||||
{ env : Env ->
|
||||
if(env.remain[0] != '/') return@arrayOf null
|
||||
{
|
||||
if(remain[0] != '/') return@arrayOf null
|
||||
val regexp = StringBuilder()
|
||||
var thisIsNotARegexp = false
|
||||
var notClosed = false
|
||||
var i = 1
|
||||
while(i < env.remain.length) {
|
||||
val char = env.remain[i ++]
|
||||
while(i < remain.length) {
|
||||
val char = remain[i ++]
|
||||
if(char == '/') {
|
||||
break
|
||||
} else if(char == '\n' || i >= env.remain.length) {
|
||||
thisIsNotARegexp = true
|
||||
} else if(char == '\n' || i >= remain.length) {
|
||||
notClosed = true
|
||||
break
|
||||
} else {
|
||||
regexp.append(char)
|
||||
if(char == '\\' && i < env.remain.length) {
|
||||
regexp.append(env.remain[i ++])
|
||||
if(char == '\\' && i < remain.length) {
|
||||
regexp.append(remain[i ++])
|
||||
}
|
||||
}
|
||||
}
|
||||
when {
|
||||
thisIsNotARegexp -> null
|
||||
notClosed -> null
|
||||
regexp.isEmpty() -> null
|
||||
regexp[0] == ' ' && regexp[regexp.length - 1] == ' ' -> null
|
||||
else -> Token(length = regexp.length + 2, color = 0xe9003f)
|
||||
|
@ -227,53 +306,57 @@ object MisskeySyntaxHighlighter {
|
|||
},
|
||||
|
||||
// label
|
||||
{ env : Env ->
|
||||
{
|
||||
// 直前に識別子があればNG
|
||||
val prev = if(env.pos <= 0) null else env.source[env.pos - 1]
|
||||
val prev = if(pos <= 0) null else source[pos - 1]
|
||||
if(prev?.isLetterOrDigit() == true) return@arrayOf null
|
||||
|
||||
val match = reLabel.matcher(env.remain)
|
||||
val match = reLabel.matcher(remain)
|
||||
if(! match.find()) return@arrayOf null
|
||||
|
||||
val end = match.end()
|
||||
when {
|
||||
// @user@host のように直後に@が続くのはNG
|
||||
env.remain.length > end && env.remain[end] == '@' -> null
|
||||
remain.length > end && remain[end] == '@' -> null
|
||||
else -> Token(length = match.end(), color = 0xe9003f)
|
||||
}
|
||||
},
|
||||
|
||||
// number
|
||||
{ env : Env ->
|
||||
val prev = if(env.pos <= 0) null else env.source[env.pos - 1]
|
||||
{
|
||||
val prev = if(pos <= 0) null else source[pos - 1]
|
||||
if(prev?.isLetterOrDigit() == true) return@arrayOf null
|
||||
val match = reNumber.matcher(env.remain)
|
||||
val match = reNumber.matcher(remain)
|
||||
when {
|
||||
match.find() -> Token(length = match.end(), color = 0xae81ff)
|
||||
else -> null
|
||||
! match.find() -> null
|
||||
else -> Token(length = match.end(), color = 0xae81ff)
|
||||
}
|
||||
},
|
||||
|
||||
// method, property, keyword
|
||||
{ env : Env ->
|
||||
{
|
||||
// 直前の文字が識別子に使えるなら識別子の開始とはみなさない
|
||||
val prev = if(env.pos <= 0) null else env.source[env.pos - 1]
|
||||
val prev = if(pos <= 0) null else source[pos - 1]
|
||||
if(prev?.isLetterOrDigit() == true || prev == '_') return@arrayOf null
|
||||
|
||||
val match = reKeyword.matcher(env.remain)
|
||||
val match = reKeyword.matcher(remain)
|
||||
if(! match.find()) return@arrayOf null
|
||||
val kw = match.group(1)
|
||||
val bracket = match.group(2)
|
||||
|
||||
when {
|
||||
// メソッド呼び出しは対照が変数かプロパティかに関わらずメソッドの色になる
|
||||
// メソッド呼び出しは対象が変数かプロパティかに関わらずメソッドの色になる
|
||||
bracket?.isNotEmpty() == true ->
|
||||
Token(length = kw.length, color = 0x8964c1, italic = true)
|
||||
|
||||
// 変数や定数ではなくプロパティならプロパティの色になる
|
||||
prev == '.' -> Token(length = kw.length, color = 0xa71d5d)
|
||||
|
||||
keywords.contains(kw) -> when(kw) {
|
||||
// 予約語ではない
|
||||
// 強調表示しないが、識別子単位で読み飛ばす
|
||||
! keywords.contains(kw) -> Token(length = kw.length)
|
||||
|
||||
else -> when(kw) {
|
||||
|
||||
// 定数
|
||||
"true", "false", "null", "nil", "undefined", "NaN" ->
|
||||
|
@ -282,86 +365,21 @@ object MisskeySyntaxHighlighter {
|
|||
// その他の予約語
|
||||
else -> Token(length = kw.length, color = 0x2973b7)
|
||||
}
|
||||
|
||||
// 強調表示しないが、識別子単位で読み飛ばす
|
||||
else -> Token(length = kw.length)
|
||||
}
|
||||
},
|
||||
|
||||
// symbol
|
||||
{ env : Env ->
|
||||
{
|
||||
when {
|
||||
symbolMap.get(env.remain[0].toInt(), false) ->
|
||||
symbolMap.get(remain[0].toInt(), false) ->
|
||||
Token(length = 1, color = 0x42b983)
|
||||
else ->
|
||||
null
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
fun parse(source : String) : SpannableStringBuilder {
|
||||
val sb = SpannableStringBuilder()
|
||||
|
||||
val env = Env(source = source, pos = 0, remain = source)
|
||||
|
||||
fun push(pos : Int, token : Token) {
|
||||
val end = pos + token.length
|
||||
sb.append(source.substring(pos, end))
|
||||
env.pos = end
|
||||
|
||||
if(token.comment) {
|
||||
sb.setSpan(
|
||||
ForegroundColorSpan(Color.BLACK or 0x808000)
|
||||
, pos, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
} else {
|
||||
var c = token.color
|
||||
if(c != 0) {
|
||||
if(c < 0x1000000) {
|
||||
c = c or Color.BLACK
|
||||
}
|
||||
sb.setSpan(
|
||||
ForegroundColorSpan(c)
|
||||
, pos, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
if(token.italic) {
|
||||
sb.setSpan(
|
||||
CalligraphyTypefaceSpan(Typeface.defaultFromStyle(Typeface.ITALIC))
|
||||
, pos, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var textToken : Token? = null
|
||||
var textTokenStart = 0
|
||||
fun closeTextToken() {
|
||||
val token = textToken
|
||||
if(token != null) {
|
||||
token.length = env.pos - textTokenStart
|
||||
push(textTokenStart, token)
|
||||
textToken = null
|
||||
}
|
||||
}
|
||||
loop1@ while(env.remain.isNotEmpty()) {
|
||||
for(el in elements) {
|
||||
val token = el(env) ?: continue
|
||||
closeTextToken()
|
||||
push(env.pos, token)
|
||||
env.remain = source.substring(env.pos)
|
||||
continue@loop1
|
||||
}
|
||||
if(textToken == null) {
|
||||
textToken = Token(length = 0)
|
||||
textTokenStart = env.pos
|
||||
}
|
||||
env.remain = source.substring(++ env.pos)
|
||||
}
|
||||
closeTextToken()
|
||||
|
||||
return sb
|
||||
}
|
||||
fun parse(source : String) = Env(source = source).parse()
|
||||
|
||||
}
|
||||
|
||||
object MisskeyMarkdownDecoder {
|
||||
|
@ -505,188 +523,14 @@ object MisskeyMarkdownDecoder {
|
|||
}
|
||||
setHighlight()
|
||||
}
|
||||
}
|
||||
|
||||
private class ParseParams(
|
||||
val text : String
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// リンクを作ったりデータを参照したりする要素
|
||||
|
||||
private val dEmoji : DecodeEnv.() -> Unit = {
|
||||
val code = data?.get(0)
|
||||
if(code?.isNotEmpty() == true) {
|
||||
appendText(options.decodeEmoji(":$code:"))
|
||||
}
|
||||
}
|
||||
|
||||
private val dHashtag : DecodeEnv.() -> Unit = {
|
||||
val linkHelper = linkHelper
|
||||
val tag = data?.get(0)
|
||||
if(tag?.isNotEmpty() == true && linkHelper != null) {
|
||||
appendLink(
|
||||
"#$tag",
|
||||
"https://${linkHelper.host}/tags/" + tag.encodePercent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val dMention : DecodeEnv.() -> Unit = {
|
||||
|
||||
val username = data?.get(0) ?: ""
|
||||
val host = data?.get(1) ?: ""
|
||||
|
||||
val linkHelper = linkHelper
|
||||
if(linkHelper == null) {
|
||||
appendText(
|
||||
when {
|
||||
host.isEmpty() -> "@$username"
|
||||
else -> "@$username@$host"
|
||||
}
|
||||
)
|
||||
} else {
|
||||
|
||||
val shortAcct = when {
|
||||
host.isEmpty()
|
||||
|| host.equals(linkHelper.host, ignoreCase = true) ->
|
||||
username
|
||||
else ->
|
||||
"$username@$host"
|
||||
}
|
||||
|
||||
val userHost = when {
|
||||
host.isEmpty() -> linkHelper.host
|
||||
else -> host
|
||||
}
|
||||
val userUrl = "https://$userHost/@$username"
|
||||
|
||||
var mentions = sb.mentions
|
||||
if(mentions == null) {
|
||||
mentions = ArrayList()
|
||||
sb.mentions = mentions
|
||||
}
|
||||
|
||||
if(mentions.find { it.acct == shortAcct } == null) {
|
||||
mentions.add(
|
||||
TootMention(
|
||||
EntityIdLong(- 1L)
|
||||
, userUrl
|
||||
, shortAcct
|
||||
, username
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
appendLink(
|
||||
when {
|
||||
Pref.bpMentionFullAcct(App1.pref) -> "@$username@$userHost"
|
||||
else -> "@$shortAcct"
|
||||
}
|
||||
, userUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// インラインの装飾要素
|
||||
|
||||
private val dText : DecodeEnv.() -> Unit = {
|
||||
appendText(nodeSource)
|
||||
}
|
||||
|
||||
private val dBig : DecodeEnv.() -> Unit = {
|
||||
appendText(data?.get(0))
|
||||
setSpan(MisskeyBigSpan(font_bold))
|
||||
}
|
||||
|
||||
private val dBold : DecodeEnv.() -> Unit = {
|
||||
appendText(data?.get(0))
|
||||
setSpan(CalligraphyTypefaceSpan(font_bold))
|
||||
}
|
||||
|
||||
private val dCodeInline : DecodeEnv.() -> Unit = {
|
||||
appendTextCode(data?.get(0))
|
||||
setSpan(BackgroundColorSpan(0x40808080))
|
||||
setSpan(CalligraphyTypefaceSpan(Typeface.MONOSPACE))
|
||||
}
|
||||
|
||||
private val dMotion : DecodeEnv.() -> Unit = {
|
||||
val code = data?.get(0)
|
||||
appendText(code)
|
||||
setSpan(MisskeyMotionSpan(ActMain.timeline_font))
|
||||
}
|
||||
|
||||
private val dUrl : DecodeEnv.() -> Unit = {
|
||||
val url = data?.get(0)
|
||||
if(url?.isNotEmpty() == true) {
|
||||
appendLink(url, url, allowShort = true)
|
||||
}
|
||||
}
|
||||
|
||||
private val dLink : DecodeEnv.() -> Unit = {
|
||||
val title = data?.get(0) ?: "?"
|
||||
val url = data?.get(1)
|
||||
// val silent = data?.get(2)
|
||||
// silentはプレビュー表示を抑制するが、Subwayにはもともとないので関係なかった
|
||||
if(url?.isNotEmpty() == true) {
|
||||
appendLink(title, url)
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// ブロック要素
|
||||
|
||||
private val dCodeBlock : DecodeEnv.() -> Unit = {
|
||||
closePreviousBlock()
|
||||
appendTextCode(trimBlock(data?.get(0)))
|
||||
setSpan(BackgroundColorSpan(0x40808080))
|
||||
setSpan(RelativeSizeSpan(0.7f))
|
||||
setSpan(CalligraphyTypefaceSpan(Typeface.MONOSPACE))
|
||||
appendText("\n")
|
||||
}
|
||||
|
||||
private val dQuote : DecodeEnv.() -> Unit = {
|
||||
closePreviousBlock()
|
||||
appendText(trimBlock(data?.get(0)))
|
||||
setSpan(BackgroundColorSpan(0x20808080))
|
||||
setSpan(CalligraphyTypefaceSpan(Typeface.defaultFromStyle(Typeface.ITALIC)))
|
||||
appendText("\n")
|
||||
}
|
||||
|
||||
private val dTitle : DecodeEnv.() -> Unit = {
|
||||
closePreviousBlock()
|
||||
appendText(trimBlock(data?.get(0)))
|
||||
setSpan(AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER))
|
||||
setSpan(BackgroundColorSpan(0x20808080))
|
||||
setSpan(RelativeSizeSpan(1.5f))
|
||||
appendText("\n")
|
||||
}
|
||||
|
||||
private val dSearch : DecodeEnv.() -> Unit = {
|
||||
val text = data?.get(0)
|
||||
closePreviousBlock()
|
||||
val kw_start = sb.length // キーワードの開始位置
|
||||
appendText(text)
|
||||
appendText(" ")
|
||||
start = sb.length // 検索リンクの開始位置
|
||||
appendLink(
|
||||
context.getString(R.string.search),
|
||||
"https://www.google.co.jp/search?q=" + (text
|
||||
?: "Subway Tooter").encodePercent()
|
||||
)
|
||||
sb.setSpan(
|
||||
RelativeSizeSpan(1.2f),
|
||||
kw_start,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
appendText("\n")
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
private class ParseParams(val text : String) {
|
||||
var remain : String = ""
|
||||
var previous : String = ""
|
||||
|
||||
var pos : Int = 0
|
||||
set(value) {
|
||||
field = value
|
||||
|
@ -698,14 +542,14 @@ object MisskeyMarkdownDecoder {
|
|||
private class Node(
|
||||
var start : Int, // ソース文字列中の開始位置
|
||||
var length : Int, // ソース文字列中の長さ
|
||||
var decoder : DecodeEnv.() -> Unit, // ノード種別
|
||||
var data : ArrayList<String?>? // パラメータ
|
||||
var data : ArrayList<String?>?, // パラメータ
|
||||
var decoder : DecodeEnv.() -> Unit // ノード種別
|
||||
)
|
||||
|
||||
// generate lambda with captured parameter.
|
||||
private fun simpleParser(
|
||||
decoder : DecodeEnv.() -> Unit,
|
||||
pattern : Pattern
|
||||
, decoder : DecodeEnv.() -> Unit
|
||||
) : (ParseParams) -> Node? = { env : ParseParams ->
|
||||
val matcher = pattern.matcher(env.remain)
|
||||
when {
|
||||
|
@ -713,8 +557,8 @@ object MisskeyMarkdownDecoder {
|
|||
else -> Node(
|
||||
env.pos,
|
||||
matcher.end(),
|
||||
decoder,
|
||||
arrayListOf(matcher.group(1))
|
||||
arrayListOf(matcher.group(1)),
|
||||
decoder
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -729,33 +573,46 @@ object MisskeyMarkdownDecoder {
|
|||
|
||||
addParser(
|
||||
"\""
|
||||
, simpleParser(
|
||||
dQuote,
|
||||
Pattern.compile("""^"([\s\S]+?)\n"""")
|
||||
)
|
||||
, simpleParser(Pattern.compile("""^"([\s\S]+?)\n"""")) {
|
||||
closePreviousBlock()
|
||||
appendText(trimBlock(data?.get(0)))
|
||||
setSpan(BackgroundColorSpan(0x20808080))
|
||||
setSpan(CalligraphyTypefaceSpan(Typeface.defaultFromStyle(Typeface.ITALIC)))
|
||||
appendText("\n")
|
||||
}
|
||||
)
|
||||
|
||||
addParser(
|
||||
":"
|
||||
, simpleParser(
|
||||
dEmoji,
|
||||
Pattern.compile("""^:([a-zA-Z0-9+-_]+):""")
|
||||
)
|
||||
) {
|
||||
val code = data?.get(0)
|
||||
if(code?.isNotEmpty() == true) {
|
||||
appendText(options.decodeEmoji(":$code:"))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val dMotion : DecodeEnv.() -> Unit = {
|
||||
val code = data?.get(0)
|
||||
appendText(code)
|
||||
setSpan(MisskeyMotionSpan(ActMain.timeline_font))
|
||||
}
|
||||
|
||||
addParser(
|
||||
"("
|
||||
, simpleParser(
|
||||
dMotion,
|
||||
Pattern.compile("""^\Q(((\E(.+?)\Q)))\E""")
|
||||
, dMotion
|
||||
)
|
||||
)
|
||||
|
||||
addParser(
|
||||
"<"
|
||||
, simpleParser(
|
||||
dMotion,
|
||||
Pattern.compile("""^<motion>(.+?)</motion>""")
|
||||
, dMotion
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -764,21 +621,29 @@ object MisskeyMarkdownDecoder {
|
|||
// 処理順序に意味があるので入れ替えないこと
|
||||
// 記号列が長い順
|
||||
, simpleParser(
|
||||
dBig,
|
||||
Pattern.compile("""^\Q***\E(.+?)\Q***\E""")
|
||||
)
|
||||
) {
|
||||
appendText(data?.get(0))
|
||||
setSpan(MisskeyBigSpan(font_bold))
|
||||
}
|
||||
, simpleParser(
|
||||
dBold,
|
||||
Pattern.compile("""^\Q**\E(.+?)\Q**\E""")
|
||||
)
|
||||
) {
|
||||
appendText(data?.get(0))
|
||||
setSpan(CalligraphyTypefaceSpan(font_bold))
|
||||
}
|
||||
)
|
||||
|
||||
addParser(
|
||||
"h"
|
||||
, simpleParser(
|
||||
dUrl,
|
||||
Pattern.compile("""^(https?://[\w/:%#@${'$'}&?!()\[\]~.=+\-]+)""")
|
||||
)
|
||||
) {
|
||||
val url = data?.get(0)
|
||||
if(url?.isNotEmpty() == true) {
|
||||
appendLink(url, url, allowShort = true)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 検索だけはボタン開始位置からバックトラックした方が効率的
|
||||
|
@ -811,20 +676,48 @@ object MisskeyMarkdownDecoder {
|
|||
else -> Node(
|
||||
env.pos - (keyword.length + 1)
|
||||
, buttonLength + (keyword.length + 1)
|
||||
, dSearch
|
||||
, arrayListOf(keyword)
|
||||
)
|
||||
|
||||
) {
|
||||
val text = data?.get(0)
|
||||
closePreviousBlock()
|
||||
val kw_start = sb.length // キーワードの開始位置
|
||||
appendText(text)
|
||||
appendText(" ")
|
||||
start = sb.length // 検索リンクの開始位置
|
||||
appendLink(
|
||||
context.getString(R.string.search),
|
||||
"https://www.google.co.jp/search?q=" + (text
|
||||
?: "Subway Tooter").encodePercent()
|
||||
)
|
||||
sb.setSpan(
|
||||
RelativeSizeSpan(1.2f),
|
||||
kw_start,
|
||||
sb.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
appendText("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val titleParser = simpleParser(dTitle, Pattern.compile("""^[【\[](.+?)[】\]]\n"""))
|
||||
val titleParser = simpleParser(
|
||||
Pattern.compile("""^[【\[](.+?)[】\]]\n""")
|
||||
) {
|
||||
closePreviousBlock()
|
||||
appendText(trimBlock(data?.get(0)))
|
||||
setSpan(AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER))
|
||||
setSpan(BackgroundColorSpan(0x20808080))
|
||||
setSpan(RelativeSizeSpan(1.5f))
|
||||
appendText("\n")
|
||||
}
|
||||
|
||||
val reLink = Pattern.compile(
|
||||
"""^\??\[([^\[\]]+?)]\((https?://[\w/:%#@${'$'}&?!()\[\]~.=+\-]+?)\)"""
|
||||
)
|
||||
|
||||
|
||||
val linkParser = { env : ParseParams ->
|
||||
val matcher = reLink.matcher(env.remain)
|
||||
when {
|
||||
|
@ -832,13 +725,20 @@ object MisskeyMarkdownDecoder {
|
|||
else -> Node(
|
||||
env.pos
|
||||
, matcher.end()
|
||||
, dLink
|
||||
, arrayListOf(
|
||||
matcher.group(1) // title
|
||||
, matcher.group(2) // url
|
||||
, env.remain[0].toString() // silent なら "?" になる
|
||||
)
|
||||
)
|
||||
) {
|
||||
val title = data?.get(0) ?: "?"
|
||||
val url = data?.get(1)
|
||||
// val silent = data?.get(2)
|
||||
// silentはプレビュー表示を抑制するが、Subwayにはもともとないので関係なかった
|
||||
if(url?.isNotEmpty() == true) {
|
||||
appendLink(title, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -859,9 +759,62 @@ object MisskeyMarkdownDecoder {
|
|||
else -> Node(
|
||||
env.pos
|
||||
, matcher.end()
|
||||
, dMention
|
||||
, arrayListOf(matcher.group(1), matcher.group(2)) // username, host
|
||||
)
|
||||
) {
|
||||
|
||||
val username = data?.get(0) ?: ""
|
||||
val host = data?.get(1) ?: ""
|
||||
|
||||
val linkHelper = linkHelper
|
||||
if(linkHelper == null) {
|
||||
appendText(
|
||||
when {
|
||||
host.isEmpty() -> "@$username"
|
||||
else -> "@$username@$host"
|
||||
}
|
||||
)
|
||||
} else {
|
||||
|
||||
val shortAcct = when {
|
||||
host.isEmpty()
|
||||
|| host.equals(linkHelper.host, ignoreCase = true) ->
|
||||
username
|
||||
else ->
|
||||
"$username@$host"
|
||||
}
|
||||
|
||||
val userHost = when {
|
||||
host.isEmpty() -> linkHelper.host
|
||||
else -> host
|
||||
}
|
||||
val userUrl = "https://$userHost/@$username"
|
||||
|
||||
var mentions = sb.mentions
|
||||
if(mentions == null) {
|
||||
mentions = ArrayList()
|
||||
sb.mentions = mentions
|
||||
}
|
||||
|
||||
if(mentions.find { it.acct == shortAcct } == null) {
|
||||
mentions.add(
|
||||
TootMention(
|
||||
EntityIdLong(- 1L)
|
||||
, userUrl
|
||||
, shortAcct
|
||||
, username
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
appendLink(
|
||||
when {
|
||||
Pref.bpMentionFullAcct(App1.pref) -> "@$username@$userHost"
|
||||
else -> "@$shortAcct"
|
||||
}
|
||||
, userUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -880,10 +833,18 @@ object MisskeyMarkdownDecoder {
|
|||
else -> Node(
|
||||
env.pos
|
||||
, matcher.end()
|
||||
, dHashtag
|
||||
, arrayListOf(matcher.group(1))
|
||||
// 先頭の#を含まないハッシュタグ
|
||||
)
|
||||
, arrayListOf(matcher.group(1)) // 先頭の#を含まない
|
||||
|
||||
) {
|
||||
val linkHelper = linkHelper
|
||||
val tag = data?.get(0)
|
||||
if(tag?.isNotEmpty() == true && linkHelper != null) {
|
||||
appendLink(
|
||||
"#$tag",
|
||||
"https://${linkHelper.host}/tags/" + tag.encodePercent()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -892,14 +853,24 @@ object MisskeyMarkdownDecoder {
|
|||
addParser(
|
||||
"`"
|
||||
, simpleParser(
|
||||
dCodeBlock,
|
||||
Pattern.compile("""^```(.+?)```""", Pattern.DOTALL)
|
||||
)
|
||||
|
||||
) {
|
||||
closePreviousBlock()
|
||||
appendTextCode(trimBlock(data?.get(0)))
|
||||
setSpan(BackgroundColorSpan(0x40808080))
|
||||
setSpan(RelativeSizeSpan(0.7f))
|
||||
setSpan(CalligraphyTypefaceSpan(Typeface.MONOSPACE))
|
||||
appendText("\n")
|
||||
}
|
||||
, simpleParser(
|
||||
dCodeInline,
|
||||
Pattern.compile("""^`([^`´\x0d\x0a]+)`""")
|
||||
// インラインコードは内部にとある文字を含むと認識されない。理由は顔文字と衝突するからだとか
|
||||
)
|
||||
Pattern.compile("""^`([^`´\x0d\x0a]+)`""")
|
||||
) {
|
||||
appendTextCode(data?.get(0))
|
||||
setSpan(BackgroundColorSpan(0x40808080))
|
||||
setSpan(CalligraphyTypefaceSpan(Typeface.MONOSPACE))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -912,15 +883,7 @@ object MisskeyMarkdownDecoder {
|
|||
.replace(reEndEmptyLines, "")
|
||||
}
|
||||
|
||||
// 配列中の要素をラムダ式で変換して、戻り値が非nullならそこで処理を打ち切る
|
||||
private inline fun <T, V> Array<out T>.firstNonNull(predicate : (T) -> V?) : V? {
|
||||
for(element in this) return predicate(element) ?: continue
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parse(source : String?) : ArrayList<Node> {
|
||||
val result = ArrayList<Node>()
|
||||
|
||||
private fun parse(source : String?, callback : (Node) -> Unit) {
|
||||
if(source != null) {
|
||||
val env = ParseParams(source)
|
||||
val end = source.length
|
||||
|
@ -930,7 +893,11 @@ object MisskeyMarkdownDecoder {
|
|||
// 直前のノードの終了位置から次のノードの開始位置の手前までをresultに追加する
|
||||
fun closeText(endText : Int) {
|
||||
val length = endText - lastEnd
|
||||
if(length > 0) result.add(Node(lastEnd, length, dText, null))
|
||||
if(length > 0) callback(
|
||||
Node(lastEnd, length, null) {
|
||||
appendText(nodeSource)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
while(pos < end) {
|
||||
|
@ -941,19 +908,17 @@ object MisskeyMarkdownDecoder {
|
|||
}
|
||||
env.pos = pos
|
||||
val node = lastParsers.firstNonNull { it(env) }
|
||||
if( node == null) {
|
||||
if(node == null) {
|
||||
++ pos
|
||||
continue
|
||||
}
|
||||
closeText(node.start)
|
||||
result.add(node)
|
||||
callback(node)
|
||||
lastEnd = node.start + node.length
|
||||
pos = lastEnd
|
||||
}
|
||||
closeText(pos)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun decodeMarkdown(options : DecodeOptions, src : String?) =
|
||||
|
@ -961,14 +926,12 @@ object MisskeyMarkdownDecoder {
|
|||
try {
|
||||
val env = DecodeEnv(options, this)
|
||||
|
||||
if(src != null) {
|
||||
for(node in parse(src)) {
|
||||
env.nodeSource = src.substring(node.start, node.start + node.length)
|
||||
env.start = length
|
||||
env.data = node.data
|
||||
val decoder = node.decoder
|
||||
env.decoder()
|
||||
}
|
||||
if(src != null) parse(src) { node ->
|
||||
env.nodeSource = src.substring(node.start, node.start + node.length)
|
||||
env.start = length
|
||||
env.data = node.data
|
||||
val decoder = node.decoder
|
||||
env.decoder()
|
||||
}
|
||||
|
||||
// 末尾の空白を取り除く
|
||||
|
|
|
@ -967,6 +967,115 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/background_color_unlisted"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorUnlistedEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorUnlistedReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/background_color_follower"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorFollowerEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorFollowerReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/background_color_direct_with_user"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorDirectWithUserEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorDirectWithUserReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/background_color_direct_no_user"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorDirectNoUserEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBackgroundColorDirectNoUserReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -772,6 +772,10 @@
|
|||
<string name="reply_to">Reply to %1$s</string>
|
||||
<string name="deleted_at">deleted at %1$s</string>
|
||||
<string name="already_reactioned">already reactioned.</string>
|
||||
<string name="background_color_unlisted">Toot background color of \'Unlisted\' visibility</string>
|
||||
<string name="background_color_follower">Toot background color of \'follower\' visibility</string>
|
||||
<string name="background_color_direct_with_user">Toot background color of \'Direct to users\' visibility</string>
|
||||
<string name="background_color_direct_no_user">Toot background color of \'Direct only me\' visibility</string>
|
||||
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->
|
||||
|
|
|
@ -1049,5 +1049,9 @@
|
|||
<string name="reply_to">%1$sへの返信</string>
|
||||
<string name="deleted_at">%1$sに削除されました</string>
|
||||
<string name="already_reactioned">リアクション済みです</string>
|
||||
<string name="background_color_unlisted">トゥート背景色 \'未収載\'</string>
|
||||
<string name="background_color_follower">トゥート背景色 \'フォロワーのみ\'</string>
|
||||
<string name="background_color_direct_with_user">トゥート背景色 \'ダイレクト(対象ユーザあり)\'</string>
|
||||
<string name="background_color_direct_no_user">トゥート背景色 \'ダイレクト(自分のみ)\'</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -757,5 +757,9 @@
|
|||
<string name="reply_to">Reply to %1$s</string>
|
||||
<string name="deleted_at">deleted at %1$s</string>
|
||||
<string name="already_reactioned">already reactioned.</string>
|
||||
<string name="background_color_unlisted">Toot background color of \'Unlisted\' visibility</string>
|
||||
<string name="background_color_follower">Toot background color of \'follower\' visibility</string>
|
||||
<string name="background_color_direct_with_user">Toot background color of \'Direct to users\' visibility</string>
|
||||
<string name="background_color_direct_no_user">Toot background color of \'Direct only me\' visibility</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -17,6 +17,9 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation "com.android.support:appcompat-v7:$asl_version"
|
||||
|
||||
implementation 'com.google.android:flexbox:0.3.2'
|
||||
|
||||
}
|
||||
|
||||
//apply plugin: 'com.getkeepsafe.dexcount'
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
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:layout_height="wrap_content"
|
||||
tools:ignore="RtlHardcoded"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
@ -18,16 +20,20 @@
|
|||
style="@style/cpv_ColorPickerViewStyle"
|
||||
android:padding="16dp"/>
|
||||
|
||||
<LinearLayout
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
tools:ignore="RtlHardcoded"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_start"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_old"
|
||||
android:layout_width="@dimen/cpv_dialog_preview_width"
|
||||
|
@ -49,11 +55,7 @@
|
|||
android:layout_width="@dimen/cpv_dialog_preview_width"
|
||||
android:layout_height="@dimen/cpv_dialog_preview_height"
|
||||
app:cpv_colorShape="square"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -63,18 +65,20 @@
|
|||
android:focusableInTouchMode="true"
|
||||
android:gravity="right"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="RtlHardcoded">
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="#"
|
||||
android:typeface="monospace"
|
||||
tools:ignore="HardcodedText"/>
|
||||
tools:ignore="HardcodedText"
|
||||
android:labelFor="@+id/cpv_hex"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/cpv_hex"
|
||||
android:layout_width="110dp"
|
||||
android:layout_width="110sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789ABCDEFabcdef"
|
||||
android:focusable="true"
|
||||
|
@ -87,7 +91,7 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
|
@ -4,87 +4,103 @@
|
|||
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">
|
||||
|
||||
<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="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPickerView
|
||||
android:id="@id/cpv_color_picker_view"
|
||||
style="@style/cpv_ColorPickerViewStyle"
|
||||
android:padding="16dp"/>
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
tools:ignore="RtlHardcoded"
|
||||
>
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_old"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
app:cpv_colorShape="square"
|
||||
<com.jrummyapps.android.colorpicker.ColorPickerView
|
||||
android:id="@id/cpv_color_picker_view"
|
||||
style="@style/cpv_ColorPickerViewStyle"
|
||||
android:padding="16dp"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cpv_arrow_right"
|
||||
android:layout_width="wrap_content"
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingRight="3dp"
|
||||
android:src="@drawable/cpv_ic_arrow_right_black_24dp"
|
||||
tools:ignore="ContentDescription"/>
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_start"
|
||||
tools:ignore="RtlHardcoded"
|
||||
>
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_new"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
app:cpv_colorShape="square"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:descendantFocusability="beforeDescendants"
|
||||
android:focusableInTouchMode="true"
|
||||
android:gravity="right"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="#"
|
||||
android:typeface="monospace"
|
||||
tools:ignore="HardcodedText"/>
|
||||
>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/cpv_hex"
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_old"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
app:cpv_colorShape="square"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cpv_arrow_right"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingRight="3dp"
|
||||
android:src="@drawable/cpv_ic_arrow_right_black_24dp"
|
||||
tools:ignore="ContentDescription"
|
||||
/>
|
||||
|
||||
<com.jrummyapps.android.colorpicker.ColorPanelView
|
||||
android:id="@id/cpv_color_panel_new"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
app:cpv_colorShape="square"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789ABCDEFabcdef"
|
||||
android:focusable="true"
|
||||
android:imeOptions="actionGo"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:maxLength="8"
|
||||
android:maxLines="1"
|
||||
android:minWidth="110dp"
|
||||
android:typeface="monospace"
|
||||
tools:text="88888888"/>
|
||||
android:layout_marginLeft="8dp"
|
||||
android:descendantFocusability="beforeDescendants"
|
||||
android:focusableInTouchMode="true"
|
||||
android:gravity="right"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="RtlHardcoded"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="#"
|
||||
android:typeface="monospace"
|
||||
tools:ignore="HardcodedText"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/cpv_hex"
|
||||
android:layout_width="110sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789ABCDEFabcdef"
|
||||
android:focusable="true"
|
||||
android:imeOptions="actionGo"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:maxLength="8"
|
||||
android:maxLines="1"
|
||||
android:minWidth="110dp"
|
||||
android:typeface="monospace"
|
||||
tools:text="88888888"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
Loading…
Reference in New Issue